ignore inferred @NotNull on overrideable methods (IDEA-132831, IDEA-131608)
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / bookmarks / Bookmark.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.ide.bookmarks;
18
19 import com.intellij.codeInsight.daemon.GutterMark;
20 import com.intellij.icons.AllIcons;
21 import com.intellij.ide.IdeBundle;
22 import com.intellij.ide.structureView.StructureViewBuilder;
23 import com.intellij.ide.structureView.StructureViewModel;
24 import com.intellij.ide.structureView.TreeBasedStructureViewBuilder;
25 import com.intellij.lang.LanguageStructureViewBuilder;
26 import com.intellij.navigation.ItemPresentation;
27 import com.intellij.navigation.NavigationItem;
28 import com.intellij.openapi.editor.Document;
29 import com.intellij.openapi.editor.RangeMarker;
30 import com.intellij.openapi.editor.colors.CodeInsightColors;
31 import com.intellij.openapi.editor.colors.EditorColorsManager;
32 import com.intellij.openapi.editor.ex.MarkupModelEx;
33 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
34 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
35 import com.intellij.openapi.editor.markup.GutterIconRenderer;
36 import com.intellij.openapi.editor.markup.HighlighterLayer;
37 import com.intellij.openapi.editor.markup.RangeHighlighter;
38 import com.intellij.openapi.editor.markup.TextAttributes;
39 import com.intellij.openapi.fileEditor.FileDocumentManager;
40 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.util.Comparing;
43 import com.intellij.openapi.util.Ref;
44 import com.intellij.openapi.util.text.StringUtil;
45 import com.intellij.openapi.vfs.VirtualFile;
46 import com.intellij.pom.Navigatable;
47 import com.intellij.psi.PsiDocumentManager;
48 import com.intellij.psi.PsiFile;
49 import com.intellij.psi.PsiManager;
50 import com.intellij.ui.ColorUtil;
51 import com.intellij.ui.JBColor;
52 import com.intellij.util.NotNullProducer;
53 import com.intellij.util.PlatformIcons;
54 import com.intellij.util.Processor;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import java.awt.*;
60
61 public class Bookmark implements Navigatable {
62   public static final Icon DEFAULT_ICON = new MyCheckedIcon();
63
64   private final VirtualFile myFile;
65   @NotNull private final OpenFileDescriptor myTarget;
66   private final Project myProject;
67
68   private String myDescription;
69   private char myMnemonic = 0;
70   public static final Font MNEMONIC_FONT = new Font("Monospaced", 0, 11);
71
72   public Bookmark(@NotNull Project project, @NotNull VirtualFile file, int line, @NotNull String description) {
73     myFile = file;
74     myProject = project;
75     myDescription = description;
76
77     myTarget = new OpenFileDescriptor(project, file, line, -1, true);
78
79     addHighlighter();
80   }
81
82   public void updateHighlighter() {
83     release();
84     addHighlighter();
85   }
86
87   private void addHighlighter() {
88     Document document = FileDocumentManager.getInstance().getCachedDocument(getFile());
89     if (document != null) {
90       createHighlighter((MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, true));
91     }
92   }
93
94   public RangeHighlighter createHighlighter(@NotNull MarkupModelEx markup) {
95     final RangeHighlighterEx myHighlighter;
96     int line = getLine();
97     if (line >= 0) {
98       myHighlighter = markup.addPersistentLineHighlighter(line, HighlighterLayer.ERROR + 1, null);
99       if (myHighlighter != null) {
100         myHighlighter.setGutterIconRenderer(new MyGutterIconRenderer(this));
101
102         TextAttributes textAttributes =
103           EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.BOOKMARKS_ATTRIBUTES);
104
105         Color stripeColor = textAttributes.getErrorStripeColor();
106         myHighlighter.setErrorStripeMarkColor(stripeColor != null ? stripeColor : Color.black);
107         myHighlighter.setErrorStripeTooltip(getBookmarkTooltip());
108
109         TextAttributes attributes = myHighlighter.getTextAttributes();
110         if (attributes == null) {
111           attributes = new TextAttributes();
112         }
113         attributes.setBackgroundColor(textAttributes.getBackgroundColor());
114         attributes.setForegroundColor(textAttributes.getForegroundColor());
115         myHighlighter.setTextAttributes(attributes);
116       }
117     }
118     else {
119       myHighlighter = null;
120     }
121     return myHighlighter;
122   }
123
124   @Nullable
125   public Document getDocument() {
126     return FileDocumentManager.getInstance().getCachedDocument(getFile());
127   }
128
129   public void release() {
130     int line = getLine();
131     if (line < 0) {
132       return;
133     }
134     final Document document = getDocument();
135     if (document == null) return;
136     MarkupModelEx markup = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, true);
137     final Document markupDocument = markup.getDocument();
138     if (markupDocument.getLineCount() <= line) return;
139     final int startOffset = markupDocument.getLineStartOffset(line);
140     final int endOffset = markupDocument.getLineEndOffset(line);
141
142     final Ref<RangeHighlighterEx> found = new Ref<RangeHighlighterEx>();
143     markup.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
144       @Override
145       public boolean process(RangeHighlighterEx highlighter) {
146         GutterMark renderer = highlighter.getGutterIconRenderer();
147         if (renderer instanceof MyGutterIconRenderer && ((MyGutterIconRenderer)renderer).myBookmark == Bookmark.this) {
148           found.set(highlighter);
149           return false;
150         }
151         return true;
152       }
153     });
154     if (!found.isNull()) found.get().dispose();
155   }
156
157   public Icon getIcon() {
158     return myMnemonic == 0 ? DEFAULT_ICON : MnemonicIcon.getIcon(myMnemonic);
159   }
160
161   public String getDescription() {
162     return myDescription;
163   }
164
165   public void setDescription(String description) {
166     myDescription = description;
167   }
168
169   public char getMnemonic() {
170     return myMnemonic;
171   }
172
173   public void setMnemonic(char mnemonic) {
174     myMnemonic = Character.toUpperCase(mnemonic);
175   }
176
177   @NotNull
178   public VirtualFile getFile() {
179     return myFile;
180   }
181
182   @Nullable
183   public String getNotEmptyDescription() {
184     return StringUtil.isEmpty(myDescription) ? null : myDescription;
185   }
186
187   public boolean isValid() {
188     if (!getFile().isValid()) {
189       return false;
190     }
191
192     // There is a possible case that target document line that is referenced by the current bookmark is removed. We assume
193     // that corresponding range marker becomes invalid then.
194     RangeMarker rangeMarker = myTarget.getRangeMarker();
195     return rangeMarker == null || rangeMarker.isValid();
196   }
197
198   @Override
199   public boolean canNavigate() {
200     return myTarget.canNavigate();
201   }
202
203   @Override
204   public boolean canNavigateToSource() {
205     return myTarget.canNavigateToSource();
206   }
207
208   @Override
209   public void navigate(boolean requestFocus) {
210     myTarget.navigate(requestFocus);
211   }
212
213   public int getLine() {
214     RangeMarker marker = myTarget.getRangeMarker();
215     if (marker != null && marker.isValid()) {
216       Document document = marker.getDocument();
217       return document.getLineNumber(marker.getStartOffset());
218     }
219     return myTarget.getLine();
220   }
221
222   @Override
223   public String toString() {
224     StringBuilder result = new StringBuilder(getQualifiedName());
225     String description = StringUtil.escapeXml(getNotEmptyDescription());
226     if (description != null) {
227       result.append(": ").append(description);
228     }
229     return result.toString();
230   }
231
232   public String getQualifiedName() {
233     String presentableUrl = myFile.getPresentableUrl();
234     if (myFile.isDirectory()) return presentableUrl;
235
236     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
237     final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(myFile);
238
239     if (psiFile == null) return presentableUrl;
240
241     StructureViewBuilder builder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(psiFile);
242     if (builder instanceof TreeBasedStructureViewBuilder) {
243       StructureViewModel model = ((TreeBasedStructureViewBuilder)builder).createStructureViewModel(null);
244       Object element;
245       try {
246         element = model.getCurrentEditorElement();
247       }
248       finally {
249         model.dispose();
250       }
251       if (element instanceof NavigationItem) {
252         ItemPresentation presentation = ((NavigationItem)element).getPresentation();
253         if (presentation != null) {
254           presentableUrl = ((NavigationItem)element).getName() + " " + presentation.getLocationString();
255         }
256       }
257     }
258
259     return IdeBundle.message("bookmark.file.X.line.Y", presentableUrl, getLine() + 1);
260   }
261
262   private String getBookmarkTooltip() {
263     StringBuilder result = new StringBuilder("Bookmark");
264     if (myMnemonic != 0) {
265       result.append(" ").append(myMnemonic);
266     }
267     String description = StringUtil.escapeXml(getNotEmptyDescription());
268     if (description != null) {
269       result.append(": ").append(description);
270     }
271     return result.toString();
272   }
273
274   static class MnemonicIcon implements Icon {
275     private static final MnemonicIcon[] cache = new MnemonicIcon[36];//0..9  + A..Z
276     private final char myMnemonic;
277
278     @NotNull
279     static MnemonicIcon getIcon(char mnemonic) {
280       int index = mnemonic - 48;
281       if (index > 9)
282         index -= 7;
283       if (index < 0 || index > cache.length-1)
284         return new MnemonicIcon(mnemonic);
285       if (cache[index] == null)
286         cache[index] = new MnemonicIcon(mnemonic);
287       return cache[index];
288     }
289
290     private MnemonicIcon(char mnemonic) {
291       myMnemonic = mnemonic;
292     }
293
294     @Override
295     public void paintIcon(Component c, Graphics g, int x, int y) {
296       g.setColor(new JBColor(new NotNullProducer<Color>() {
297         @NotNull
298         @Override
299         public Color produce() {
300           //noinspection UseJBColor
301           return !darkBackground() ? new Color(0xffffcc) : new Color(0x675133);
302         }
303       }));
304       g.fillRect(x, y, getIconWidth(), getIconHeight());
305
306       g.setColor(JBColor.GRAY);
307       g.drawRect(x, y, getIconWidth(), getIconHeight());
308
309       g.setColor(EditorColorsManager.getInstance().getGlobalScheme().getDefaultForeground());
310       final Font oldFont = g.getFont();
311       g.setFont(MNEMONIC_FONT);
312
313       ((Graphics2D)g).drawString(Character.toString(myMnemonic), x + 3, y + getIconHeight() - 1.5F);
314       g.setFont(oldFont);
315     }
316
317     @Override
318     public int getIconWidth() {
319       return DEFAULT_ICON.getIconWidth();
320     }
321
322     @Override
323     public int getIconHeight() {
324       return DEFAULT_ICON.getIconHeight();
325     }
326
327     @Override
328     public boolean equals(Object o) {
329       if (this == o) return true;
330       if (o == null || getClass() != o.getClass()) return false;
331
332       MnemonicIcon that = (MnemonicIcon)o;
333
334       return myMnemonic == that.myMnemonic;
335     }
336
337     @Override
338     public int hashCode() {
339       return (int)myMnemonic;
340     }
341   }
342
343   private static class MyCheckedIcon implements Icon {
344     @Override
345     public void paintIcon(Component c, Graphics g, int x, int y) {
346       (darkBackground() ? AllIcons.Actions.CheckedGrey : AllIcons.Actions.CheckedBlack).paintIcon(c, g, x, y);
347     }
348
349     @Override
350     public int getIconWidth() {
351       return PlatformIcons.CHECK_ICON.getIconWidth();
352     }
353
354     @Override
355     public int getIconHeight() {
356       return PlatformIcons.CHECK_ICON.getIconHeight();
357     }
358   }
359
360   private static boolean darkBackground() {
361     return ColorUtil.isDark(EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground()); // or gutter background?
362   }
363
364   private static class MyGutterIconRenderer extends GutterIconRenderer {
365     private final Bookmark myBookmark;
366
367     public MyGutterIconRenderer(@NotNull Bookmark bookmark) {
368       myBookmark = bookmark;
369     }
370
371     @Override
372     @NotNull
373     public Icon getIcon() {
374       return myBookmark.getIcon();
375     }
376
377     @Override
378     public String getTooltipText() {
379       return myBookmark.getBookmarkTooltip();
380     }
381
382     @Override
383     public boolean equals(Object obj) {
384       return obj instanceof MyGutterIconRenderer &&
385              Comparing.equal(getTooltipText(), ((MyGutterIconRenderer)obj).getTooltipText()) &&
386              Comparing.equal(getIcon(), ((MyGutterIconRenderer)obj).getIcon());
387     }
388
389     @Override
390      public int hashCode() {
391       return getIcon().hashCode();
392     }
393   }
394 }