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