135e53015408210365d7f418a0acd1393f320d55
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / EditorGutterComponentImpl.java
1 /*
2  * Copyright 2000-2015 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 /*
18  * Created by IntelliJ IDEA.
19  * User: max
20  * Date: Jun 6, 2002
21  * Time: 8:37:03 PM
22  * To change template for new class use
23  * Code Style | Class Templates options (Tools | IDE Options).
24  */
25 package com.intellij.openapi.editor.impl;
26
27 import com.intellij.codeInsight.daemon.GutterMark;
28 import com.intellij.codeInsight.hint.TooltipController;
29 import com.intellij.codeInsight.hint.TooltipGroup;
30 import com.intellij.ide.IdeEventQueue;
31 import com.intellij.ide.dnd.*;
32 import com.intellij.ide.ui.customization.CustomActionsSchema;
33 import com.intellij.openapi.actionSystem.*;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.application.impl.ApplicationImpl;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.editor.*;
38 import com.intellij.openapi.editor.colors.ColorKey;
39 import com.intellij.openapi.editor.colors.EditorColors;
40 import com.intellij.openapi.editor.colors.EditorFontType;
41 import com.intellij.openapi.editor.event.EditorMouseEventArea;
42 import com.intellij.openapi.editor.ex.*;
43 import com.intellij.openapi.editor.ex.util.EditorUIUtil;
44 import com.intellij.openapi.editor.ex.util.EditorUtil;
45 import com.intellij.openapi.editor.markup.*;
46 import com.intellij.openapi.project.DumbAwareAction;
47 import com.intellij.openapi.project.DumbService;
48 import com.intellij.openapi.project.Project;
49 import com.intellij.openapi.ui.popup.Balloon;
50 import com.intellij.openapi.util.Comparing;
51 import com.intellij.openapi.util.Ref;
52 import com.intellij.openapi.util.ScalableIcon;
53 import com.intellij.openapi.util.SystemInfo;
54 import com.intellij.openapi.util.registry.Registry;
55 import com.intellij.openapi.wm.impl.IdeBackgroundUtil;
56 import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
57 import com.intellij.ui.HintHint;
58 import com.intellij.ui.JBColor;
59 import com.intellij.ui.awt.RelativePoint;
60 import com.intellij.util.Function;
61 import com.intellij.util.IconUtil;
62 import com.intellij.util.NullableFunction;
63 import com.intellij.util.SmartList;
64 import com.intellij.util.containers.HashMap;
65 import com.intellij.util.ui.JBUI;
66 import com.intellij.util.ui.UIUtil;
67 import gnu.trove.TIntArrayList;
68 import gnu.trove.TIntFunction;
69 import gnu.trove.TIntObjectHashMap;
70 import gnu.trove.TObjectProcedure;
71 import org.jetbrains.annotations.Contract;
72 import org.jetbrains.annotations.NonNls;
73 import org.jetbrains.annotations.NotNull;
74 import org.jetbrains.annotations.Nullable;
75
76 import javax.swing.*;
77 import javax.swing.plaf.ComponentUI;
78 import java.awt.*;
79 import java.awt.event.*;
80 import java.awt.geom.AffineTransform;
81 import java.util.*;
82 import java.util.List;
83
84 /**
85  * Gutter content (left to right):
86  * <ul>
87  *   <li>GAP_BETWEEN_AREAS</li>
88  *   <li>Line numbers area
89  *     <ul>
90  *       <li>Line numbers</li>
91  *       <li>GAP_BETWEEN_AREAS</li>
92  *       <li>Additional line numbers (used in diff)</li>
93  *     </ul>
94  *   </li>
95  *   <li>GAP_BETWEEN_AREAS</li>
96  *   <li>Annotations area
97  *     <ul>
98  *       <li>Annotations</li>
99  *       <li>Annotations extra (used in distraction free mode)</li>
100  *     </ul>
101  *   </li>
102  *   <li>GAP_BETWEEN_AREAS</li>
103  *   <li>Line markers area
104  *     <ul>
105  *       <li>Left free painters</li>
106  *       <li>Icons</li>
107  *       <li>GAP_BETWEEN_AREAS</li>
108  *       <li>Free painters</li>
109  *     </ul>
110  *   </li>
111  *   <li>Folding area</li>
112  *</ul>
113  */
114 class EditorGutterComponentImpl extends EditorGutterComponentEx implements MouseListener, MouseMotionListener, DataProvider {
115   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorGutterComponentImpl");
116   private static final int START_ICON_AREA_WIDTH = 15;
117   private static final int FREE_PAINTERS_LEFT_AREA_WIDTH = 8;
118   private static final int FREE_PAINTERS_RIGHT_AREA_WIDTH = 5;
119   private static final int GAP_BETWEEN_ICONS = 3;
120   private static final int GAP_BETWEEN_AREAS = 5;
121   private static final TooltipGroup GUTTER_TOOLTIP_GROUP = new TooltipGroup("GUTTER_TOOLTIP_GROUP", 0);
122   public static final TIntFunction ID = new TIntFunction() {
123     @Override
124     public int execute(int value) {
125       return value;
126     }
127   };
128
129   private final EditorImpl myEditor;
130   private final FoldingAnchorsOverlayStrategy myAnchorsDisplayStrategy;
131   private int myIconsAreaWidth = START_ICON_AREA_WIDTH;
132   private int myLineNumberAreaWidth = 0;
133   private int myAdditionalLineNumberAreaWidth = 0;
134   private FoldRegion myActiveFoldRegion;
135   private int myTextAnnotationGuttersSize = 0;
136   private int myTextAnnotationExtraSize = 0;
137   private TIntArrayList myTextAnnotationGutterSizes = new TIntArrayList();
138   private ArrayList<TextAnnotationGutterProvider> myTextAnnotationGutters = new ArrayList<TextAnnotationGutterProvider>();
139   private final Map<TextAnnotationGutterProvider, EditorGutterAction> myProviderToListener = new HashMap<TextAnnotationGutterProvider, EditorGutterAction>();
140   private static final int GAP_BETWEEN_ANNOTATIONS = 5;
141   private String myLastGutterToolTip = null;
142   @NotNull private TIntFunction myLineNumberConvertor;
143   @Nullable private TIntFunction myAdditionalLineNumberConvertor;
144   private TIntFunction myLineNumberAreaWidthFunction;
145   private boolean myShowDefaultGutterPopup = true;
146   @Nullable private ActionGroup myCustomGutterPopupGroup;
147   private TIntObjectHashMap<Color> myTextFgColors = new TIntObjectHashMap<Color>();
148   private boolean myPaintBackground = true;
149   private boolean myLeftFreePaintersAreaShown;
150
151   @SuppressWarnings("unchecked")
152   public EditorGutterComponentImpl(EditorImpl editor) {
153     myEditor = editor;
154     myLineNumberConvertor = ID;
155     if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
156       installDnD();
157     }
158     setOpaque(true);
159     myAnchorsDisplayStrategy = new FoldingAnchorsOverlayStrategy(editor);
160
161     Project project = myEditor.getProject();
162     if (project != null) {
163       project.getMessageBus().connect(myEditor.getDisposable()).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
164         @Override
165         public void enteredDumbMode() {
166         }
167
168         @Override
169         public void exitDumbMode() {
170           updateSize();
171         }
172       });
173     }
174   }
175
176   @SuppressWarnings({"ConstantConditions"})
177   private void installDnD() {
178     DnDSupport.createBuilder(this)
179       .setBeanProvider(new Function<DnDActionInfo, DnDDragStartBean>() {
180         @Override
181         public DnDDragStartBean fun(DnDActionInfo info) {
182           final GutterMark renderer = getGutterRenderer(info.getPoint());
183           return renderer != null && (info.isCopy() || info.isMove()) ? new DnDDragStartBean(renderer) : null;
184         }
185       })
186       .setDropHandler(new DnDDropHandler() {
187         @Override
188         public void drop(DnDEvent e) {
189           final Object attachedObject = e.getAttachedObject();
190           if (attachedObject instanceof GutterIconRenderer && checkDumbAware(attachedObject, myEditor.getProject())) {
191             final GutterDraggableObject draggableObject = ((GutterIconRenderer)attachedObject).getDraggableObject();
192             if (draggableObject != null) {
193               final int line = convertPointToLineNumber(e.getPoint());
194               if (line != -1) {
195                 draggableObject.copy(line, myEditor.getVirtualFile());
196               }
197             }
198           }
199         }
200       })
201       .setImageProvider(new NullableFunction<DnDActionInfo, DnDImage>() {
202         @Override
203         public DnDImage fun(DnDActionInfo info) {
204           return new DnDImage(IconUtil.toImage(scaleIcon(getGutterRenderer(info.getPoint()).getIcon())));
205         }
206       })
207       .install();
208   }
209
210   private void fireResized() {
211     processComponentEvent(new ComponentEvent(this, ComponentEvent.COMPONENT_RESIZED));
212   }
213
214   @Override
215   public Dimension getPreferredSize() {
216     int w = getFoldingAreaOffset() + getFoldingAreaWidth();
217     if (w <= GAP_BETWEEN_AREAS) w = 0;
218     return new Dimension(w, myEditor.getPreferredHeight());
219   }
220
221   @Override
222   protected void setUI(ComponentUI newUI) {
223     super.setUI(newUI);
224     reinitSettings();
225   }
226
227   @Override
228   public void updateUI() {
229     super.updateUI();
230     reinitSettings();
231   }
232
233   public void reinitSettings() {
234     revalidateMarkup();
235     repaint();
236   }
237
238   @Override
239   public void paint(Graphics g_) {
240     ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart();
241     try {
242       Rectangle clip = g_.getClipBounds();
243       if (clip.height < 0) return;
244
245       Graphics2D g = IdeBackgroundUtil.withEditorBackground(g_, this);
246       AffineTransform old = g.getTransform();
247
248       if (isMirrored()) {
249         final AffineTransform transform = new AffineTransform(old);
250         transform.scale(-1, 1);
251         transform.translate(-getWidth(), 0);
252         g.setTransform(transform);
253       }
254
255       EditorUIUtil.setupAntialiasing(g);
256       Color backgroundColor = getBackground();
257
258       // paint all backgrounds
259       int gutterSeparatorX = getWhitespaceSeparatorOffset();
260       paintBackground(g, clip, 0, gutterSeparatorX, backgroundColor);
261       paintBackground(g, clip, gutterSeparatorX, getFoldingAreaWidth(), myEditor.getBackgroundColor());
262
263       int firstVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y - myEditor.getLineHeight())));
264       int lastVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y + clip.height + myEditor.getLineHeight())));
265       paintEditorBackgrounds(g, firstVisibleOffset, lastVisibleOffset);
266
267       Object hint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
268       if (!UIUtil.isRetina()) g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
269
270       try {
271         paintAnnotations(g, clip);
272         paintLineMarkers(g, firstVisibleOffset, lastVisibleOffset);
273         paintFoldingLines(g, clip);
274         paintFoldingTree(g, clip, firstVisibleOffset, lastVisibleOffset);
275         paintLineNumbers(g, clip);
276       }
277       finally {
278         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint);
279       }
280
281       g.setTransform(old);
282     }
283     finally {
284       ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintFinish();
285     }
286   }
287
288   private void paintEditorBackgrounds(Graphics g, int firstVisibleOffset, int lastVisibleOffset) {
289     myTextFgColors.clear();
290     Color defaultBackgroundColor = myEditor.getBackgroundColor();
291     Color defaultForegroundColor = myEditor.getColorsScheme().getDefaultForeground();
292     int startX = myEditor.isInDistractionFreeMode() ? 0 : getWhitespaceSeparatorOffset() + (isFoldingOutlineShown() ? 1 : 0);
293     if (myEditor.myUseNewRendering) {
294       com.intellij.openapi.editor.impl.view.IterationState state =
295         new com.intellij.openapi.editor.impl.view.IterationState(myEditor, firstVisibleOffset, lastVisibleOffset, false, true, true, false);
296       while (!state.atEnd()) {
297         drawEditorBackgroundForRange(g, state.getStartOffset(), state.getEndOffset(), state.getMergedAttributes(),
298                                      defaultBackgroundColor, defaultForegroundColor, startX);
299         state.advance();
300       }
301     }
302     else {
303       IterationState state = new IterationState(myEditor, firstVisibleOffset, lastVisibleOffset, false, true);
304       while (!state.atEnd()) {
305         drawEditorBackgroundForRange(g, state.getStartOffset(), state.getEndOffset(), state.getMergedAttributes(),
306                                      defaultBackgroundColor, defaultForegroundColor, startX);
307         state.advance();
308       }
309     }
310   }
311
312   private void drawEditorBackgroundForRange(Graphics g, int startOffset, int endOffset, TextAttributes attributes,
313                                             Color defaultBackgroundColor, Color defaultForegroundColor, int startX) {
314     VisualPosition visualStart = myEditor.offsetToVisualPosition(startOffset, true, false);
315     VisualPosition visualEnd   = myEditor.offsetToVisualPosition(endOffset, false, false);
316     for (int line = visualStart.getLine(); line <= visualEnd.getLine(); line++) {
317       if (line == visualStart.getLine()) {
318         if (visualStart.getColumn() == 0) {
319           drawEditorLineBackgroundRect(g, attributes, line, defaultBackgroundColor, defaultForegroundColor, startX,
320                                        myEditor.visibleLineToY(line));
321         }
322       }
323       else if (line != visualEnd.getLine() || visualEnd.getColumn() != 0) {
324         drawEditorLineBackgroundRect(g, attributes, line, defaultBackgroundColor, defaultForegroundColor, startX,
325                                      myEditor.visibleLineToY(line));
326       }
327     }
328   }
329
330   private void drawEditorLineBackgroundRect(Graphics g,
331                                             TextAttributes attributes,
332                                             int visualLine,
333                                             Color defaultBackgroundColor,
334                                             Color defaultForegroundColor,
335                                             int startX,
336                                             int startY) {
337     Color color = myEditor.getBackgroundColor(attributes);
338     if (!Comparing.equal(color, defaultBackgroundColor)) {
339       Color fgColor = attributes.getForegroundColor();
340       if (!Comparing.equal(fgColor, defaultForegroundColor)) {
341         myTextFgColors.put(visualLine, fgColor);
342       }
343       g.setColor(color);
344       g.fillRect(startX, startY, getWidth() - startX, myEditor.getLineHeight());
345     }
346   }
347
348   private void processClose(final MouseEvent e) {
349     final IdeEventQueue queue = IdeEventQueue.getInstance();
350
351     // See IDEA-59553 for rationale on why this feature is disabled
352     //if (isLineNumbersShown()) {
353     //  if (e.getX() >= getLineNumberAreaOffset() && getLineNumberAreaOffset() + getLineNumberAreaWidth() >= e.getX()) {
354     //    queue.blockNextEvents(e);
355     //    myEditor.getSettings().setLineNumbersShown(false);
356     //    e.consume();
357     //    return;
358     //  }
359     //}
360
361     if (getGutterRenderer(e) != null) return;
362
363     int x = getAnnotationsAreaOffset();
364     for (int i = 0; i < myTextAnnotationGutters.size(); i++) {
365       final int size = myTextAnnotationGutterSizes.get(i);
366       if (x <= e.getX() && e.getX() <= x + size + GAP_BETWEEN_ANNOTATIONS) {
367         queue.blockNextEvents(e);
368         closeAllAnnotations();
369         e.consume();
370         break;
371       }
372
373       x += size + GAP_BETWEEN_ANNOTATIONS;
374     }
375   }
376
377   private void paintAnnotations(Graphics2D g, Rectangle clip) {
378     int x = getAnnotationsAreaOffset();
379     int w = getAnnotationsAreaWidthEx();
380
381     if (w == 0) return;
382
383     AffineTransform old = g.getTransform();
384     g.setTransform(getMirrorTransform(old, x, w));
385     try {
386       Color color = myEditor.getColorsScheme().getColor(EditorColors.ANNOTATIONS_COLOR);
387       g.setColor(color != null ? color : JBColor.blue);
388       g.setFont(myEditor.getColorsScheme().getFont(EditorFontType.PLAIN));
389
390       for (int i = 0; i < myTextAnnotationGutters.size(); i++) {
391         TextAnnotationGutterProvider gutterProvider = myTextAnnotationGutters.get(i);
392
393         int lineHeight = myEditor.getLineHeight();
394         int startLineNumber = clip.y / lineHeight;
395         int endLineNumber = (clip.y + clip.height) / lineHeight + 1;
396         int lastLine = myEditor.logicalToVisualPosition(
397           new LogicalPosition(endLineNumber(), 0))
398           .line;
399         endLineNumber = Math.min(endLineNumber, lastLine + 1);
400         if (startLineNumber >= endLineNumber) {
401           break;
402         }
403
404         int annotationSize = myTextAnnotationGutterSizes.get(i);
405         for (int j = startLineNumber; j < endLineNumber; j++) {
406           int logLine = myEditor.visualToLogicalPosition(new VisualPosition(j, 0)).line;
407           String s = gutterProvider.getLineText(logLine, myEditor);
408           final EditorFontType style = gutterProvider.getStyle(logLine, myEditor);
409           final Color bg = gutterProvider.getBgColor(logLine, myEditor);
410           if (bg != null) {
411             g.setColor(bg);
412             g.fillRect(x, j * lineHeight, annotationSize, lineHeight);
413           }
414           g.setColor(myEditor.getColorsScheme().getColor(gutterProvider.getColor(logLine, myEditor)));
415           g.setFont(myEditor.getColorsScheme().getFont(style));
416           if (s != null) {
417             g.drawString(s, x, (j + 1) * lineHeight - myEditor.getDescent());
418           }
419         }
420
421         x += annotationSize;
422       }
423
424     }
425     finally {
426       g.setTransform(old);
427     }
428   }
429
430   private void paintFoldingTree(Graphics g, Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
431     if (isFoldingOutlineShown()) {
432       doPaintFoldingTree((Graphics2D)g, clip, firstVisibleOffset, lastVisibleOffset);
433     }
434   }
435
436   private void paintLineMarkers(Graphics2D g, int firstVisibleOffset, int lastVisibleOffset) {
437     if (isLineMarkersShown()) {
438       paintGutterRenderers(g, firstVisibleOffset, lastVisibleOffset);
439     }
440   }
441
442   private void paintBackground(final Graphics g,
443                                final Rectangle clip,
444                                final int x,
445                                final int width,
446                                Color background) {
447     g.setColor(background);
448     g.fillRect(x, clip.y, width, clip.height);
449
450     paintCaretRowBackground(g, x, width);
451   }
452
453   private void paintCaretRowBackground(final Graphics g, final int x, final int width) {
454     if (!myEditor.getSettings().isCaretRowShown()) return;
455     final VisualPosition visCaret = myEditor.getCaretModel().getVisualPosition();
456     Color caretRowColor = myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
457     if (caretRowColor != null) {
458       g.setColor(caretRowColor);
459       final Point caretPoint = myEditor.visualPositionToXY(visCaret);
460       g.fillRect(x, caretPoint.y, width, myEditor.getLineHeight());
461     }
462   }
463
464   private void paintLineNumbers(Graphics2D g, Rectangle clip) {
465     if (isLineNumbersShown()) {
466       int offset = getLineNumberAreaOffset() + myLineNumberAreaWidth;
467       doPaintLineNumbers(g, clip, offset, myLineNumberConvertor);
468       if (myAdditionalLineNumberConvertor != null) {
469         doPaintLineNumbers(g, clip, offset + getAreaWidthWithGap(myAdditionalLineNumberAreaWidth), myAdditionalLineNumberConvertor);
470       }
471     }
472   }
473
474   @Override
475   public Color getBackground() {
476     if (myEditor.isInDistractionFreeMode() || !myPaintBackground) {
477       return myEditor.getBackgroundColor();
478     }
479     Color color = myEditor.getColorsScheme().getColor(EditorColors.GUTTER_BACKGROUND);
480     return color != null ? color : EditorColors.GUTTER_BACKGROUND.getDefaultColor();
481   }
482
483   private void doPaintLineNumbers(Graphics2D g, Rectangle clip, int offset, @NotNull TIntFunction convertor) {
484     if (!isLineNumbersShown()) {
485       return;
486     }
487     int lineHeight = myEditor.getLineHeight();
488     int startLineNumber = clip.y / lineHeight;
489     int endLineNumber = (clip.y + clip.height) / lineHeight + 1;
490     int lastLine = myEditor.logicalToVisualPosition(
491       new LogicalPosition(endLineNumber(), 0))
492       .line;
493     endLineNumber = Math.min(endLineNumber, lastLine + 1);
494     if (startLineNumber >= endLineNumber) {
495       return;
496     }
497
498     Color color = myEditor.getColorsScheme().getColor(EditorColors.LINE_NUMBERS_COLOR);
499     g.setColor(color != null ? color : JBColor.blue);
500     g.setFont(myEditor.getColorsScheme().getFont(EditorFontType.PLAIN));
501
502     AffineTransform old = g.getTransform();
503     g.setTransform(getMirrorTransform(old, getLineNumberAreaOffset(), getLineNumberAreaWidth()));
504     try {
505       for (int i = startLineNumber; i < endLineNumber; i++) {
506         LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(new VisualPosition(i, 0));
507         if (EditorUtil.getSoftWrapCountAfterLineStart(myEditor, logicalPosition) > 0) {
508           continue;
509         }
510         int logLine = convertor.execute(logicalPosition.line);
511         if (logLine >= 0) {
512           String s = String.valueOf(logLine + 1);
513           int startY = (i + 1) * lineHeight;
514           if (myEditor.isInDistractionFreeMode()) {
515             Color fgColor = myTextFgColors.get(i);
516             g.setColor(fgColor != null ? fgColor : color != null ? color : JBColor.blue);
517           }
518
519           int textOffset = isMirrored() ?
520                            offset - getLineNumberAreaWidth() - 1:
521                            offset - myEditor.getFontMetrics(Font.PLAIN).stringWidth(s);
522
523           g.drawString(s,
524                        textOffset,
525                        startY - myEditor.getDescent());
526         }
527       }
528     }
529     finally {
530       g.setTransform(old);
531     }
532   }
533
534   private int endLineNumber() {
535     return Math.max(0, myEditor.getDocument().getLineCount() - 1);
536   }
537
538   @Nullable
539   @Override
540   public Object getData(@NonNls String dataId) {
541     if (EditorGutter.KEY.is(dataId)) {
542       return this;
543     }
544     else if (CommonDataKeys.EDITOR.is(dataId)) {
545       return myEditor;
546     }
547     return null;
548   }
549
550   private interface RangeHighlighterProcessor {
551     void process(@NotNull RangeHighlighter highlighter);
552   }
553
554   private void processRangeHighlighters(int startOffset, int endOffset, @NotNull RangeHighlighterProcessor processor) {
555     Document document = myEditor.getDocument();
556     final MarkupModelEx docMarkup = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myEditor.getProject(), true);
557     // we limit highlighters to process to between line starting at startOffset and line ending at endOffset
558     DisposableIterator<RangeHighlighterEx> docHighlighters = docMarkup.overlappingIterator(startOffset, endOffset);
559     DisposableIterator<RangeHighlighterEx> editorHighlighters = myEditor.getMarkupModel().overlappingIterator(startOffset, endOffset);
560
561     try {
562       RangeHighlighterEx lastDocHighlighter = null;
563       RangeHighlighterEx lastEditorHighlighter = null;
564       while (true) {
565         if (lastDocHighlighter == null && docHighlighters.hasNext()) {
566           lastDocHighlighter = docHighlighters.next();
567           if (!lastDocHighlighter.isValid() || lastDocHighlighter.getAffectedAreaStartOffset() > endOffset) {
568             lastDocHighlighter = null;
569             continue;
570           }
571           if (lastDocHighlighter.getAffectedAreaEndOffset() < startOffset) {
572             lastDocHighlighter = null;
573             continue;
574           }
575         }
576
577         if (lastEditorHighlighter == null && editorHighlighters.hasNext()) {
578           lastEditorHighlighter = editorHighlighters.next();
579           if (!lastEditorHighlighter.isValid() || lastEditorHighlighter.getAffectedAreaStartOffset() > endOffset) {
580             lastEditorHighlighter = null;
581             continue;
582           }
583           if (lastEditorHighlighter.getAffectedAreaEndOffset() < startOffset) {
584             lastEditorHighlighter = null;
585             continue;
586           }
587         }
588
589         if (lastDocHighlighter == null && lastEditorHighlighter == null) return;
590
591         final RangeHighlighterEx lowerHighlighter;
592         if (less(lastDocHighlighter, lastEditorHighlighter)) {
593           lowerHighlighter = lastDocHighlighter;
594           lastDocHighlighter = null;
595         }
596         else {
597           lowerHighlighter = lastEditorHighlighter;
598           lastEditorHighlighter = null;
599         }
600
601         if (!lowerHighlighter.isValid()) continue;
602
603         int startLineIndex = lowerHighlighter.getDocument().getLineNumber(startOffset);
604         if (!isValidLine(document, startLineIndex)) continue;
605
606         int endLineIndex = lowerHighlighter.getDocument().getLineNumber(endOffset);
607         if (!isValidLine(document, endLineIndex)) continue;
608
609         if (lowerHighlighter.getEditorFilter().avaliableIn(myEditor)) {
610           processor.process(lowerHighlighter);
611         }
612       }
613     }
614     finally {
615       docHighlighters.dispose();
616       editorHighlighters.dispose();
617     }
618   }
619
620   private static boolean isValidLine(@NotNull Document document, int line) {
621     if (line < 0) return false;
622     int lineCount = document.getLineCount();
623     return lineCount == 0 ? line == 0 : line < lineCount;
624   }
625
626   private static boolean less(RangeHighlighter h1, RangeHighlighter h2) {
627     return h1 != null && (h2 == null || h1.getStartOffset() < h2.getStartOffset());
628   }
629
630   @Override
631   public void revalidateMarkup() {
632     updateSize();
633   }
634
635   public void updateSize() {
636     updateSize(false);
637   }
638
639   void updateSize(boolean onLayout) {
640     int prevHash = sizeHash();
641     updateSizeInner(onLayout);
642
643     if (prevHash != sizeHash()) {
644       fireResized();
645     }
646     repaint();
647   }
648
649   private void updateSizeInner(boolean onLayout) {
650     if (!onLayout) {
651       calcLineNumberAreaWidth();
652       calcLineMarkerAreaWidth();
653       calcAnnotationsSize();
654     }
655     calcAnnotationExtraSize();
656   }
657
658   private int sizeHash() {
659     int result = getLineMarkerAreaWidth();
660     result = 31 * result + myTextAnnotationGuttersSize;
661     result = 31 * result + myTextAnnotationExtraSize;
662     result = 31 * result + getLineNumberAreaWidth();
663     return result;
664   }
665
666   private void calcAnnotationsSize() {
667     myTextAnnotationGuttersSize = 0;
668     final FontMetrics fontMetrics = myEditor.getFontMetrics(Font.PLAIN);
669     final int lineCount = myEditor.getDocument().getLineCount();
670     for (int j = 0; j < myTextAnnotationGutters.size(); j++) {
671       TextAnnotationGutterProvider gutterProvider = myTextAnnotationGutters.get(j);
672       int gutterSize = 0;
673       for (int i = 0; i < lineCount; i++) {
674         final String lineText = gutterProvider.getLineText(i, myEditor);
675         if (lineText != null) {
676           gutterSize = Math.max(gutterSize, fontMetrics.stringWidth(lineText));
677         }
678       }
679       if (gutterSize > 0) gutterSize += GAP_BETWEEN_ANNOTATIONS;
680       myTextAnnotationGutterSizes.set(j, gutterSize);
681       myTextAnnotationGuttersSize += gutterSize;
682     }
683   }
684
685   private void calcAnnotationExtraSize() {
686     myTextAnnotationExtraSize = 0;
687     if (!myEditor.isInDistractionFreeMode() || isMirrored()) return;
688
689     Window frame = SwingUtilities.getWindowAncestor(myEditor.getComponent());
690     if (frame == null) return;
691
692     EditorSettings settings = myEditor.getSettings();
693     int rightMargin = settings.getRightMargin(myEditor.getProject());
694     if (rightMargin <= 0) return;
695
696     JComponent editorComponent = myEditor.getComponent();
697     RelativePoint point = new RelativePoint(editorComponent, new Point(0, 0));
698     Point editorLocationInWindow = point.getPoint(frame);
699
700     int editorLocationX = (int)editorLocationInWindow.getX();
701     int rightMarginX = rightMargin * EditorUtil.getSpaceWidth(Font.PLAIN, myEditor) + editorLocationX;
702
703     int width = editorLocationX + editorComponent.getWidth();
704     if (rightMarginX < width && editorLocationX < width - rightMarginX) {
705       int centeredSize = (width - rightMarginX - editorLocationX) / 2 - (getLineMarkerAreaWidth() + getLineNumberAreaWidth());
706       myTextAnnotationExtraSize = Math.max(0, centeredSize - myTextAnnotationGuttersSize);
707     }
708   }
709
710   private TIntObjectHashMap<List<GutterMark>> myLineToGutterRenderers;
711
712   private void calcLineMarkerAreaWidth() {
713     myLineToGutterRenderers = new TIntObjectHashMap<List<GutterMark>>();
714     myLeftFreePaintersAreaShown = false;
715
716     processRangeHighlighters(0, myEditor.getDocument().getTextLength(), new RangeHighlighterProcessor() {
717       @Override
718       public void process(@NotNull RangeHighlighter highlighter) {
719         LineMarkerRenderer lineMarkerRenderer = highlighter.getLineMarkerRenderer();
720         if (lineMarkerRenderer instanceof LineMarkerRendererEx && 
721             ((LineMarkerRendererEx)lineMarkerRenderer).getPosition() == LineMarkerRendererEx.Position.LEFT && 
722             isLineMarkerVisible(highlighter)) {
723           myLeftFreePaintersAreaShown = true;
724         }
725         
726         GutterMark renderer = highlighter.getGutterIconRenderer();
727         if (renderer == null) {
728           return;
729         }
730         if (!isHighlighterVisible(highlighter)) {
731           return;
732         }
733         int lineStartOffset = EditorUtil.getNotFoldedLineStartOffset(myEditor, highlighter.getStartOffset());
734         int line = myEditor.getDocument().getLineNumber(lineStartOffset);
735         List<GutterMark> renderers = myLineToGutterRenderers.get(line);
736         if (renderers == null) {
737           renderers = new SmartList<GutterMark>();
738           myLineToGutterRenderers.put(line, renderers);
739         }
740
741         if (renderers.size() < 5) { // Don't allow more than 5 icons per line
742           renderers.add(renderer);
743         }
744       }
745     });
746
747     myIconsAreaWidth = START_ICON_AREA_WIDTH;
748
749     myLineToGutterRenderers.forEachValue(new TObjectProcedure<List<GutterMark>>() {
750       @Override
751       public boolean execute(List<GutterMark> renderers) {
752         int width = 1;
753         for (int i = 0; i < renderers.size(); i++) {
754           GutterMark renderer = renderers.get(i);
755           if (!checkDumbAware(renderer, myEditor.getProject())) continue;
756           width += scaleIcon(renderer.getIcon()).getIconWidth();
757           if (i > 0) width += GAP_BETWEEN_ICONS;
758         }
759         if (myIconsAreaWidth < width) {
760           myIconsAreaWidth = width + 1;
761         }
762         return true;
763       }
764     });
765   }
766
767   private boolean isHighlighterVisible(RangeHighlighter highlighter) {
768     int startOffset = highlighter instanceof RangeHighlighterEx ?
769                       ((RangeHighlighterEx)highlighter).getAffectedAreaStartOffset() :
770                       highlighter.getStartOffset();
771     int endOffset = highlighter instanceof RangeHighlighterEx ?
772                     ((RangeHighlighterEx)highlighter).getAffectedAreaEndOffset() :
773                     highlighter.getEndOffset();
774     FoldRegion foldRegion = myEditor.getFoldingModel().getCollapsedRegionAtOffset(startOffset);
775     return foldRegion == null || foldRegion.getEndOffset() < endOffset;
776   }
777
778   private void paintGutterRenderers(final Graphics2D g, int firstVisibleOffset, int lastVisibleOffset) {
779     Object hint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
780     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
781     try {
782       processRangeHighlighters(firstVisibleOffset, lastVisibleOffset, new RangeHighlighterProcessor() {
783         @Override
784         public void process(@NotNull RangeHighlighter highlighter) {
785           paintLineMarkerRenderer(highlighter, g);
786         }
787       });
788     }
789     finally {
790       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint);
791     }
792
793     int firstVisibleLine = myEditor.getDocument().getLineNumber(firstVisibleOffset);
794     int lastVisibleLine = myEditor.getDocument().getLineNumber(lastVisibleOffset);
795     paintIcons(firstVisibleLine, lastVisibleLine, g);
796   }
797
798   private void paintIcons(final int firstVisibleLine, final int lastVisibleLine, final Graphics2D g) {
799     for (int line = firstVisibleLine; line <= lastVisibleLine; line++) {
800       List<GutterMark> renderers = myLineToGutterRenderers.get(line);
801       if (renderers != null) {
802         paintIconRow(line, renderers, g);
803       }
804     }
805   }
806
807   private void paintIconRow(int line, List<GutterMark> row, final Graphics2D g) {
808     processIconsRow(line, row, new LineGutterIconRendererProcessor() {
809       @Override
810       public void process(int x, int y, GutterMark renderer) {
811         Icon icon = scaleIcon(renderer.getIcon());
812
813         AffineTransform old = g.getTransform();
814         g.setTransform(getMirrorTransform(old, x, icon.getIconWidth()));
815         try {
816           icon.paintIcon(EditorGutterComponentImpl.this, g, x, y);
817         }
818         finally {
819           g.setTransform(old);
820         }
821       }
822     });
823   }
824
825   private void paintLineMarkerRenderer(RangeHighlighter highlighter, Graphics g) {
826     LineMarkerRenderer lineMarkerRenderer = highlighter.getLineMarkerRenderer();
827     if (lineMarkerRenderer != null) {
828       Rectangle rectangle = getLineRendererRectangle(highlighter);
829       if (rectangle != null) {
830         lineMarkerRenderer.paint(myEditor, g, rectangle);
831       }
832     }
833   }
834   
835   private boolean isLineMarkerVisible(RangeHighlighter highlighter) {
836     int startOffset = highlighter.getStartOffset();
837     int endOffset = highlighter.getEndOffset();
838
839     FoldRegion startFoldRegion = myEditor.getFoldingModel().getCollapsedRegionAtOffset(startOffset);
840     FoldRegion endFoldRegion = myEditor.getFoldingModel().getCollapsedRegionAtOffset(endOffset);
841     return startFoldRegion == null || endFoldRegion == null || !startFoldRegion.equals(endFoldRegion);
842   }
843
844   @Nullable
845   private Rectangle getLineRendererRectangle(RangeHighlighter highlighter) {
846     if (!isLineMarkerVisible(highlighter)) return null;
847
848     int startOffset = highlighter.getStartOffset();
849     int endOffset = highlighter.getEndOffset();
850
851     int startY = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(startOffset)).y;
852
853     // top edge of the last line of the highlighted area
854     int endY = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(endOffset)).y;
855     // => add one line height to make height correct (bottom edge of the highlighted area)
856     DocumentEx document = myEditor.getDocument();
857     if (document.getLineStartOffset(document.getLineNumber(endOffset)) != endOffset) {
858       // but if the highlighter ends with the end of line, its line number is the next line, but that line should not be highlighted
859       endY += myEditor.getLineHeight();
860     }
861
862     LineMarkerRenderer renderer = highlighter.getLineMarkerRenderer();
863     boolean leftPosition = renderer instanceof LineMarkerRendererEx && 
864                            ((LineMarkerRendererEx)renderer).getPosition() == LineMarkerRendererEx.Position.LEFT;
865
866     int height = endY - startY;
867     int w = leftPosition ? FREE_PAINTERS_LEFT_AREA_WIDTH : FREE_PAINTERS_RIGHT_AREA_WIDTH;
868     int x = leftPosition ? getLineMarkerAreaOffset() : getLineMarkerFreePaintersAreaOffset() - 1;
869     return new Rectangle(x, startY, w, height);
870   }
871
872   private interface LineGutterIconRendererProcessor {
873     void process(int x, int y, GutterMark renderer);
874   }
875
876   private Icon scaleIcon(Icon icon) {
877     if (Registry.is("editor.scale.gutter.icons") && icon instanceof ScalableIcon) {
878       return ((ScalableIcon)icon).scale((float)myEditor.getLineHeight() / JBUI.scale(17f));
879     }
880     return icon;
881   }
882
883   private void processIconsRow(int line, List<GutterMark> row, LineGutterIconRendererProcessor processor) {
884     processIconsRow(line, row, processor, false);    
885   }
886
887   private void processIconsRow(int line, List<GutterMark> row, LineGutterIconRendererProcessor processor, boolean force) {
888     if (!force && Registry.is("editor.hide.gutter.icons")) return;
889     int middleCount = 0;
890     int middleSize = 0;
891     int x = getIconAreaOffset() + 2;
892     final int y = myEditor.logicalPositionToXY(new LogicalPosition(line, 0)).y;
893
894     for (GutterMark r : row) {
895       if (!checkDumbAware(r, myEditor.getProject())) continue;
896       final GutterIconRenderer.Alignment alignment = ((GutterIconRenderer)r).getAlignment();
897       final Icon icon = scaleIcon(r.getIcon());
898       if (alignment == GutterIconRenderer.Alignment.LEFT) {
899         processor.process(x, y + getTextAlignmentShift(icon), r);
900         x += icon.getIconWidth() + GAP_BETWEEN_ICONS;
901       }
902       else if (alignment == GutterIconRenderer.Alignment.CENTER) {
903         middleCount++;
904         middleSize += icon.getIconWidth() + GAP_BETWEEN_ICONS;
905       }
906     }
907
908     final int leftSize = x - getIconAreaOffset();
909
910     x = getIconAreaOffset() + myIconsAreaWidth - 2; // because of 2px LineMarkerRenderers
911     for (GutterMark r : row) {
912       if (!checkDumbAware(r, myEditor.getProject())) continue;
913       if (((GutterIconRenderer)r).getAlignment() == GutterIconRenderer.Alignment.RIGHT) {
914         Icon icon = scaleIcon(r.getIcon());
915         x -= icon.getIconWidth();
916         processor.process(x, y + getTextAlignmentShift(icon), r);
917         x -= GAP_BETWEEN_ICONS;
918       }
919     }
920
921     int rightSize = myIconsAreaWidth + getIconAreaOffset() - x + 1;
922
923     if (middleCount > 0) {
924       middleSize -= GAP_BETWEEN_ICONS;
925       x = getIconAreaOffset() + leftSize + (myIconsAreaWidth - leftSize - rightSize - middleSize) / 2;
926       for (GutterMark r : row) {
927         if (!checkDumbAware(r, myEditor.getProject())) continue;
928         if (((GutterIconRenderer)r).getAlignment() == GutterIconRenderer.Alignment.CENTER) {
929           Icon icon = scaleIcon(r.getIcon());
930           processor.process(x, y + getTextAlignmentShift(icon), r);
931           x += icon.getIconWidth() + GAP_BETWEEN_ICONS;
932         }
933       }
934     }
935   }
936
937   private int getTextAlignmentShift(Icon icon) {
938     return (myEditor.getLineHeight() - icon.getIconHeight()) /2;
939   }
940
941   @Override
942   public Color getOutlineColor(boolean isActive) {
943     ColorKey key = isActive ? EditorColors.SELECTED_TEARLINE_COLOR : EditorColors.TEARLINE_COLOR;
944     Color color = myEditor.getColorsScheme().getColor(key);
945     return color != null ? color : JBColor.black;
946   }
947
948   @Override
949   public void registerTextAnnotation(@NotNull TextAnnotationGutterProvider provider) {
950     myTextAnnotationGutters.add(provider);
951     myTextAnnotationGutterSizes.add(0);
952     updateSize();
953   }
954
955   @Override
956   public void registerTextAnnotation(@NotNull TextAnnotationGutterProvider provider, @NotNull EditorGutterAction action) {
957     myTextAnnotationGutters.add(provider);
958     myProviderToListener.put(provider, action);
959     myTextAnnotationGutterSizes.add(0);
960     updateSize();
961   }
962
963   private void doPaintFoldingTree(final Graphics2D g, final Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
964     final int anchorX = getFoldingAreaOffset();
965     final int width = getFoldingAnchorWidth();
966
967     Collection<DisplayedFoldingAnchor> anchorsToDisplay =
968       myAnchorsDisplayStrategy.getAnchorsToDisplay(firstVisibleOffset, lastVisibleOffset, myActiveFoldRegion);
969     for (DisplayedFoldingAnchor anchor : anchorsToDisplay) {
970       drawAnchor(width, clip, g, anchorX, anchor.visualLine, anchor.type, anchor.foldRegion == myActiveFoldRegion);
971     }
972   }
973
974   private void paintFoldingLines(final Graphics2D g, final Rectangle clip) {
975     if (!isFoldingOutlineShown()) return;
976
977     if (myPaintBackground) {
978       g.setColor(getOutlineColor(false));
979       int x = getWhitespaceSeparatorOffset();
980       UIUtil.drawLine(g, x, clip.y, x, clip.y + clip.height);
981     }
982
983     final int anchorX = getFoldingAreaOffset();
984     final int width = getFoldingAnchorWidth();
985
986     if (myActiveFoldRegion != null && myActiveFoldRegion.isExpanded() && myActiveFoldRegion.isValid()) {
987       int foldStart = myEditor.offsetToVisualLine(myActiveFoldRegion.getStartOffset());
988       int foldEnd = myEditor.offsetToVisualLine(getEndOffset(myActiveFoldRegion));
989       int startY = myEditor.visibleLineToY(foldStart + 1) - myEditor.getDescent();
990       int endY = myEditor.visibleLineToY(foldEnd) + myEditor.getLineHeight() -
991                  myEditor.getDescent();
992
993       if (startY <= clip.y + clip.height && endY + 1 + myEditor.getDescent() >= clip.y) {
994         int lineX = anchorX + width / 2;
995
996         g.setColor(getOutlineColor(true));
997         UIUtil.drawLine(g, lineX, startY, lineX, endY);
998       }
999     }
1000   }
1001
1002   @Override
1003   public int getWhitespaceSeparatorOffset() {
1004     return getFoldingAreaOffset() + getFoldingAnchorWidth() / 2;
1005   }
1006
1007   public void setActiveFoldRegion(FoldRegion activeFoldRegion) {
1008     if (myActiveFoldRegion != activeFoldRegion) {
1009       myActiveFoldRegion = activeFoldRegion;
1010       repaint();
1011     }
1012   }
1013
1014   public int getHeadCenterY(FoldRegion foldRange) {
1015     int width = getFoldingAnchorWidth();
1016     int foldStart = myEditor.offsetToVisualLine(foldRange.getStartOffset());
1017
1018     return myEditor.visibleLineToY(foldStart) + myEditor.getLineHeight() - myEditor.getDescent() - width / 2;
1019   }
1020
1021   private void drawAnchor(int width, Rectangle clip, Graphics2D g, int anchorX, int visualLine,
1022                           DisplayedFoldingAnchor.Type type, boolean active) {
1023
1024     final int off = JBUI.scale(2);
1025     int height = width + off;
1026     int y;
1027     switch (type) {
1028       case COLLAPSED:
1029         y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent() - width;
1030         if (y <= clip.y + clip.height && y + height >= clip.y) {
1031           drawSquareWithPlus(g, anchorX, y, width, active);
1032         }
1033         break;
1034       case EXPANDED_TOP:
1035         y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent() - width;
1036         if (y <= clip.y + clip.height && y + height >= clip.y) {
1037           drawDirectedBox(g, anchorX, y, width, height, width - off, active);
1038         }
1039         break;
1040       case EXPANDED_BOTTOM:
1041         y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent();
1042         if (y - height <= clip.y + clip.height && y >= clip.y) {
1043           drawDirectedBox(g, anchorX, y, width, -height, -width + off, active);
1044         }
1045         break;
1046     }
1047   }
1048
1049   private int getEndOffset(FoldRegion foldRange) {
1050     LOG.assertTrue(foldRange.isValid(), foldRange);
1051     FoldingGroup group = foldRange.getGroup();
1052     return group == null ? foldRange.getEndOffset() : myEditor.getFoldingModel().getEndOffset(group);
1053   }
1054
1055   private void drawDirectedBox(Graphics2D g,
1056                                int anchorX,
1057                                int y,
1058                                int width,
1059                                int height,
1060                                int baseHeight,
1061                                boolean active) {
1062     Object antialiasing = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
1063     if (SystemInfo.isMac && SystemInfo.JAVA_VERSION.startsWith("1.4.1") || UIUtil.isRetina()) {
1064       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
1065     }
1066
1067     try {
1068       final int off = JBUI.scale(2);
1069       int[] xPoints = {anchorX, anchorX + width, anchorX + width, anchorX + width / 2, anchorX};
1070       int[] yPoints = {y, y, y + baseHeight, y + height, y + baseHeight};
1071
1072       g.setColor(myEditor.getBackgroundColor());
1073       g.fillPolygon(xPoints, yPoints, 5);
1074
1075       g.setColor(getOutlineColor(active));
1076       g.drawPolygon(xPoints, yPoints, 5);
1077
1078       //Minus
1079       int minusHeight = y + baseHeight / 2 + (height - baseHeight) / 4;
1080       UIUtil.drawLine(g, anchorX + off, minusHeight, anchorX + width - off, minusHeight);
1081     }
1082     finally {
1083       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
1084     }
1085   }
1086
1087   private void drawSquareWithPlus(Graphics2D g,
1088                                   int anchorX,
1089                                   int y,
1090                                   int width,
1091                                   boolean active) {
1092     drawSquareWithMinus(g, anchorX, y, width, active);
1093     final int off = JBUI.scale(2);
1094     UIUtil.drawLine(g, anchorX + width / 2, y + off, anchorX + width / 2, y + width - off);
1095   }
1096
1097   @SuppressWarnings("SuspiciousNameCombination")
1098   private void drawSquareWithMinus(Graphics2D g,
1099                                    int anchorX,
1100                                    int y,
1101                                    int width,
1102                                    boolean active) {
1103     g.setColor(myEditor.getBackgroundColor());
1104     g.fillRect(anchorX, y, width, width);
1105
1106     g.setColor(getOutlineColor(active));
1107     g.drawRect(anchorX, y, width, width);
1108     final int off = JBUI.scale(2);
1109     // Draw plus
1110     if (!active) g.setColor(getOutlineColor(true));
1111     UIUtil.drawLine(g, anchorX + off, y + width / 2, anchorX + width - off, y + width / 2);
1112   }
1113
1114   private int getFoldingAnchorWidth() {
1115     return Math.min(JBUI.scale(4), myEditor.getLineHeight() / 2 - JBUI.scale(2)) * 2;
1116   }
1117
1118   public int getFoldingAreaOffset() {
1119     int res = getLineMarkerAreaOffset() + getLineMarkerAreaWidth();
1120     // if there's only folding area visible - no need for the starting gap
1121     return res > GAP_BETWEEN_AREAS ? res : 0;
1122   }
1123
1124   public int getFoldingAreaWidth() {
1125     int width = isFoldingOutlineShown() ? getFoldingAnchorWidth() + 2 : (isRealEditor() ? getFoldingAnchorWidth() : 0);
1126     return JBUI.scale(width);
1127   }
1128
1129   public boolean isRealEditor() {
1130     return EditorUtil.isRealFileEditor(myEditor);
1131   }
1132
1133   @Override
1134   public boolean isLineMarkersShown() {
1135     return myEditor.getSettings().isLineMarkerAreaShown();
1136   }
1137
1138   public boolean isLineNumbersShown() {
1139     return myEditor.getSettings().isLineNumbersShown();
1140   }
1141
1142   @Override
1143   public boolean isAnnotationsShown() {
1144     return !myTextAnnotationGutters.isEmpty();
1145   }
1146
1147   @Override
1148   public boolean isFoldingOutlineShown() {
1149     return myEditor.getSettings().isFoldingOutlineShown() &&
1150            myEditor.getFoldingModel().isFoldingEnabled() &&
1151            !myEditor.isInPresentationMode();
1152   }
1153
1154   private static int getAreaWidthWithGap(int width) {
1155     if (width > 0) {
1156       return width + GAP_BETWEEN_AREAS;
1157     }
1158     return 0;
1159   }
1160
1161   public int getLineNumberAreaWidth() {
1162     return isLineNumbersShown() ? myLineNumberAreaWidth + getAreaWidthWithGap(myAdditionalLineNumberAreaWidth) : 0;
1163   }
1164
1165   public int getLineMarkerAreaWidth() {
1166     return isLineMarkersShown() ? ((myLeftFreePaintersAreaShown ? FREE_PAINTERS_LEFT_AREA_WIDTH : 0) + 
1167                                    myIconsAreaWidth + GAP_BETWEEN_AREAS + FREE_PAINTERS_RIGHT_AREA_WIDTH) :
1168            0;
1169   }
1170
1171   public void setLineNumberAreaWidthFunction(@NotNull TIntFunction calculator) {
1172     myLineNumberAreaWidthFunction = calculator;
1173   }
1174
1175   private void calcLineNumberAreaWidth() {
1176     if (myLineNumberAreaWidthFunction == null || !isLineNumbersShown()) return;
1177
1178     int maxLineNumber = getMaxLineNumber(myLineNumberConvertor);
1179     myLineNumberAreaWidth = myLineNumberAreaWidthFunction.execute(maxLineNumber);
1180
1181     myAdditionalLineNumberAreaWidth = 0;
1182     if (myAdditionalLineNumberConvertor != null) {
1183       int maxAdditionalLineNumber = getMaxLineNumber(myAdditionalLineNumberConvertor);
1184       myAdditionalLineNumberAreaWidth = myLineNumberAreaWidthFunction.execute(maxAdditionalLineNumber);
1185     }
1186   }
1187
1188   private int getMaxLineNumber(@NotNull TIntFunction convertor) {
1189     for (int i = endLineNumber(); i >= 0; i--) {
1190       int number = convertor.execute(i);
1191       if (number >= 0) {
1192         return number;
1193       }
1194     }
1195     return 0;
1196   }
1197
1198   @Nullable
1199   public EditorMouseEventArea getEditorMouseAreaByOffset(int offset) {
1200     if (offset < getAnnotationsAreaOffset()) {
1201       return EditorMouseEventArea.LINE_NUMBERS_AREA;
1202     }
1203
1204     if (offset < getAnnotationsAreaOffset() + getAnnotationsAreaWidth()) {
1205       return EditorMouseEventArea.ANNOTATIONS_AREA;
1206     }
1207
1208     if (offset < getFoldingAreaOffset()) {
1209       return EditorMouseEventArea.LINE_MARKERS_AREA;
1210     }
1211
1212     if (offset < getFoldingAreaOffset() + getFoldingAreaWidth()) {
1213       return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
1214     }
1215
1216     return null;
1217   }
1218
1219   public static int getLineNumberAreaOffset() {
1220     return GAP_BETWEEN_AREAS;
1221   }
1222
1223   @Override
1224   public int getAnnotationsAreaOffset() {
1225     return getLineNumberAreaOffset() + getAreaWidthWithGap(getLineNumberAreaWidth());
1226   }
1227
1228   @Override
1229   public int getAnnotationsAreaWidth() {
1230     return myTextAnnotationGuttersSize;
1231   }
1232
1233   public int getAnnotationsAreaWidthEx() {
1234     return myTextAnnotationGuttersSize + myTextAnnotationExtraSize;
1235   }
1236
1237   @Override
1238   public int getLineMarkerAreaOffset() {
1239     return getAnnotationsAreaOffset() + getAreaWidthWithGap(getAnnotationsAreaWidthEx());
1240   }
1241
1242   @Override
1243   public int getIconAreaOffset() {
1244     return getLineMarkerAreaOffset() + (myLeftFreePaintersAreaShown ? FREE_PAINTERS_LEFT_AREA_WIDTH : 0);
1245   }
1246   
1247   @Override
1248   public int getLineMarkerFreePaintersAreaOffset() {
1249     return getIconAreaOffset() + myIconsAreaWidth + GAP_BETWEEN_AREAS;
1250   }
1251
1252   @Override
1253   public int getIconsAreaWidth() {
1254     return myIconsAreaWidth;
1255   }
1256
1257   private boolean isMirrored() {
1258     return myEditor.getVerticalScrollbarOrientation() != EditorEx.VERTICAL_SCROLLBAR_RIGHT;
1259   }
1260
1261   @NotNull
1262   private AffineTransform getMirrorTransform(@NotNull AffineTransform old, int offset, int width) {
1263     final AffineTransform transform = new AffineTransform(old);
1264     if (isMirrored()) {
1265       //transform.translate(getWidth(), 0); // revert mirroring transform
1266       //transform.scale(-1, 1); // revert mirroring transform
1267       //transform.translate(getWidth() - offset - width, 0); // move range start to the X==0
1268       //transform.translate(-offset, 0);
1269
1270       transform.scale(-1, 1);
1271       transform.translate(-offset * 2 - width, 0);
1272     }
1273     return transform;
1274   }
1275
1276   @Nullable
1277   @Override
1278   public FoldRegion findFoldingAnchorAt(int x, int y) {
1279     if (!myEditor.getSettings().isFoldingOutlineShown()) return null;
1280
1281     int anchorX = getFoldingAreaOffset();
1282     int anchorWidth = getFoldingAnchorWidth();
1283
1284     int neighbourhoodStartOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y - myEditor.getLineHeight())));
1285     int neighbourhoodEndOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y + myEditor.getLineHeight())));
1286
1287     Collection<DisplayedFoldingAnchor> displayedAnchors = myAnchorsDisplayStrategy.getAnchorsToDisplay(neighbourhoodStartOffset, neighbourhoodEndOffset, null);
1288     for (DisplayedFoldingAnchor anchor : displayedAnchors) {
1289       if (rectangleByFoldOffset(anchor.visualLine, anchorWidth, anchorX).contains(convertX(x), y)) return anchor.foldRegion;
1290     }
1291
1292     return null;
1293   }
1294
1295   @SuppressWarnings("SuspiciousNameCombination")
1296   private Rectangle rectangleByFoldOffset(int foldStart, int anchorWidth, int anchorX) {
1297     int anchorY = myEditor.visibleLineToY(foldStart) + myEditor.getLineHeight() -
1298                   myEditor.getDescent() - anchorWidth;
1299     return new Rectangle(anchorX, anchorY, anchorWidth, anchorWidth);
1300   }
1301
1302   @Override
1303   public void mouseDragged(MouseEvent e) {
1304     TooltipController.getInstance().cancelTooltips();
1305   }
1306
1307   @Override
1308   public void mouseMoved(final MouseEvent e) {
1309     String toolTip = null;
1310     final GutterIconRenderer renderer = getGutterRenderer(e);
1311     TooltipController controller = TooltipController.getInstance();
1312     if (renderer != null) {
1313       toolTip = renderer.getTooltipText();
1314     }
1315     else {
1316       ActiveGutterRenderer lineRenderer = getActiveRendererByMouseEvent(e);
1317       if (lineRenderer == null) {
1318         TextAnnotationGutterProvider provider = getProviderAtPoint(e.getPoint());
1319         if (provider != null) {
1320           final int line = getLineNumAtPoint(e.getPoint());
1321           toolTip = provider.getToolTip(line, myEditor);
1322           if (!Comparing.equal(toolTip, myLastGutterToolTip)) {
1323             controller.cancelTooltip(GUTTER_TOOLTIP_GROUP, e, true);
1324             myLastGutterToolTip = toolTip;
1325           }
1326         }
1327       }
1328     }
1329
1330     if (toolTip != null && !toolTip.isEmpty()) {
1331       final Ref<Point> t = new Ref<Point>(e.getPoint());
1332       int line = EditorUtil.yPositionToLogicalLine(myEditor, e);
1333       List<GutterMark> row = myLineToGutterRenderers.get(line);
1334       Balloon.Position ballPosition = Balloon.Position.atRight;
1335       if (row != null) {
1336         final TreeMap<Integer, GutterMark> xPos = new TreeMap<Integer, GutterMark>();
1337         final int[] currentPos = {0};
1338         processIconsRow(line, row, new LineGutterIconRendererProcessor() {
1339           @Override
1340           public void process(int x, int y, GutterMark r) {
1341             xPos.put(x, r);
1342             if (renderer == r && r != null) {
1343               currentPos[0] = x;
1344               Icon icon = scaleIcon(r.getIcon());
1345               t.set(new Point(x + icon.getIconWidth() / 2, y + icon.getIconHeight() / 2));
1346             }
1347           }
1348         });
1349
1350         List<Integer> xx = new ArrayList<Integer>(xPos.keySet());
1351         int posIndex = xx.indexOf(currentPos[0]);
1352         if (xPos.size() > 1 && posIndex == 0) {
1353           ballPosition = Balloon.Position.below;
1354         }
1355       }
1356
1357       RelativePoint showPoint = new RelativePoint(this, t.get());
1358
1359       controller.showTooltipByMouseMove(myEditor, showPoint, ((EditorMarkupModel)myEditor.getMarkupModel()).getErrorStripTooltipRendererProvider().calcTooltipRenderer(toolTip), false, GUTTER_TOOLTIP_GROUP,
1360                                         new HintHint(this, t.get()).setAwtTooltip(true).setPreferredPosition(ballPosition));
1361     }
1362     else {
1363       controller.cancelTooltip(GUTTER_TOOLTIP_GROUP, e, false);
1364     }
1365   }
1366
1367   void validateMousePointer(@NotNull MouseEvent e) {
1368     if (IdeGlassPaneImpl.hasPreProcessedCursor(this)) return;
1369
1370     Cursor cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
1371     FoldRegion foldingAtCursor = findFoldingAnchorAt(e.getX(), e.getY());
1372     setActiveFoldRegion(foldingAtCursor);
1373     if (foldingAtCursor != null) {
1374       cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
1375     }
1376     GutterIconRenderer renderer = getGutterRenderer(e);
1377     if (renderer != null) {
1378       if (renderer.isNavigateAction()) {
1379         cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
1380       }
1381     }
1382     else {
1383       ActiveGutterRenderer lineRenderer = getActiveRendererByMouseEvent(e);
1384       if (lineRenderer != null) {
1385         cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
1386       }
1387       else {
1388         TextAnnotationGutterProvider provider = getProviderAtPoint(e.getPoint());
1389         if (provider != null) {
1390           if (myProviderToListener.containsKey(provider)) {
1391             EditorGutterAction action = myProviderToListener.get(provider);
1392             if (action != null) {
1393               int line = getLineNumAtPoint(e.getPoint());
1394               cursor = action.getCursor(line);
1395             }
1396           }
1397         }
1398       }
1399     }
1400     setCursor(cursor);
1401   }
1402
1403   @Override
1404   public void mouseClicked(MouseEvent e) {
1405     if (e.isPopupTrigger()) {
1406       invokePopup(e);
1407     }
1408   }
1409
1410   private void fireEventToTextAnnotationListeners(final MouseEvent e) {
1411     if (myEditor.getMouseEventArea(e) == EditorMouseEventArea.ANNOTATIONS_AREA) {
1412       final Point clickPoint = e.getPoint();
1413
1414       final TextAnnotationGutterProvider provider = getProviderAtPoint(clickPoint);
1415
1416       if (provider == null) {
1417         return;
1418       }
1419
1420       if (myProviderToListener.containsKey(provider)) {
1421         int line = getLineNumAtPoint(clickPoint);
1422
1423         if (line >= 0 && line < myEditor.getDocument().getLineCount() && UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED)) {
1424           myProviderToListener.get(provider).doAction(line);
1425         }
1426
1427       }
1428     }
1429   }
1430
1431   private int getLineNumAtPoint(final Point clickPoint) {
1432     return EditorUtil.yPositionToLogicalLine(myEditor, clickPoint);
1433   }
1434
1435   @Nullable
1436   private TextAnnotationGutterProvider getProviderAtPoint(final Point clickPoint) {
1437     int current = getAnnotationsAreaOffset();
1438     if (clickPoint.x < current) return null;
1439     for (int i = 0; i < myTextAnnotationGutterSizes.size(); i++) {
1440       current += myTextAnnotationGutterSizes.get(i);
1441       if (clickPoint.x <= current) return myTextAnnotationGutters.get(i);
1442     }
1443
1444     return null;
1445   }
1446
1447   @Override
1448   public void mousePressed(MouseEvent e) {
1449     if (e.isPopupTrigger() || isPopupAction(e)) {
1450       invokePopup(e);
1451     } else if (UIUtil.isCloseClick(e)) {
1452       processClose(e);
1453     }
1454   }
1455
1456   private boolean isPopupAction(MouseEvent e) {
1457     GutterIconRenderer renderer = getGutterRenderer(e);
1458     return renderer != null && renderer.getClickAction() == null && renderer.getPopupMenuActions() != null;
1459   }
1460
1461   @Override
1462   public void mouseReleased(final MouseEvent e) {
1463     if (e.isPopupTrigger()) {
1464       invokePopup(e);
1465       return;
1466     }
1467
1468     GutterIconRenderer renderer = getGutterRenderer(e);
1469     final Project project = myEditor.getProject();
1470
1471     AnAction clickAction = null;
1472     if (renderer != null && e.getButton() < 4) {
1473       clickAction = (InputEvent.BUTTON2_MASK & e.getModifiers()) > 0
1474                     ? renderer.getMiddleButtonClickAction()
1475                     : renderer.getClickAction();
1476     }
1477     if (clickAction != null) {
1478       if (checkDumbAware(clickAction, project)) {
1479         performAction(clickAction, e, "ICON_NAVIGATION", myEditor.getDataContext());
1480         repaint();
1481       }
1482       else {
1483         notifyNotDumbAware(project);
1484       }
1485       e.consume();
1486     }
1487     else {
1488       ActiveGutterRenderer lineRenderer = getActiveRendererByMouseEvent(e);
1489       if (lineRenderer != null) {
1490         lineRenderer.doAction(myEditor, e);
1491       } else {
1492         fireEventToTextAnnotationListeners(e);
1493       }
1494     }
1495   }
1496
1497   @Contract("_, null -> true")
1498   private static boolean checkDumbAware(@NotNull Object possiblyDumbAware, @Nullable Project project) {
1499     return project == null || !DumbService.isDumb(project) || DumbService.isDumbAware(possiblyDumbAware);
1500   }
1501
1502   private static void notifyNotDumbAware(@NotNull Project project) {
1503     DumbService.getInstance(project).showDumbModeNotification("This functionality is not available during indexing");
1504   }
1505
1506   private static void performAction(@NotNull AnAction action, @NotNull InputEvent e, @NotNull String place, @NotNull DataContext context) {
1507     AnActionEvent actionEvent = AnActionEvent.createFromAnAction(action, e, place, context);
1508     action.update(actionEvent);
1509     if (actionEvent.getPresentation().isEnabledAndVisible()) action.actionPerformed(actionEvent);
1510   }
1511
1512   @Nullable
1513   private ActiveGutterRenderer getActiveRendererByMouseEvent(final MouseEvent e) {
1514     if (findFoldingAnchorAt(e.getX(), e.getY()) != null) {
1515       return null;
1516     }
1517     if (e.isConsumed() || e.getX() > getWhitespaceSeparatorOffset()) {
1518       return null;
1519     }
1520     final ActiveGutterRenderer[] gutterRenderer = {null};
1521     Rectangle clip = myEditor.getScrollingModel().getVisibleArea();
1522     int firstVisibleOffset = myEditor.logicalPositionToOffset(
1523       myEditor.xyToLogicalPosition(new Point(0, clip.y - myEditor.getLineHeight())));
1524     int lastVisibleOffset = myEditor.logicalPositionToOffset(
1525       myEditor.xyToLogicalPosition(new Point(0, clip.y + clip.height + myEditor.getLineHeight())));
1526
1527     processRangeHighlighters(firstVisibleOffset, lastVisibleOffset, new RangeHighlighterProcessor() {
1528       @Override
1529       public void process(@NotNull RangeHighlighter highlighter) {
1530         LineMarkerRenderer renderer = highlighter.getLineMarkerRenderer();
1531         if (renderer == null || gutterRenderer[0] != null) return;
1532         Rectangle rectangle = getLineRendererRectangle(highlighter);
1533         if (rectangle == null) return;
1534
1535         int startY = rectangle.y;
1536         int endY = startY + rectangle.height;
1537         if (startY == endY) {
1538           endY += myEditor.getLineHeight();
1539         }
1540
1541         if (startY < e.getY() && e.getY() <= endY) {
1542           if (renderer instanceof ActiveGutterRenderer && ((ActiveGutterRenderer)renderer).canDoAction(e)) {
1543             gutterRenderer[0] = (ActiveGutterRenderer)renderer;
1544           }
1545         }
1546       }
1547     });
1548     return gutterRenderer[0];
1549   }
1550
1551   @Override
1552   public void closeAllAnnotations() {
1553     for (TextAnnotationGutterProvider provider : myTextAnnotationGutters) {
1554       provider.gutterClosed();
1555     }
1556
1557     revalidateSizes();
1558   }
1559
1560   private void revalidateSizes() {
1561     myTextAnnotationGutters = new ArrayList<TextAnnotationGutterProvider>();
1562     myTextAnnotationGutterSizes = new TIntArrayList();
1563     updateSize();
1564   }
1565
1566   private class CloseAnnotationsAction extends DumbAwareAction {
1567     public CloseAnnotationsAction() {
1568       super(EditorBundle.message("close.editor.annotations.action.name"));
1569     }
1570
1571     @Override
1572     public void actionPerformed(@NotNull AnActionEvent e) {
1573       closeAllAnnotations();
1574     }
1575   }
1576
1577   @Override
1578   @Nullable
1579   public Point getPoint(final GutterIconRenderer renderer) {
1580     final Ref<Point> result = Ref.create();
1581     for (int line : myLineToGutterRenderers.keys()) {
1582       processIconsRow(line, myLineToGutterRenderers.get(line), new LineGutterIconRendererProcessor() {
1583         @Override
1584         public void process(int x, int y, GutterMark r) {
1585           if (result.isNull() && r.equals(renderer)) {
1586             result.set(new Point(x, y));
1587           }
1588         }
1589       }, true);
1590
1591       if (!result.isNull()) {
1592         return result.get();
1593       }
1594     }
1595     return null;
1596   }
1597
1598   @Override
1599   public void setLineNumberConvertor(@NotNull TIntFunction lineNumberConvertor) {
1600     setLineNumberConvertor(lineNumberConvertor, null);
1601   }
1602
1603   @Override
1604   public void setLineNumberConvertor(@NotNull TIntFunction lineNumberConvertor1, @Nullable TIntFunction lineNumberConvertor2) {
1605     myLineNumberConvertor = lineNumberConvertor1;
1606     myAdditionalLineNumberConvertor = lineNumberConvertor2;
1607   }
1608
1609   @Override
1610   public void setShowDefaultGutterPopup(boolean show) {
1611     myShowDefaultGutterPopup = show;
1612   }
1613
1614   @Override
1615   public void setGutterPopupGroup(@Nullable ActionGroup group) {
1616     myCustomGutterPopupGroup = group;
1617   }
1618
1619   @Override
1620   public void setPaintBackground(boolean value) {
1621     myPaintBackground = value;
1622   }
1623
1624   private void invokePopup(MouseEvent e) {
1625     final ActionManager actionManager = ActionManager.getInstance();
1626     if (myEditor.getMouseEventArea(e) == EditorMouseEventArea.ANNOTATIONS_AREA) {
1627       DefaultActionGroup actionGroup = new DefaultActionGroup(EditorBundle.message("editor.annotations.action.group.name"), true);
1628       actionGroup.add(new CloseAnnotationsAction());
1629       final List<AnAction> addActions = new ArrayList<AnAction>();
1630       final Point p = e.getPoint();
1631       int line = EditorUtil.yPositionToLogicalLine(myEditor, p);
1632       //if (line >= myEditor.getDocument().getLineCount()) return;
1633
1634       for (TextAnnotationGutterProvider gutterProvider : myTextAnnotationGutters) {
1635         final List<AnAction> list = gutterProvider.getPopupActions(line, myEditor);
1636         if (list != null) {
1637           for (AnAction action : list) {
1638             if (! addActions.contains(action)) {
1639               addActions.add(action);
1640             }
1641           }
1642         }
1643       }
1644       for (AnAction addAction : addActions) {
1645         actionGroup.add(addAction);
1646       }
1647       JPopupMenu menu = actionManager.createActionPopupMenu("", actionGroup).getComponent();
1648       menu.show(this, e.getX(), e.getY());
1649       e.consume();
1650     }
1651     else {
1652       GutterIconRenderer renderer = getGutterRenderer(e);
1653       if (renderer != null) {
1654         Project project = myEditor.getProject();
1655
1656         ActionGroup actionGroup = renderer.getPopupMenuActions();
1657           if (actionGroup != null) {
1658           if (checkDumbAware(actionGroup, project)) {
1659             ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN,
1660                                                                             actionGroup);
1661             popupMenu.getComponent().show(this, e.getX(), e.getY());
1662           } else {
1663             notifyNotDumbAware(project);
1664           }
1665           e.consume();
1666         }
1667         else {
1668           AnAction rightButtonAction = renderer.getRightButtonClickAction();
1669           if (rightButtonAction != null) {
1670             if (checkDumbAware(rightButtonAction, project)) {
1671               performAction(rightButtonAction, e, "ICON_NAVIGATION_SECONDARY_BUTTON", myEditor.getDataContext());
1672             } else {
1673               notifyNotDumbAware(project);
1674             }
1675             e.consume();
1676           }
1677         }
1678       }
1679       else {
1680         ActionGroup group = myCustomGutterPopupGroup;
1681         if (group == null && myShowDefaultGutterPopup) {
1682           group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_EDITOR_GUTTER);
1683         }
1684         if (group != null) {
1685           ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, group);
1686           popupMenu.getComponent().show(this, e.getX(), e.getY());
1687         }
1688         e.consume();
1689       }
1690     }
1691   }
1692
1693   @Override
1694   public void mouseEntered(MouseEvent e) {
1695   }
1696
1697   @Override
1698   public void mouseExited(MouseEvent e) {
1699     TooltipController.getInstance().cancelTooltip(GUTTER_TOOLTIP_GROUP, e, false);
1700   }
1701
1702   private int convertPointToLineNumber(final Point p) {
1703     int line = EditorUtil.yPositionToLogicalLine(myEditor, p);
1704
1705     if (line >= myEditor.getDocument().getLineCount()) return -1;
1706     int startOffset = myEditor.getDocument().getLineStartOffset(line);
1707     final FoldRegion region = myEditor.getFoldingModel().getCollapsedRegionAtOffset(startOffset);
1708     if (region != null) {
1709       line = myEditor.getDocument().getLineNumber(region.getEndOffset());
1710       if (line >= myEditor.getDocument().getLineCount()) return -1;
1711     }
1712     return line;
1713   }
1714
1715   @Nullable
1716   private GutterMark getGutterRenderer(final Point p) {
1717     int line = convertPointToLineNumber(p);
1718     if (line == -1) return null;
1719     List<GutterMark> renderers = myLineToGutterRenderers.get(line);
1720     if (renderers == null) {
1721       return null;
1722     }
1723
1724     final GutterMark[] result = {null};
1725     processIconsRow(line, renderers, new LineGutterIconRendererProcessor() {
1726       @Override
1727       public void process(int x, int y, GutterMark renderer) {
1728         final int ex = convertX((int)p.getX());
1729         Icon icon = scaleIcon(renderer.getIcon());
1730         // Do not check y to extend the area where users could click
1731         if (x <= ex && ex <= x + icon.getIconWidth()) {
1732           result[0] = renderer;
1733         }
1734       }
1735     });
1736
1737     return result[0];
1738   }
1739
1740   @Nullable
1741   private GutterIconRenderer getGutterRenderer(final MouseEvent e) {
1742     return (GutterIconRenderer)getGutterRenderer(e.getPoint());
1743   }
1744
1745   public int convertX(int x) {
1746     if (!isMirrored()) return x;
1747     return getWidth() - x;
1748   }
1749
1750   public void dispose() {
1751     for (TextAnnotationGutterProvider gutterProvider : myTextAnnotationGutters) {
1752       gutterProvider.gutterClosed();
1753     }
1754     myProviderToListener.clear();
1755   }
1756 }