/*
* Copyright 2003-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.ide.bookmark;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.util.messages.MessageBusConnection;
import jetbrains.mps.ide.ThreadUtils;
import jetbrains.mps.ide.bookmark.BookmarkManager.BookmarkListener;
import jetbrains.mps.ide.editor.util.EditorComponentUtil;
import jetbrains.mps.nodeEditor.EditorComponent;
import jetbrains.mps.nodeEditor.EditorMessageIconRenderer;
import jetbrains.mps.nodeEditor.cells.EditorCell;
import jetbrains.mps.nodeEditor.highlighter.EditorComponentCreateListener;
import jetbrains.mps.openapi.editor.update.UpdaterListener;
import jetbrains.mps.openapi.editor.update.UpdaterListenerAdapter;
import jetbrains.mps.smodel.ModelAccess;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import javax.swing.Icon;
import javax.swing.JPopupMenu;
import java.awt.Cursor;
import java.util.Collections;
import java.util.List;
/**
* evgeny, 11/14/11
*/
public class BookmarksUIComponent implements ProjectComponent {
private final Project myProject;
private final FileEditorManager myFileEditorManager;
private final BookmarkManager myBookmarkManager;
private MessageBusConnection myMessageBusConnection;
private final EditorComponentCreateListener editorListener = new MyEditorComponentCreateListener();
private final BookmarkListener bookmarkListener = new MyBookmarkListener();
private final UpdaterListener myUpdaterListener = new UpdaterListenerAdapter() {
@Override
public void editorUpdated(jetbrains.mps.openapi.editor.EditorComponent editorComponent) {
BookmarksUIComponent.this.onEditorRebuilt((EditorComponent) editorComponent);
}
};
public BookmarksUIComponent(Project project, FileEditorManager fileEditorManager, BookmarkManager bookmarkManager) {
myProject = project;
myFileEditorManager = fileEditorManager;
myBookmarkManager = bookmarkManager;
}
@Override
public void initComponent() {
myBookmarkManager.addBookmarkListener(bookmarkListener);
myMessageBusConnection = myProject.getMessageBus().connect();
myMessageBusConnection.subscribe(EditorComponentCreateListener.EDITOR_COMPONENT_CREATION, editorListener);
for (jetbrains.mps.openapi.editor.EditorComponent editor : EditorComponentUtil.getAllEditorComponents(myFileEditorManager, true)) {
if (editor instanceof EditorComponent) {
editorComponentCreated((EditorComponent) editor);
}
}
}
@Override
public void disposeComponent() {
myMessageBusConnection.disconnect();
myBookmarkManager.removeBookmarkListener(bookmarkListener);
}
private void onEditorRebuilt(EditorComponent editorComponent) {
ThreadUtils.assertEDT();
editorComponent.getLeftEditorHighlighter().removeAllIconRenderers(BookmarkIconRenderer.TYPE);
if (myBookmarkManager != null) {
SNode editedNode = editorComponent.getEditedNode();
if (editedNode != null) {
for (Pair<SNode, Integer> bookmark : myBookmarkManager.getBookmarks(editedNode.getContainingRoot())) {
addRenderer(editorComponent, bookmark.o1, bookmark.o2);
}
}
}
}
private void editorComponentCreated(@NotNull EditorComponent editorComponent) {
editorComponent.getUpdater().addListener(myUpdaterListener);
SNode editedNode = editorComponent.getEditedNode();
if (editedNode != null) {
boolean modified = false;
for (Pair<SNode, Integer> bookmark : myBookmarkManager.getBookmarks(editedNode.getContainingRoot())) {
modified |= addRenderer(editorComponent, bookmark.o1, bookmark.o2);
}
if (modified) {
editorComponent.repaintExternalComponent();
}
}
}
private void editorComponentDisposed(@NotNull EditorComponent editorComponent) {
editorComponent.getUpdater().removeListener(myUpdaterListener);
editorComponent.getLeftEditorHighlighter().removeAllIconRenderers(BookmarkIconRenderer.TYPE);
}
@Override
public void projectOpened() {
}
@Override
public void projectClosed() {
}
@NotNull
@Override
public String getComponentName() {
return "Bookmarks UI";
}
@NotNull
private List<EditorComponent> findComponentsForNode(SNode node) {
if (node != null) {
return EditorComponentUtil.findComponentForNode(node, myFileEditorManager);
}
return Collections.emptyList();
}
private void addBookmark(SNode node, int number) {
ThreadUtils.assertEDT();
List<EditorComponent> editorComponents = findComponentsForNode(node);
for (EditorComponent editorComponent : editorComponents) {
if (addRenderer(editorComponent, node, number)) {
editorComponent.repaintExternalComponent();
}
}
}
private boolean addRenderer(EditorComponent editorComponent, SNode node, int number) {
EditorCell nodeCell = editorComponent.findNodeCell(node);
if (nodeCell == null) {
// LOG.error("can't find a cell for node " + node);
return false;
}
editorComponent.getLeftEditorHighlighter().addIconRenderer(new BookmarkIconRenderer(node, number));
return true;
}
private void removeBookmark(SNode node, int number) {
ThreadUtils.assertEDT();
List<EditorComponent> editorComponents = findComponentsForNode(node);
for (EditorComponent editorComponent : editorComponents) {
EditorCell nodeCell = editorComponent.findNodeCell(node);
if (nodeCell == null) {
// LOG.error("can't find a cell for node " + node);
continue;
}
editorComponent.getLeftEditorHighlighter().removeIconRenderer(node, BookmarkIconRenderer.TYPE);
editorComponent.repaintExternalComponent();
// todo should it be executed in ED thread?
}
}
private class MyEditorComponentCreateListener implements EditorComponentCreateListener {
private MyEditorComponentCreateListener() {
}
@Override
public void editorComponentCreated(@NotNull EditorComponent editorComponent) {
BookmarksUIComponent.this.editorComponentCreated(editorComponent);
}
@Override
public void editorComponentDisposed(@NotNull EditorComponent editorComponent) {
BookmarksUIComponent.this.editorComponentDisposed(editorComponent);
}
}
private class MyBookmarkListener implements BookmarkListener {
@Override
public void bookmarkAdded(int number, SNode node) {
addBookmark(node, number);
}
@Override
public void bookmarkRemoved(int number, SNode node) {
removeBookmark(node, number);
}
}
private static class BookmarkIconRenderer implements EditorMessageIconRenderer {
private static final IconRendererType TYPE = new IconRendererType(3);
private SNode myNode;
private int myNumber;
private BookmarkIconRenderer(SNode node, int number) {
myNode = node;
myNumber = number;
}
@Override
public Icon getIcon() {
return BookmarkManager.getIcon(myNumber);
}
@Override
public String getTooltipText() {
String nodePresentation = ModelAccess.instance().runReadAction(new Computable<String>() {
@Override
public String compute() {
return myNode.getPresentation();
}
});
return (myNumber != -1 ? "Bookmark " + myNumber + " (" : "Bookmark (") + nodePresentation + ")";
}
@Override
public SNode getNode() {
return myNode;
}
public int getNumber() {
return myNumber;
}
@Override
public IconRendererType getType() {
return TYPE;
}
@Override
public jetbrains.mps.openapi.editor.cells.EditorCell getAnchorCell(jetbrains.mps.openapi.editor.cells.EditorCell bigCell) {
return bigCell;
}
@Override
public Cursor getMouseOverCursor() {
return myNumber != -1 ? Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) : null;
}
@Override
public AnAction getClickAction() {
return myNumber != -1 ? ActionManager.getInstance().getAction("jetbrains.mps.ide.actions.GoToBookmark_Action#" + myNumber + "!") : null;
}
@Override
public JPopupMenu getPopupMenu() {
return null;
}
}
}