/* * 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; } } }