| /* |
| * Copyright 2000-2013 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 com.intellij.ide.bookmarks; |
| |
| import com.intellij.codeInsight.daemon.GutterMark; |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.ide.structureView.StructureViewBuilder; |
| import com.intellij.ide.structureView.StructureViewModel; |
| import com.intellij.ide.structureView.TreeBasedStructureViewBuilder; |
| import com.intellij.lang.LanguageStructureViewBuilder; |
| import com.intellij.navigation.ItemPresentation; |
| import com.intellij.navigation.NavigationItem; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.RangeMarker; |
| import com.intellij.openapi.editor.colors.CodeInsightColors; |
| import com.intellij.openapi.editor.colors.EditorColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.ex.MarkupModelEx; |
| import com.intellij.openapi.editor.ex.RangeHighlighterEx; |
| import com.intellij.openapi.editor.impl.DocumentMarkupModel; |
| import com.intellij.openapi.editor.markup.GutterIconRenderer; |
| import com.intellij.openapi.editor.markup.HighlighterLayer; |
| import com.intellij.openapi.editor.markup.RangeHighlighter; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.ui.JBColor; |
| import com.intellij.util.PlatformIcons; |
| import com.intellij.util.Processor; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| |
| public class Bookmark implements Navigatable { |
| private static final JBColor ICON_BACKGROUND_COLOR = new JBColor(new Color(0xffffcc), new Color(0x675133)); |
| public static final Icon DEFAULT_ICON = new MyDefaultIcon(); |
| |
| private final VirtualFile myFile; |
| @NotNull private final OpenFileDescriptor myTarget; |
| private final Project myProject; |
| |
| private String myDescription; |
| private char myMnemonic = 0; |
| public static final Font MNEMONIC_FONT = new Font("Monospaced", 0, 11); |
| |
| public Bookmark(@NotNull Project project, @NotNull VirtualFile file, int line, @NotNull String description) { |
| myFile = file; |
| myProject = project; |
| myDescription = description; |
| |
| myTarget = new OpenFileDescriptor(project, file, line, -1, true); |
| |
| addHighlighter(); |
| } |
| |
| public void updateHighlighter() { |
| release(); |
| addHighlighter(); |
| } |
| |
| private void addHighlighter() { |
| Document document = FileDocumentManager.getInstance().getCachedDocument(getFile()); |
| if (document != null) { |
| createHighlighter((MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, true)); |
| } |
| } |
| |
| public RangeHighlighter createHighlighter(@NotNull MarkupModelEx markup) { |
| final RangeHighlighterEx myHighlighter; |
| int line = getLine(); |
| if (line >= 0) { |
| myHighlighter = markup.addPersistentLineHighlighter(line, HighlighterLayer.ERROR + 1, null); |
| if (myHighlighter != null) { |
| myHighlighter.setGutterIconRenderer(new MyGutterIconRenderer(this)); |
| |
| TextAttributes textAttributes = |
| EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.BOOKMARKS_ATTRIBUTES); |
| |
| Color stripeColor = textAttributes.getErrorStripeColor(); |
| myHighlighter.setErrorStripeMarkColor(stripeColor != null ? stripeColor : Color.black); |
| myHighlighter.setErrorStripeTooltip(getBookmarkTooltip()); |
| |
| TextAttributes attributes = myHighlighter.getTextAttributes(); |
| if (attributes == null) { |
| attributes = new TextAttributes(); |
| } |
| attributes.setBackgroundColor(textAttributes.getBackgroundColor()); |
| attributes.setForegroundColor(textAttributes.getForegroundColor()); |
| myHighlighter.setTextAttributes(attributes); |
| } |
| } |
| else { |
| myHighlighter = null; |
| } |
| return myHighlighter; |
| } |
| |
| public Document getDocument() { |
| return FileDocumentManager.getInstance().getDocument(getFile()); |
| } |
| |
| public void release() { |
| int line = getLine(); |
| if (line < 0) { |
| return; |
| } |
| final Document document = getDocument(); |
| if (document == null) return; |
| MarkupModelEx markup = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, true); |
| final Document markupDocument = markup.getDocument(); |
| if (markupDocument.getLineCount() <= line) return; |
| final int startOffset = markupDocument.getLineStartOffset(line); |
| final int endOffset = markupDocument.getLineEndOffset(line); |
| |
| final Ref<RangeHighlighterEx> found = new Ref<RangeHighlighterEx>(); |
| markup.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() { |
| @Override |
| public boolean process(RangeHighlighterEx highlighter) { |
| GutterMark renderer = highlighter.getGutterIconRenderer(); |
| if (renderer instanceof MyGutterIconRenderer && ((MyGutterIconRenderer)renderer).myBookmark == Bookmark.this) { |
| found.set(highlighter); |
| return false; |
| } |
| return true; |
| } |
| }); |
| if (!found.isNull()) found.get().dispose(); |
| } |
| |
| public Icon getIcon() { |
| return myMnemonic == 0 ? DEFAULT_ICON : MnemonicIcon.getIcon(myMnemonic); |
| } |
| |
| public String getDescription() { |
| return myDescription; |
| } |
| |
| public void setDescription(String description) { |
| myDescription = description; |
| } |
| |
| public char getMnemonic() { |
| return myMnemonic; |
| } |
| |
| public void setMnemonic(char mnemonic) { |
| myMnemonic = Character.toUpperCase(mnemonic); |
| } |
| |
| @NotNull |
| public VirtualFile getFile() { |
| return myFile; |
| } |
| |
| @Nullable |
| public String getNotEmptyDescription() { |
| return StringUtil.isEmpty(myDescription) ? null : myDescription; |
| } |
| |
| public boolean isValid() { |
| if (!getFile().isValid()) { |
| return false; |
| } |
| |
| // There is a possible case that target document line that is referenced by the current bookmark is removed. We assume |
| // that corresponding range marker becomes invalid then. |
| RangeMarker rangeMarker = myTarget.getRangeMarker(); |
| return rangeMarker == null || rangeMarker.isValid(); |
| } |
| |
| @Override |
| public boolean canNavigate() { |
| return myTarget.canNavigate(); |
| } |
| |
| @Override |
| public boolean canNavigateToSource() { |
| return myTarget.canNavigateToSource(); |
| } |
| |
| @Override |
| public void navigate(boolean requestFocus) { |
| myTarget.navigate(requestFocus); |
| } |
| |
| public int getLine() { |
| RangeMarker marker = myTarget.getRangeMarker(); |
| if (marker != null && marker.isValid()) { |
| Document document = marker.getDocument(); |
| return document.getLineNumber(marker.getStartOffset()); |
| } |
| return myTarget.getLine(); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder(getQualifiedName()); |
| String description = StringUtil.escapeXml(getNotEmptyDescription()); |
| if (description != null) { |
| result.append(": ").append(description); |
| } |
| return result.toString(); |
| } |
| |
| public String getQualifiedName() { |
| String presentableUrl = myFile.getPresentableUrl(); |
| if (myFile.isDirectory()) return presentableUrl; |
| |
| PsiDocumentManager.getInstance(myProject).commitAllDocuments(); |
| final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(myFile); |
| |
| if (psiFile == null) return presentableUrl; |
| |
| StructureViewBuilder builder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(psiFile); |
| if (builder instanceof TreeBasedStructureViewBuilder) { |
| StructureViewModel model = ((TreeBasedStructureViewBuilder)builder).createStructureViewModel(null); |
| Object element; |
| try { |
| element = model.getCurrentEditorElement(); |
| } |
| finally { |
| model.dispose(); |
| } |
| if (element instanceof NavigationItem) { |
| ItemPresentation presentation = ((NavigationItem)element).getPresentation(); |
| if (presentation != null) { |
| presentableUrl = ((NavigationItem)element).getName() + " " + presentation.getLocationString(); |
| } |
| } |
| } |
| |
| return IdeBundle.message("bookmark.file.X.line.Y", presentableUrl, getLine() + 1); |
| } |
| |
| private String getBookmarkTooltip() { |
| StringBuilder result = new StringBuilder("Bookmark"); |
| if (myMnemonic != 0) { |
| result.append(" ").append(myMnemonic); |
| } |
| String description = StringUtil.escapeXml(getNotEmptyDescription()); |
| if (description != null) { |
| result.append(": ").append(description); |
| } |
| return result.toString(); |
| } |
| |
| static class MnemonicIcon implements Icon { |
| private static final MnemonicIcon[] cache = new MnemonicIcon[36];//0..9 + A..Z |
| private final char myMnemonic; |
| |
| @NotNull |
| static MnemonicIcon getIcon(char mnemonic) { |
| int index = mnemonic - 48; |
| if (index > 9) |
| index -= 7; |
| if (index < 0 || index > cache.length-1) |
| return new MnemonicIcon(mnemonic); |
| if (cache[index] == null) |
| cache[index] = new MnemonicIcon(mnemonic); |
| return cache[index]; |
| } |
| |
| private MnemonicIcon(char mnemonic) { |
| myMnemonic = mnemonic; |
| } |
| |
| @Override |
| public void paintIcon(Component c, Graphics g, int x, int y) { |
| g.setColor(ICON_BACKGROUND_COLOR); |
| g.fillRect(x, y, getIconWidth(), getIconHeight()); |
| |
| g.setColor(JBColor.GRAY); |
| g.drawRect(x, y, getIconWidth(), getIconHeight()); |
| |
| g.setColor(JBColor.foreground()); |
| final Font oldFont = g.getFont(); |
| g.setFont(MNEMONIC_FONT); |
| |
| ((Graphics2D)g).drawString(Character.toString(myMnemonic), x + 3, y + getIconHeight() - 1.5F); |
| g.setFont(oldFont); |
| } |
| |
| @Override |
| public int getIconWidth() { |
| return DEFAULT_ICON.getIconWidth(); |
| } |
| |
| @Override |
| public int getIconHeight() { |
| return DEFAULT_ICON.getIconHeight(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| MnemonicIcon that = (MnemonicIcon)o; |
| |
| return myMnemonic == that.myMnemonic; |
| } |
| |
| @Override |
| public int hashCode() { |
| return (int)myMnemonic; |
| } |
| } |
| |
| private static class MyDefaultIcon implements Icon { |
| private static final Icon myIcon = PlatformIcons.CHECK_ICON; |
| |
| @Override |
| public void paintIcon(Component c, Graphics g, int x, int y) { |
| Graphics2D g2 = (Graphics2D)g.create(); |
| try { |
| Color gutterBackground = EditorColors.GUTTER_BACKGROUND.getDefaultColor(); |
| g2.setColor(gutterBackground); |
| g2.fillRoundRect(x, y, getIconWidth(), getIconHeight(), 4, 4); |
| myIcon.paintIcon(c, g2, x, y); |
| } finally { |
| g2.dispose(); |
| } |
| } |
| |
| @Override |
| public int getIconWidth() { |
| return myIcon.getIconWidth(); |
| } |
| |
| @Override |
| public int getIconHeight() { |
| return myIcon.getIconHeight(); |
| } |
| } |
| |
| private static class MyGutterIconRenderer extends GutterIconRenderer { |
| private final Bookmark myBookmark; |
| |
| public MyGutterIconRenderer(@NotNull Bookmark bookmark) { |
| myBookmark = bookmark; |
| } |
| |
| @Override |
| @NotNull |
| public Icon getIcon() { |
| return myBookmark.getIcon(); |
| } |
| |
| @Override |
| public String getTooltipText() { |
| return myBookmark.getBookmarkTooltip(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj instanceof MyGutterIconRenderer && |
| Comparing.equal(getTooltipText(), ((MyGutterIconRenderer)obj).getTooltipText()) && |
| Comparing.equal(getIcon(), ((MyGutterIconRenderer)obj).getIcon()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return getIcon().hashCode(); |
| } |
| } |
| } |