2 * Copyright 2000-2010 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * Created by IntelliJ IDEA.
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
25 package com
.intellij
.openapi
.editor
.impl
;
27 import com
.intellij
.codeInsight
.hint
.TooltipController
;
28 import com
.intellij
.codeInsight
.hint
.TooltipGroup
;
29 import com
.intellij
.ide
.IdeEventQueue
;
30 import com
.intellij
.ide
.ui
.LafManager
;
31 import com
.intellij
.ide
.ui
.UISettings
;
32 import com
.intellij
.openapi
.actionSystem
.*;
33 import com
.intellij
.openapi
.application
.ApplicationManager
;
34 import com
.intellij
.openapi
.application
.impl
.ApplicationImpl
;
35 import com
.intellij
.openapi
.diagnostic
.Logger
;
36 import com
.intellij
.openapi
.editor
.*;
37 import com
.intellij
.openapi
.editor
.colors
.ColorKey
;
38 import com
.intellij
.openapi
.editor
.colors
.EditorColors
;
39 import com
.intellij
.openapi
.editor
.colors
.EditorFontType
;
40 import com
.intellij
.openapi
.editor
.event
.EditorMouseEventArea
;
41 import com
.intellij
.openapi
.editor
.ex
.*;
42 import com
.intellij
.openapi
.editor
.markup
.*;
43 import com
.intellij
.openapi
.util
.Comparing
;
44 import com
.intellij
.openapi
.util
.SystemInfo
;
45 import com
.intellij
.util
.containers
.HashMap
;
46 import com
.intellij
.util
.ui
.UIUtil
;
47 import gnu
.trove
.TIntArrayList
;
48 import gnu
.trove
.TIntObjectHashMap
;
49 import gnu
.trove
.TIntProcedure
;
50 import gnu
.trove
.TObjectProcedure
;
51 import org
.jetbrains
.annotations
.NotNull
;
52 import org
.jetbrains
.annotations
.Nullable
;
55 import javax
.swing
.plaf
.ComponentUI
;
57 import java
.awt
.datatransfer
.DataFlavor
;
58 import java
.awt
.datatransfer
.Transferable
;
59 import java
.awt
.dnd
.*;
60 import java
.awt
.event
.*;
61 import java
.awt
.geom
.AffineTransform
;
62 import java
.util
.ArrayList
;
63 import java
.util
.Iterator
;
64 import java
.util
.List
;
67 class EditorGutterComponentImpl
extends EditorGutterComponentEx
implements MouseListener
, MouseMotionListener
{
68 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.editor.impl.EditorGutterComponentImpl");
69 private static final int START_ICON_AREA_WIDTH
= 15;
70 private static final int FREE_PAINTERS_AREA_WIDTH
= 3;
71 private static final int GAP_BETWEEN_ICONS
= 3;
72 private static final TooltipGroup GUTTER_TOOLTIP_GROUP
= new TooltipGroup("GUTTER_TOOLTIP_GROUP", 0);
74 private final EditorImpl myEditor
;
75 private int myLineMarkerAreaWidth
= START_ICON_AREA_WIDTH
+ FREE_PAINTERS_AREA_WIDTH
;
76 private int myIconsAreaWidth
= START_ICON_AREA_WIDTH
;
77 private int myLineNumberAreaWidth
= 0;
78 private FoldRegion myActiveFoldRegion
;
79 private boolean myPopupInvokedOnPressed
;
80 private int myTextAnnotationGuttersSize
= 0;
81 private TIntArrayList myTextAnnotationGutterSizes
= new TIntArrayList();
82 private ArrayList
<TextAnnotationGutterProvider
> myTextAnnotationGutters
= new ArrayList
<TextAnnotationGutterProvider
>();
83 private final Map
<TextAnnotationGutterProvider
, EditorGutterAction
> myProviderToListener
= new HashMap
<TextAnnotationGutterProvider
, EditorGutterAction
>();
84 private static final int GAP_BETWEEN_ANNOTATIONS
= 6;
85 private Color myBackgroundColor
= null;
86 private GutterDraggableObject myGutterDraggableObject
;
87 private String myLastGutterTooltip
= null;
90 public EditorGutterComponentImpl(EditorImpl editor
) {
92 if (!ApplicationManager
.getApplication().isHeadlessEnvironment()) {
93 new DropTarget(this, new MyDropTargetListener());
94 final DragSource dragSource
= DragSource
.getDefaultDragSource();
95 dragSource
.createDefaultDragGestureRecognizer(this, DnDConstants
.ACTION_COPY_OR_MOVE
, new MyDragGestureListener());
100 private void fireResized() {
101 processComponentEvent(new ComponentEvent(this, ComponentEvent
.COMPONENT_RESIZED
));
104 public Dimension
getPreferredSize() {
105 int w
= getLineNumberAreaWidth() + getLineMarkerAreaWidth() + getFoldingAreaWidth() + getAnnotationsAreaWidth();
106 return new Dimension(w
, myEditor
.getPreferredSize().height
);
109 protected void setUI(ComponentUI newUI
) {
114 public void updateUI() {
119 public void reinitSettings() {
120 myBackgroundColor
= null;
124 public void paint(Graphics g
) {
125 ((ApplicationImpl
)ApplicationManager
.getApplication()).editorPaintStart();
128 Rectangle clip
= g
.getClipBounds();
129 if (clip
.height
< 0) return;
131 final Graphics2D g2
= (Graphics2D
)g
;
132 final AffineTransform old
= g2
.getTransform();
135 final AffineTransform transform
= new AffineTransform(old
);
136 transform
.scale(-1, 1);
137 transform
.translate(-getWidth(), 0);
138 g2
.setTransform(transform
);
141 UISettings
.setupAntialiasing(g
);
142 paintLineNumbers(g
, clip
);
143 paintAnnotations(g
, clip
);
145 Object antialiasing
= g2
.getRenderingHint(RenderingHints
.KEY_ANTIALIASING
);
146 g2
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_OFF
);
149 paintFoldingBackground(g
);
150 paintLineMarkers(g
, clip
);
151 paintFoldingTree(g
, clip
);
154 g2
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, antialiasing
);
157 g2
.setTransform(old
);
160 ((ApplicationImpl
)ApplicationManager
.getApplication()).editorPaintFinish();
163 private void processClose(final MouseEvent e
) {
164 final IdeEventQueue queue
= IdeEventQueue
.getInstance();
166 if (isLineNumbersShown()) {
167 if (e
.getX() >= getLineNumberAreaOffset() && getLineNumberAreaOffset() + getLineNumberAreaWidth() >= e
.getX()) {
168 queue
.blockNextEvents(e
);
169 myEditor
.getSettings().setLineNumbersShown(false);
175 if (getGutterRenderer(e
) != null) return;
177 int x
= getAnnotationsAreaOffset();
178 for (int i
= 0; i
< myTextAnnotationGutters
.size(); i
++) {
179 final int size
= myTextAnnotationGutterSizes
.get(i
);
180 if (x
<= e
.getX() && e
.getX() <= x
+ size
+ GAP_BETWEEN_ANNOTATIONS
) {
181 queue
.blockNextEvents(e
);
182 closeAllAnnotations();
187 x
+= size
+ GAP_BETWEEN_ANNOTATIONS
;
192 private void paintAnnotations(Graphics g
, Rectangle clip
) {
193 paintBackground(g
, clip
, getAnnotationsAreaOffset(), getAnnotationsAreaWidth());
195 int x
= getAnnotationsAreaOffset();
197 Color color
= myEditor
.getColorsScheme().getColor(EditorColors
.ANNOTATIONS_COLOR
);
198 g
.setColor(color
!= null ? color
: Color
.blue
);
199 g
.setFont(myEditor
.getColorsScheme().getFont(EditorFontType
.PLAIN
));
201 for (int i
= 0; i
< myTextAnnotationGutters
.size(); i
++) {
202 TextAnnotationGutterProvider gutterProvider
= myTextAnnotationGutters
.get(i
);
203 int lineHeight
= myEditor
.getLineHeight();
204 int startLineNumber
= clip
.y
/ lineHeight
;
205 int endLineNumber
= (clip
.y
+ clip
.height
) / lineHeight
+ 1;
206 int lastLine
= myEditor
.logicalToVisualPosition(
207 new LogicalPosition(Math
.max(0, myEditor
.getDocument().getLineCount() - 1), 0))
209 endLineNumber
= Math
.min(endLineNumber
, lastLine
+ 1);
210 if (startLineNumber
>= endLineNumber
) {
214 for (int j
= startLineNumber
; j
< endLineNumber
; j
++) {
215 int logLine
= myEditor
.visualToLogicalPosition(new VisualPosition(j
, 0)).line
;
216 String s
= gutterProvider
.getLineText(logLine
, myEditor
);
217 final EditorFontType style
= gutterProvider
.getStyle(logLine
, myEditor
);
218 final Color bg
= gutterProvider
.getBgColor(logLine
, myEditor
);
221 g
.fillRect(x
, j
* lineHeight
, getAnnotationsAreaWidth(), lineHeight
);
223 g
.setColor(myEditor
.getColorsScheme().getColor(gutterProvider
.getColor(logLine
, myEditor
)));
224 g
.setFont(myEditor
.getColorsScheme().getFont(style
));
226 g
.drawString(s
, x
, (j
+1) * lineHeight
- myEditor
.getDescent());
230 x
+= myTextAnnotationGutterSizes
.get(i
);
234 private void paintFoldingTree(Graphics g
, Rectangle clip
) {
235 if (isFoldingOutlineShown()) {
236 paintFoldingTree((Graphics2D
)g
);
239 g
.setColor(Color
.white
);
240 int x
= getWhitespaceSeparatorOffset() - 1;
241 UIUtil
.drawVDottedLine((Graphics2D
)g
, x
, clip
.y
, clip
.y
+ clip
.height
, myEditor
.getBackroundColor(), getFoldingColor(false));
245 private void paintLineMarkers(Graphics g
, Rectangle clip
) {
246 if (isLineMarkersShown()) {
247 paintBackground(g
, clip
, getLineMarkerAreaOffset(), getLineMarkerAreaWidth());
248 paintGutterRenderers(g
);
252 private void paintBackground(final Graphics g
, final Rectangle clip
, final int x
, final int width
) {
253 g
.setColor(getBackground());
254 g
.fillRect(x
, clip
.y
, width
, clip
.height
);
256 paintCaretRowBackground(g
, x
, width
);
259 private void paintCaretRowBackground(final Graphics g
, final int x
, final int width
) {
260 final VisualPosition visCaret
= myEditor
.getCaretModel().getVisualPosition();
261 Color caretRowColor
= myEditor
.getColorsScheme().getColor(EditorColors
.CARET_ROW_COLOR
);
262 if (caretRowColor
!= null) {
263 g
.setColor(caretRowColor
);
264 final Point caretPoint
= myEditor
.visualPositionToXY(visCaret
);
265 g
.fillRect(x
, caretPoint
.y
, width
, myEditor
.getLineHeight());
269 private void paintLineNumbers(Graphics g
, Rectangle clip
) {
270 if (isLineNumbersShown()) {
271 paintBackground(g
, clip
, getLineNumberAreaOffset(), getLineNumberAreaWidth());
272 g
.setColor(Color
.white
);
273 int x
= getLineNumberAreaOffset() + getLineNumberAreaWidth() - 2;
274 UIUtil
.drawLine(g
, x
, clip
.y
, x
, clip
.y
+ clip
.height
);
279 public Color
getBackground() {
280 if (myBackgroundColor
== null) {
281 final Color userDefinedColor
= myEditor
.getColorsScheme().getColor(EditorColors
.LEFT_GUTTER_BACKGROUND
);
282 if (userDefinedColor
!= null) {
283 myBackgroundColor
= userDefinedColor
;
286 LafManager lafManager
= LafManager
.getInstance();
287 if (lafManager
!= null && lafManager
.isUnderAquaLookAndFeel()) {
288 myBackgroundColor
= new Color(0xF0F0F0);
291 myBackgroundColor
= super.getBackground();
295 return myBackgroundColor
;
298 private void paintLineNumbers(Graphics g
) {
299 if (!isLineNumbersShown()) {
302 Rectangle clip
= g
.getClipBounds();
303 int lineHeight
= myEditor
.getLineHeight();
304 int startLineNumber
= clip
.y
/ lineHeight
;
305 int endLineNumber
= (clip
.y
+ clip
.height
) / lineHeight
+ 1;
306 int lastLine
= myEditor
.logicalToVisualPosition(
307 new LogicalPosition(Math
.max(0, myEditor
.getDocument().getLineCount() - 1), 0))
309 endLineNumber
= Math
.min(endLineNumber
, lastLine
+ 1);
310 if (startLineNumber
>= endLineNumber
) {
314 Color color
= myEditor
.getColorsScheme().getColor(EditorColors
.LINE_NUMBERS_COLOR
);
315 g
.setColor(color
!= null ? color
: Color
.blue
);
316 g
.setFont(myEditor
.getColorsScheme().getFont(EditorFontType
.PLAIN
));
318 Graphics2D g2
= (Graphics2D
)g
;
319 AffineTransform old
= g2
.getTransform();
322 AffineTransform originalTransform
= new AffineTransform(old
);
323 originalTransform
.scale(-1, 1);
324 originalTransform
.translate(-getLineNumberAreaWidth() + 2, 0);
325 g2
.setTransform(originalTransform
);
328 for (int i
= startLineNumber
; i
< endLineNumber
; i
++) {
329 int logLine
= myEditor
.visualToLogicalPosition(new VisualPosition(i
, 0)).line
;
330 String s
= String
.valueOf(logLine
+ 1);
332 getLineNumberAreaOffset() + getLineNumberAreaWidth() -
333 myEditor
.getFontMetrics(Font
.PLAIN
).stringWidth(s
) -
335 (i
+ 1) * lineHeight
- myEditor
.getDescent());
338 g2
.setTransform(old
);
341 private interface RangeHighlighterProcessor
{
342 void process(RangeHighlighter highlighter
);
345 private void processRangeHighlighters(RangeHighlighterProcessor p
, int startOffset
, int endOffset
) {
346 final MarkupModelEx docMarkup
= (MarkupModelEx
)myEditor
.getDocument().getMarkupModel(myEditor
.getProject());
347 final HighlighterList docList
= docMarkup
.getHighlighterList();
348 Iterator
<RangeHighlighterImpl
> docHighlighters
= docList
!= null ? docList
.getHighlighterIterator() : null;
350 final MarkupModelEx editorMarkup
= (MarkupModelEx
)myEditor
.getMarkupModel();
351 final HighlighterList editorList
= editorMarkup
.getHighlighterList();
352 Iterator
<RangeHighlighterImpl
> editorHighlighters
= editorList
!= null ? editorList
.getHighlighterIterator() : null;
354 RangeHighlighterImpl lastDocHighlighter
= null;
355 RangeHighlighterImpl lastEditorHighlighter
= null;
358 if (lastDocHighlighter
== null && docHighlighters
!= null && docHighlighters
.hasNext()) {
359 lastDocHighlighter
= docHighlighters
.next();
360 if (!lastDocHighlighter
.isValid() || lastDocHighlighter
.getAffectedAreaStartOffset() > endOffset
) {
361 lastDocHighlighter
= null;
364 if (lastDocHighlighter
.getAffectedAreaEndOffset() < startOffset
) {
365 lastDocHighlighter
= null;
366 //docHighlighters = null;
371 if (lastEditorHighlighter
== null && editorHighlighters
!= null && editorHighlighters
.hasNext()) {
372 lastEditorHighlighter
= editorHighlighters
.next();
373 if (!lastEditorHighlighter
.isValid() || lastEditorHighlighter
.getAffectedAreaStartOffset() > endOffset
) {
374 lastEditorHighlighter
= null;
377 if (lastEditorHighlighter
.getAffectedAreaEndOffset() < startOffset
) {
378 lastEditorHighlighter
= null;
379 //editorHighlighters = null;
384 if (lastDocHighlighter
== null && lastEditorHighlighter
== null) return;
386 final RangeHighlighterImpl lowerHighlighter
;
388 if (less(lastDocHighlighter
, lastEditorHighlighter
)) {
389 lowerHighlighter
= lastDocHighlighter
;
390 lastDocHighlighter
= null;
393 lowerHighlighter
= lastEditorHighlighter
;
394 lastEditorHighlighter
= null;
397 assert lowerHighlighter
!= null;
398 if (!lowerHighlighter
.isValid()) continue;
400 int startLineIndex
= lowerHighlighter
.getDocument().getLineNumber(startOffset
);
401 if (startLineIndex
< 0 || startLineIndex
>= myEditor
.getDocument().getLineCount()) continue;
403 int endLineIndex
= lowerHighlighter
.getDocument().getLineNumber(endOffset
);
404 if (endLineIndex
< 0 || endLineIndex
>= myEditor
.getDocument().getLineCount()) continue;
406 if (lowerHighlighter
.getEditorFilter().avaliableIn(myEditor
)) {
407 p
.process(lowerHighlighter
);
412 private static boolean less(RangeHighlighter h1
, RangeHighlighter h2
) {
413 return h1
!= null && (h2
== null || h1
.getStartOffset() < h2
.getStartOffset());
416 public void revalidateMarkup() {
420 public void updateSize() {
421 int oldIconsWidth
= myLineMarkerAreaWidth
;
422 int oldAnnotationsWidth
= myTextAnnotationGuttersSize
;
424 calcAnnotationsSize();
425 if (oldIconsWidth
!= myLineMarkerAreaWidth
|| oldAnnotationsWidth
!= myTextAnnotationGuttersSize
) {
431 private void calcAnnotationsSize() {
432 myTextAnnotationGuttersSize
= 0;
433 final FontMetrics fontMetrics
= myEditor
.getFontMetrics(Font
.PLAIN
);
434 final int lineCount
= myEditor
.getDocument().getLineCount();
435 for (int j
= 0; j
< myTextAnnotationGutters
.size(); j
++) {
436 TextAnnotationGutterProvider gutterProvider
= myTextAnnotationGutters
.get(j
);
438 for (int i
= 0; i
< lineCount
; i
++) {
439 final String lineText
= gutterProvider
.getLineText(i
, myEditor
);
440 if (lineText
!= null) {
441 gutterSize
= Math
.max(gutterSize
, fontMetrics
.stringWidth(lineText
));
444 if (gutterSize
> 0) gutterSize
+= GAP_BETWEEN_ANNOTATIONS
;
445 myTextAnnotationGutterSizes
.set(j
, gutterSize
);
446 myTextAnnotationGuttersSize
+= gutterSize
;
450 private TIntObjectHashMap
<ArrayList
<GutterIconRenderer
>> myLineToGutterRenderers
;
452 private void calcIconAreaWidth() {
453 myLineToGutterRenderers
= new TIntObjectHashMap
<ArrayList
<GutterIconRenderer
>>();
455 processRangeHighlighters(new RangeHighlighterProcessor() {
456 public void process(RangeHighlighter highlighter
) {
457 GutterIconRenderer renderer
= highlighter
.getGutterIconRenderer();
458 if (renderer
== null || !highlighter
.getEditorFilter().avaliableIn(myEditor
)) return;
460 int startOffset
= highlighter
.getStartOffset();
461 int line
= myEditor
.getDocument().getLineNumber(startOffset
);
463 ArrayList
<GutterIconRenderer
> renderers
= myLineToGutterRenderers
.get(line
);
464 if (renderers
== null) {
465 renderers
= new ArrayList
<GutterIconRenderer
>();
466 myLineToGutterRenderers
.put(line
, renderers
);
469 if (renderers
.size() < 5) { // Don't allow more than 5 icons per line
470 renderers
.add(renderer
);
473 }, 0, myEditor
.getDocument().getTextLength());
475 myIconsAreaWidth
= START_ICON_AREA_WIDTH
;
477 myLineToGutterRenderers
.forEachValue(new TObjectProcedure
<ArrayList
<GutterIconRenderer
>>() {
478 public boolean execute(ArrayList
<GutterIconRenderer
> renderers
) {
480 for (int i
= 0; i
< renderers
.size(); i
++) {
481 GutterIconRenderer renderer
= renderers
.get(i
);
482 width
+= renderer
.getIcon().getIconWidth();
483 if (i
> 0) width
+= GAP_BETWEEN_ICONS
;
485 if (myIconsAreaWidth
< width
) {
486 myIconsAreaWidth
= width
;
492 myLineMarkerAreaWidth
= myIconsAreaWidth
+ FREE_PAINTERS_AREA_WIDTH
+
493 (isFoldingOutlineShown() ?
0 : getFoldingAnchorWidth() / 2);
496 private void paintGutterRenderers(final Graphics g
) {
497 Rectangle clip
= g
.getClipBounds();
499 int firstVisibleOffset
= myEditor
.logicalPositionToOffset(
500 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
- myEditor
.getLineHeight())));
501 int lastVisibleOffset
= myEditor
.logicalPositionToOffset(
502 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
+ clip
.height
+ myEditor
.getLineHeight())));
504 Graphics2D g2
= (Graphics2D
)g
;
506 Object antialiasing
= g2
.getRenderingHint(RenderingHints
.KEY_ANTIALIASING
);
507 g2
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
);
509 processRangeHighlighters(new RangeHighlighterProcessor() {
510 public void process(RangeHighlighter highlighter
) {
511 paintLineMarkerRenderer(highlighter
, g
);
513 }, firstVisibleOffset
, lastVisibleOffset
);
516 g2
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, antialiasing
);
519 int firstVisibleLine
= myEditor
.getDocument().getLineNumber(firstVisibleOffset
);
520 int lastVisibleLine
= myEditor
.getDocument().getLineNumber(lastVisibleOffset
);
521 paintIcons(firstVisibleLine
, lastVisibleLine
, g
);
524 private void paintIcons(final int firstVisibleLine
, final int lastVisibleLine
, final Graphics g
) {
525 myLineToGutterRenderers
.forEachKey(new TIntProcedure() {
526 public boolean execute(int line
) {
527 if (firstVisibleLine
> line
|| lastVisibleLine
< line
) return true;
528 if (isLineCollapsed(line
)) return true;
529 ArrayList
<GutterIconRenderer
> renderers
= myLineToGutterRenderers
.get(line
);
530 paintIconRow(line
, renderers
, g
);
536 private boolean isLineCollapsed(final int line
) {
537 int startOffset
= myEditor
.getDocument().getLineStartOffset(line
);
538 final FoldRegion region
= myEditor
.getFoldingModel().getCollapsedRegionAtOffset(startOffset
);
539 return region
!= null && region
.getEndOffset() >= myEditor
.getDocument().getLineEndOffset(line
);
542 private void paintIconRow(int line
, ArrayList
<GutterIconRenderer
> row
, final Graphics g
) {
543 processIconsRow(line
, row
, new LineGutterIconRendererProcessor() {
544 public void process(int x
, int y
, GutterIconRenderer renderer
) {
545 renderer
.getIcon().paintIcon(EditorGutterComponentImpl
.this, g
, x
, y
);
550 private void paintLineMarkerRenderer(RangeHighlighter highlighter
, Graphics g
) {
551 Rectangle rect
= getLineRendererRect(highlighter
);
554 final LineMarkerRenderer lineMarkerRenderer
= highlighter
.getLineMarkerRenderer();
555 assert lineMarkerRenderer
!= null;
556 lineMarkerRenderer
.paint(myEditor
, g
, rect
);
560 private Rectangle
getLineRendererRect(RangeHighlighter highlighter
) {
561 LineMarkerRenderer renderer
= highlighter
.getLineMarkerRenderer();
562 if (renderer
== null) return null;
564 int startOffset
= highlighter
.getStartOffset();
565 int endOffset
= highlighter
.getEndOffset();
566 if (myEditor
.getFoldingModel().isOffsetCollapsed(startOffset
) &&
567 myEditor
.getFoldingModel().isOffsetCollapsed(endOffset
)) {
571 int startY
= myEditor
.visualPositionToXY(myEditor
.offsetToVisualPosition(startOffset
)).y
;
572 int endY
= myEditor
.visualPositionToXY(myEditor
.offsetToVisualPosition(endOffset
)).y
;
574 int height
= endY
- startY
;
575 int w
= FREE_PAINTERS_AREA_WIDTH
;
576 int x
= getLineMarkerAreaOffset() + myIconsAreaWidth
;
577 return new Rectangle(x
, startY
, w
, height
);
580 private interface LineGutterIconRendererProcessor
{
581 void process(int x
, int y
, GutterIconRenderer renderer
);
584 private void processIconsRow(int line
, ArrayList
<GutterIconRenderer
> row
, LineGutterIconRendererProcessor processor
) {
587 int x
= getLineMarkerAreaOffset() + 1;
588 final int y
= myEditor
.logicalPositionToXY(new LogicalPosition(line
, 0)).y
;
590 for (GutterIconRenderer r
: row
) {
591 final GutterIconRenderer
.Alignment alignment
= r
.getAlignment();
592 final Icon icon
= r
.getIcon();
593 if (alignment
== GutterIconRenderer
.Alignment
.LEFT
) {
594 processor
.process(x
, y
+ getTextAlignmentShift(icon
), r
);
595 x
+= icon
.getIconWidth() + GAP_BETWEEN_ICONS
;
598 if (alignment
== GutterIconRenderer
.Alignment
.CENTER
) {
600 middleSize
+= icon
.getIconWidth() + GAP_BETWEEN_ICONS
;
605 final int leftSize
= x
- getLineMarkerAreaOffset();
607 x
= getLineMarkerAreaOffset() + myIconsAreaWidth
;
608 for (GutterIconRenderer r
: row
) {
609 if (r
.getAlignment() == GutterIconRenderer
.Alignment
.RIGHT
) {
610 Icon icon
= r
.getIcon();
611 x
-= icon
.getIconWidth();
612 processor
.process(x
, y
+ getTextAlignmentShift(icon
), r
);
613 x
-= GAP_BETWEEN_ICONS
;
617 int rightSize
= myIconsAreaWidth
+ getLineMarkerAreaOffset() - x
;
619 if (middleCount
> 0) {
620 middleSize
-= GAP_BETWEEN_ICONS
;
621 x
= getLineMarkerAreaOffset() + leftSize
+ (myIconsAreaWidth
- leftSize
- rightSize
- middleSize
) / 2;
622 for (GutterIconRenderer r
: row
) {
623 if (r
.getAlignment() == GutterIconRenderer
.Alignment
.CENTER
) {
624 Icon icon
= r
.getIcon();
625 processor
.process(x
, y
+ getTextAlignmentShift(icon
), r
);
626 x
+= icon
.getIconWidth() + GAP_BETWEEN_ICONS
;
632 private int getTextAlignmentShift(Icon icon
) {
633 return (myEditor
.getLineHeight() - icon
.getIconHeight()) /2;
636 public Color
getFoldingColor(boolean isActive
) {
637 ColorKey key
= isActive ? EditorColors
.SELECTED_FOLDING_TREE_COLOR
: EditorColors
.FOLDING_TREE_COLOR
;
638 Color color
= myEditor
.getColorsScheme().getColor(key
);
639 return color
!= null ? color
: Color
.black
;
642 public void registerTextAnnotation(@NotNull TextAnnotationGutterProvider provider
) {
643 myTextAnnotationGutters
.add(provider
);
644 myTextAnnotationGutterSizes
.add(0);
648 public void registerTextAnnotation(@NotNull TextAnnotationGutterProvider provider
, @NotNull EditorGutterAction action
) {
649 myTextAnnotationGutters
.add(provider
);
650 myProviderToListener
.put(provider
, action
);
651 myTextAnnotationGutterSizes
.add(0);
655 private VisualPosition
offsetToLineStartPosition(int offset
) {
656 int line
= myEditor
.getDocument().getLineNumber(offset
);
657 return myEditor
.logicalToVisualPosition(new LogicalPosition(line
, 0));
660 private void paintFoldingTree(Graphics2D g
) {
661 Rectangle clip
= g
.getClipBounds();
663 int anchorX
= getFoldingAreaOffset();
664 int width
= getFoldingAnchorWidth();
666 FoldRegion
[] visibleFoldRegions
= ((FoldingModelImpl
)myEditor
.getFoldingModel()).fetchVisible();
668 int firstVisibleOffset
= myEditor
.logicalPositionToOffset(
669 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
- myEditor
.getLineHeight())));
670 int lastVisibleOffset
= myEditor
.logicalPositionToOffset(
671 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
+ clip
.height
+ myEditor
.getLineHeight())));
673 for (FoldRegion visibleFoldRegion
: visibleFoldRegions
) {
674 if (visibleFoldRegion
.getStartOffset() > lastVisibleOffset
) continue;
675 if (getEndOffset(visibleFoldRegion
) < firstVisibleOffset
) continue;
676 drawAnchor(visibleFoldRegion
, width
, clip
, g
, anchorX
, false, false);
679 if (myActiveFoldRegion
!= null) {
680 drawAnchor(myActiveFoldRegion
, width
, clip
, g
, anchorX
, true, true);
681 drawAnchor(myActiveFoldRegion
, width
, clip
, g
, anchorX
, true, false);
685 private void paintFoldingBackground(Graphics g
) {
686 Rectangle clip
= g
.getClipBounds();
687 int lineX
= getWhitespaceSeparatorOffset();
688 paintBackground(g
, clip
, getFoldingAreaOffset(), getFoldingAreaWidth());
690 g
.setColor(myEditor
.getBackroundColor());
691 g
.fillRect(lineX
, clip
.y
, getFoldingAreaWidth(), clip
.height
);
693 paintCaretRowBackground(g
, lineX
, getFoldingAnchorWidth());
695 paintFoldingBoxBacgrounds((Graphics2D
)g
);
698 private void paintFoldingBoxBacgrounds(Graphics2D g
) {
699 if (!isFoldingOutlineShown()) return;
700 Rectangle clip
= g
.getClipBounds();
702 UIUtil
.drawVDottedLine(g
, getWhitespaceSeparatorOffset(), clip
.y
, clip
.y
+ clip
.height
, myEditor
.getBackroundColor(), getFoldingColor(false));
704 int anchorX
= getFoldingAreaOffset();
705 int width
= getFoldingAnchorWidth();
707 FoldRegion
[] visibleFoldRegions
= ((FoldingModelImpl
)myEditor
.getFoldingModel()).fetchVisible();
709 int firstVisibleOffset
= myEditor
.logicalPositionToOffset(
710 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
- myEditor
.getLineHeight())));
711 int lastVisibleOffset
= myEditor
.logicalPositionToOffset(
712 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
+ clip
.height
+ myEditor
.getLineHeight())));
714 if (myActiveFoldRegion
!= null) {
715 drawFoldingLines(myActiveFoldRegion
, clip
, width
, anchorX
, g
);
718 for (FoldRegion visibleFoldRegion
: visibleFoldRegions
) {
719 if (visibleFoldRegion
.getStartOffset() > lastVisibleOffset
) continue;
720 if (getEndOffset(visibleFoldRegion
) < firstVisibleOffset
) continue;
721 drawAnchor(visibleFoldRegion
, width
, clip
, g
, anchorX
, false, true);
725 public int getWhitespaceSeparatorOffset() {
726 return getFoldingAreaOffset() + getFoldingAnchorWidth() / 2;
729 public void setActiveFoldRegion(FoldRegion activeFoldRegion
) {
730 if (myActiveFoldRegion
!= activeFoldRegion
) {
731 myActiveFoldRegion
= activeFoldRegion
;
736 public int getHeadCenterY(FoldRegion foldRange
) {
737 int width
= getFoldingAnchorWidth();
738 VisualPosition foldStart
= offsetToLineStartPosition(foldRange
.getStartOffset());
739 int y
= myEditor
.visibleLineNumberToYPosition(foldStart
.line
) + myEditor
.getLineHeight() - myEditor
.getDescent() -
745 private void drawAnchor(FoldRegion foldRange
, int width
, Rectangle clip
, Graphics2D g
,
746 int anchorX
, boolean active
, boolean paintBackground
) {
747 if (foldRange
.isValid()) {
748 VisualPosition foldStart
= offsetToLineStartPosition(foldRange
.getStartOffset());
750 final int endOffset
= getEndOffset(foldRange
);
751 VisualPosition foldEnd
= offsetToLineStartPosition(endOffset
);
752 final Document document
= myEditor
.getDocument();
753 if (document
.getLineNumber(foldRange
.getStartOffset()) == document
.getLineNumber(endOffset
)) {
757 int y
= myEditor
.visibleLineNumberToYPosition(foldStart
.line
) + myEditor
.getLineHeight() - myEditor
.getDescent() -
759 int height
= width
+ 2;
761 final FoldingGroup group
= foldRange
.getGroup();
763 final boolean drawTop
= group
== null || ((FoldingModelImpl
)myEditor
.getFoldingModel()).getFirstRegion(group
) == foldRange
;
764 if (!foldRange
.isExpanded()) {
765 if (y
<= clip
.y
+ clip
.height
&& y
+ height
>= clip
.y
) {
767 drawSquareWithPlus(g
, anchorX
, y
, width
, active
, paintBackground
);
772 int endY
= myEditor
.visibleLineNumberToYPosition(foldEnd
.line
) + myEditor
.getLineHeight() -
773 myEditor
.getDescent();
775 if (y
<= clip
.y
+ clip
.height
&& y
+ height
>= clip
.y
) {
777 drawDirectedBox(g
, anchorX
, y
, width
, height
, width
- 2, active
, paintBackground
);
781 if (endY
- height
<= clip
.y
+ clip
.height
&& endY
>= clip
.y
) {
782 drawDirectedBox(g
, anchorX
, endY
, width
, -height
, -width
+ 2, active
, paintBackground
);
788 private int getEndOffset(FoldRegion foldRange
) {
789 FoldingGroup group
= foldRange
.getGroup();
790 return group
== null ? foldRange
.getEndOffset() : ((FoldingModelImpl
)myEditor
.getFoldingModel()).getEndOffset(group
);
793 private void drawDirectedBox(Graphics2D g
,
799 boolean active
, boolean paintBackground
) {
800 Object antialiasing
= g
.getRenderingHint(RenderingHints
.KEY_ANTIALIASING
);
801 if (SystemInfo
.isMac
&& SystemInfo
.JAVA_VERSION
.startsWith("1.4.1")) {
802 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
);
806 int[] xPoints
= new int[]{anchorX
, anchorX
+ width
, anchorX
+ width
, anchorX
+ width
/ 2, anchorX
};
807 int[] yPoints
= new int[]{y
, y
, y
+ baseHeight
, y
+ height
, y
+ baseHeight
};
809 if (paintBackground
) {
810 g
.setColor(myEditor
.getBackroundColor());
812 g
.fillPolygon(xPoints
, yPoints
, 5);
815 g
.setColor(getFoldingColor(active
));
816 g
.drawPolygon(xPoints
, yPoints
, 5);
819 int minusHeight
= y
+ baseHeight
/ 2 + (height
- baseHeight
) / 4;
820 UIUtil
.drawLine(g
, anchorX
+ 2, minusHeight
, anchorX
+ width
- 2, minusHeight
);
824 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, antialiasing
);
828 private void drawSquareWithPlus(Graphics2D g
,
833 boolean paintBackground
) {
834 drawSquareWithMinus(g
, anchorX
, y
, width
, active
, paintBackground
);
836 UIUtil
.drawLine(g
, anchorX
+ width
/ 2, y
+ 2, anchorX
+ width
/ 2, y
+ width
- 2);
839 private void drawSquareWithMinus(Graphics2D g
,
844 boolean paintBackground
) {
845 if (paintBackground
) {
846 g
.setColor(myEditor
.getBackroundColor());
847 g
.fillRect(anchorX
, y
, width
, width
);
850 g
.setColor(getFoldingColor(active
));
851 g
.drawRect(anchorX
, y
, width
, width
);
854 if (!active
) g
.setColor(getFoldingColor(true));
855 UIUtil
.drawLine(g
, anchorX
+ 2, y
+ width
/ 2, anchorX
+ width
- 2, y
+ width
/ 2);
859 private void drawFoldingLines(FoldRegion foldRange
, Rectangle clip
, int width
, int anchorX
, Graphics2D g
) {
860 if (foldRange
.isExpanded() && foldRange
.isValid()) {
861 VisualPosition foldStart
= offsetToLineStartPosition(foldRange
.getStartOffset());
862 VisualPosition foldEnd
= offsetToLineStartPosition(getEndOffset(foldRange
));
863 int startY
= myEditor
.visibleLineNumberToYPosition(foldStart
.line
+ 1) - myEditor
.getDescent();
864 int endY
= myEditor
.visibleLineNumberToYPosition(foldEnd
.line
) + myEditor
.getLineHeight() -
865 myEditor
.getDescent();
867 if (startY
> clip
.y
+ clip
.height
|| endY
+ 1 + myEditor
.getDescent() < clip
.y
) return;
869 int lineX
= anchorX
+ width
/ 2;
871 g
.setColor(getFoldingColor(true));
872 UIUtil
.drawLine(g
, lineX
, startY
, lineX
, endY
);
876 private int getFoldingAnchorWidth() {
877 return Math
.min(4, myEditor
.getLineHeight() / 2 - 2) * 2;
880 public int getFoldingAreaOffset() {
881 return getLineMarkerAreaOffset() +
882 getLineMarkerAreaWidth();
885 public int getFoldingAreaWidth() {
886 return isFoldingOutlineShown()
887 ?
getFoldingAnchorWidth() + 2
888 : isLineNumbersShown() ?
getFoldingAnchorWidth() / 2 : 0;
891 public boolean isLineMarkersShown() {
892 return myEditor
.getSettings().isLineMarkerAreaShown();
895 public boolean isLineNumbersShown() {
896 return myEditor
.getSettings().isLineNumbersShown();
899 public boolean isFoldingOutlineShown() {
900 return myEditor
.getSettings().isFoldingOutlineShown() &&
901 ((FoldingModelEx
)myEditor
.getFoldingModel()).isFoldingEnabled();
904 public int getLineNumberAreaWidth() {
905 if (isLineNumbersShown()) {
906 return myLineNumberAreaWidth
;
913 public int getLineMarkerAreaWidth() {
914 return isLineMarkersShown() ? myLineMarkerAreaWidth
: 0;
917 public void setLineNumberAreaWidth(int lineNumberAriaWidth
) {
918 if (myLineNumberAreaWidth
!= lineNumberAriaWidth
) {
919 myLineNumberAreaWidth
= lineNumberAriaWidth
;
924 public int getLineNumberAreaOffset() {
928 public int getAnnotationsAreaOffset() {
929 return getLineNumberAreaOffset() + getLineNumberAreaWidth();
932 public int getAnnotationsAreaWidth() {
933 return myTextAnnotationGuttersSize
;
936 public int getLineMarkerAreaOffset() {
937 return getAnnotationsAreaOffset() + getAnnotationsAreaWidth();
940 public int getIconsAreaWidth() {
941 return myIconsAreaWidth
;
944 private boolean isMirrored() {
945 return myEditor
.getVerticalScrollbarOrientation() != EditorEx
.VERTICAL_SCROLLBAR_RIGHT
;
948 public FoldRegion
findFoldingAnchorAt(int x
, int y
) {
949 if (!myEditor
.getSettings().isFoldingOutlineShown()) return null;
951 int anchorX
= getFoldingAreaOffset();
952 int anchorWidth
= getFoldingAnchorWidth();
954 FoldRegion
[] visibleRanges
= ((FoldingModelImpl
)myEditor
.getFoldingModel()).fetchVisible();
955 for (FoldRegion foldRange
: visibleRanges
) {
956 final FoldingGroup group
= foldRange
.getGroup();
957 if (group
!= null && ((FoldingModelImpl
)myEditor
.getFoldingModel()).getFirstRegion(group
) != foldRange
) {
961 VisualPosition foldStart
= offsetToLineStartPosition(foldRange
.getStartOffset());
962 final int endOffset
= getEndOffset(foldRange
);
963 VisualPosition foldEnd
= offsetToLineStartPosition(endOffset
);
964 final Document document
= myEditor
.getDocument();
965 if (document
.getLineNumber(foldRange
.getStartOffset()) == document
.getLineNumber(endOffset
)) {
969 if (rectByFoldOffset(foldStart
, anchorWidth
, anchorX
).contains(x
, y
)) return foldRange
;
970 if ((group
== null || foldRange
.isExpanded()) && rectByFoldOffset(foldEnd
, anchorWidth
, anchorX
).contains(x
, y
)) return foldRange
;
976 private Rectangle
rectByFoldOffset(VisualPosition foldStart
, int anchorWidth
, int anchorX
) {
977 int anchorY
= myEditor
.visibleLineNumberToYPosition(foldStart
.line
) + myEditor
.getLineHeight() -
978 myEditor
.getDescent() - anchorWidth
;
979 return new Rectangle(anchorX
, anchorY
, anchorWidth
, anchorWidth
);
982 public void mouseDragged(MouseEvent e
) {
983 TooltipController
.getInstance().cancelTooltips();
986 public void mouseMoved(final MouseEvent e
) {
987 String tooltip
= null;
988 GutterIconRenderer renderer
= getGutterRenderer(e
);
989 TooltipController controller
= TooltipController
.getInstance();
990 if (renderer
!= null) {
991 tooltip
= renderer
.getTooltipText();
992 if (renderer
.isNavigateAction()) {
993 setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
997 ActiveGutterRenderer lineRenderer
= getActiveRendererByMouseEvent(e
);
998 if (lineRenderer
!= null) {
999 setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
1003 TextAnnotationGutterProvider provider
= getProviderAtPoint(e
.getPoint());
1004 if (provider
!= null) {
1005 final int line
= getLineNumAtPoint(e
.getPoint());
1006 tooltip
= provider
.getToolTip(line
, myEditor
);
1007 if (!Comparing
.equal(tooltip
, myLastGutterTooltip
)) {
1008 controller
.cancelTooltip(GUTTER_TOOLTIP_GROUP
);
1009 myLastGutterTooltip
= tooltip
;
1011 if (myProviderToListener
.containsKey(provider
)) {
1012 final EditorGutterAction action
= myProviderToListener
.get(provider
);
1013 if (action
!= null) {
1014 setCursor(action
.getCursor(line
));
1021 if (tooltip
!= null && tooltip
.length() != 0) {
1022 controller
.showTooltipByMouseMove(myEditor
, e
, ((EditorMarkupModel
)myEditor
.getMarkupModel()).getErrorStripTooltipRendererProvider().calcTooltipRenderer(tooltip
), false, GUTTER_TOOLTIP_GROUP
);
1025 controller
.cancelTooltip(GUTTER_TOOLTIP_GROUP
);
1029 public void mouseClicked(MouseEvent e
) {
1030 if (e
.isPopupTrigger()) {
1035 private void fireEventToTextAnnotationListeners(final MouseEvent e
) {
1036 if (myEditor
.getMouseEventArea(e
) == EditorMouseEventArea
.ANNOTATIONS_AREA
) {
1037 final Point clickPoint
= e
.getPoint();
1039 final TextAnnotationGutterProvider provider
= getProviderAtPoint(clickPoint
);
1041 if (provider
== null) {
1045 if (myProviderToListener
.containsKey(provider
)) {
1046 int line
= getLineNumAtPoint(clickPoint
);
1048 if (line
>= 0 && UIUtil
.isActionClick(e
, MouseEvent
.MOUSE_RELEASED
)) {
1049 myProviderToListener
.get(provider
).doAction(line
);
1056 private int getLineNumAtPoint(final Point clickPoint
) {
1057 return myEditor
.xyToLogicalPosition(new Point(0, clickPoint
.y
)).line
;
1060 private TextAnnotationGutterProvider
getProviderAtPoint(final Point clickPoint
) {
1061 int current
= getAnnotationsAreaOffset();
1062 if (clickPoint
.x
< current
) return null;
1063 for (int i
= 0; i
< myTextAnnotationGutterSizes
.size(); i
++) {
1064 current
+= myTextAnnotationGutterSizes
.get(i
);
1065 if (clickPoint
.x
<= current
) return myTextAnnotationGutters
.get(i
);
1071 public void mousePressed(MouseEvent e
) {
1072 if (e
.isPopupTrigger()) {
1074 myPopupInvokedOnPressed
= true;
1075 } else if (UIUtil
.isCloseClick(e
)) {
1080 public void mouseReleased(final MouseEvent e
) {
1081 if (e
.isPopupTrigger()) {
1086 if (myPopupInvokedOnPressed
) {
1087 myPopupInvokedOnPressed
= false;
1091 GutterIconRenderer renderer
= getGutterRenderer(e
);
1092 AnAction clickAction
= null;
1093 if (renderer
!= null) {
1094 clickAction
= (InputEvent
.BUTTON2_MASK
& e
.getModifiers()) > 0
1095 ? renderer
.getMiddleButtonClickAction()
1096 : renderer
.getClickAction();
1098 if (clickAction
!= null) {
1099 clickAction
.actionPerformed(new AnActionEvent(e
, myEditor
.getDataContext(), "ICON_NAVIGATION", clickAction
.getTemplatePresentation(),
1100 ActionManager
.getInstance(),
1106 ActiveGutterRenderer lineRenderer
= getActiveRendererByMouseEvent(e
);
1107 if (lineRenderer
!= null) {
1108 lineRenderer
.doAction(myEditor
, e
);
1110 fireEventToTextAnnotationListeners(e
);
1115 private ActiveGutterRenderer
getActiveRendererByMouseEvent(final MouseEvent e
) {
1116 final ActiveGutterRenderer
[] gutterRenderer
= new ActiveGutterRenderer
[]{null};
1117 if (findFoldingAnchorAt(e
.getX(), e
.getY()) == null) {
1118 if (!e
.isConsumed() &&
1120 e
.getX() <= getWhitespaceSeparatorOffset()) {
1121 Rectangle clip
= myEditor
.getScrollingModel().getVisibleArea();
1122 int firstVisibleOffset
= myEditor
.logicalPositionToOffset(
1123 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
- myEditor
.getLineHeight())));
1124 int lastVisibleOffset
= myEditor
.logicalPositionToOffset(
1125 myEditor
.xyToLogicalPosition(new Point(0, clip
.y
+ clip
.height
+ myEditor
.getLineHeight())));
1127 processRangeHighlighters(new RangeHighlighterProcessor() {
1128 public void process(RangeHighlighter highlighter
) {
1129 if (gutterRenderer
[0] != null) return;
1130 Rectangle rect
= getLineRendererRect(highlighter
);
1131 if (rect
== null) return;
1133 int startY
= rect
.y
;
1134 int endY
= startY
+ rect
.height
;
1135 if (startY
== endY
) {
1136 endY
+= myEditor
.getLineHeight();
1139 if (startY
< e
.getY() && e
.getY() <= endY
) {
1140 final LineMarkerRenderer renderer
= highlighter
.getLineMarkerRenderer();
1141 if (renderer
instanceof ActiveGutterRenderer
&& ((ActiveGutterRenderer
)renderer
).canDoAction(e
)) {
1142 gutterRenderer
[0] = (ActiveGutterRenderer
)renderer
;
1146 }, firstVisibleOffset
, lastVisibleOffset
);
1149 return gutterRenderer
[0];
1152 public void closeAllAnnotations() {
1153 for (TextAnnotationGutterProvider provider
: myTextAnnotationGutters
) {
1154 provider
.gutterClosed();
1160 private void revalidateSizes() {
1161 myTextAnnotationGutters
= new ArrayList
<TextAnnotationGutterProvider
>();
1162 myTextAnnotationGutterSizes
= new TIntArrayList();
1166 private class CloseAnnotationsAction
extends AnAction
{
1167 public CloseAnnotationsAction() {
1168 super(EditorBundle
.message("close.editor.annotations.action.name"));
1171 public void actionPerformed(AnActionEvent e
) {
1172 closeAllAnnotations();
1176 private void invokePopup(MouseEvent e
) {
1177 final ActionManager actionManager
= ActionManager
.getInstance();
1178 if (myEditor
.getMouseEventArea(e
) == EditorMouseEventArea
.ANNOTATIONS_AREA
) {
1179 DefaultActionGroup actionGroup
= new DefaultActionGroup(EditorBundle
.message("editor.annotations.action.group.name"), true);
1180 actionGroup
.add(new CloseAnnotationsAction());
1181 final List
<AnAction
> addActions
= new ArrayList
<AnAction
>();
1182 for (TextAnnotationGutterProvider gutterProvider
: myTextAnnotationGutters
) {
1183 final List
<AnAction
> list
= gutterProvider
.getPopupActions(myEditor
);
1185 for (AnAction action
: list
) {
1186 if (! addActions
.contains(action
)) {
1187 addActions
.add(action
);
1192 for (AnAction addAction
: addActions
) {
1193 actionGroup
.add(addAction
);
1195 JPopupMenu menu
= actionManager
.createActionPopupMenu("", actionGroup
).getComponent();
1196 menu
.show(this, e
.getX(), e
.getY());
1199 GutterIconRenderer renderer
= getGutterRenderer(e
);
1200 if (renderer
!= null) {
1201 ActionGroup actionGroup
= renderer
.getPopupMenuActions();
1202 if (actionGroup
!= null) {
1203 ActionPopupMenu popupMenu
= actionManager
.createActionPopupMenu(ActionPlaces
.UNKNOWN
,
1205 popupMenu
.getComponent().show(this, e
.getX(), e
.getY());
1210 ActionPopupMenu popupMenu
= actionManager
.createActionPopupMenu(ActionPlaces
.UNKNOWN
,
1211 (ActionGroup
) actionManager
.getAction("EditorGutterPopupMenu"));
1212 popupMenu
.getComponent().show(this, e
.getX(), e
.getY());
1218 public void mouseEntered(MouseEvent e
) {
1221 public void mouseExited(MouseEvent e
) {
1222 TooltipController
.getInstance().cancelTooltip(GUTTER_TOOLTIP_GROUP
);
1226 private GutterIconRenderer
getGutterRenderer(final Point p
) {
1227 final int ex
= convertX((int)p
.getX());
1228 int line
= myEditor
.xyToLogicalPosition(new Point(0, (int)p
.getY())).line
;
1230 if (line
>= myEditor
.getDocument().getLineCount()) return null;
1231 int startOffset
= myEditor
.getDocument().getLineStartOffset(line
);
1232 final FoldRegion region
= myEditor
.getFoldingModel().getCollapsedRegionAtOffset(startOffset
);
1233 if (region
!= null) {
1234 line
= myEditor
.getDocument().getLineNumber(region
.getEndOffset());
1235 if (line
>= myEditor
.getDocument().getLineCount()) return null;
1238 ArrayList
<GutterIconRenderer
> renderers
= myLineToGutterRenderers
.get(line
);
1239 if (renderers
== null) return null;
1241 final GutterIconRenderer
[] result
= new GutterIconRenderer
[]{null};
1242 processIconsRow(line
, renderers
, new LineGutterIconRendererProcessor() {
1243 public void process(int x
, int y
, GutterIconRenderer renderer
) {
1244 Icon icon
= renderer
.getIcon();
1245 if (x
<= ex
&& ex
<= x
+ icon
.getIconWidth() &&
1246 y
<= p
.getY() && p
.getY() <= y
+ icon
.getIconHeight()) {
1247 result
[0] = renderer
;
1256 private GutterIconRenderer
getGutterRenderer(final MouseEvent e
) {
1257 return getGutterRenderer(e
.getPoint());
1260 public int convertX(int x
) {
1261 if (!isMirrored()) return x
;
1262 return getWidth() - x
;
1265 public void dispose() {
1266 for (TextAnnotationGutterProvider gutterProvider
: myTextAnnotationGutters
) {
1267 gutterProvider
.gutterClosed();
1269 myProviderToListener
.clear();
1272 private static final DataFlavor
[] FLAVORS
;
1274 DataFlavor
[] flavors
;
1276 final Class
<EditorGutterComponentImpl
> aClass
= EditorGutterComponentImpl
.class;
1277 //noinspection HardCodedStringLiteral
1278 flavors
= new DataFlavor
[]{new DataFlavor(
1279 DataFlavor
.javaJVMLocalObjectMimeType
+ ";class=" + aClass
.getName(), "GutterTransferable", aClass
.getClassLoader()
1282 catch (ClassNotFoundException e
) {
1283 LOG
.error(e
); // should not happen
1284 flavors
= new DataFlavor
[0];
1289 private class MyDragGestureListener
implements DragGestureListener
{
1290 public void dragGestureRecognized(DragGestureEvent dge
) {
1291 if ((dge
.getDragAction() & DnDConstants
.ACTION_MOVE
) == 0) return;
1292 final GutterIconRenderer renderer
= getGutterRenderer(dge
.getDragOrigin());
1293 if (renderer
!= null) {
1294 final GutterDraggableObject draggableObject
= renderer
.getDraggableObject();
1295 if (draggableObject
!= null) {
1297 myGutterDraggableObject
= draggableObject
;
1298 final MyDragSourceListener dragSourceListener
= new MyDragSourceListener();
1299 dge
.startDrag(DragSource
.DefaultMoveNoDrop
, new Transferable () {
1300 public DataFlavor
[] getTransferDataFlavors() {
1304 public boolean isDataFlavorSupported(DataFlavor flavor
) {
1305 DataFlavor
[] flavors
= getTransferDataFlavors();
1306 for (DataFlavor flavor1
: flavors
) {
1307 if (flavor
.equals(flavor1
)) {
1314 public Object
getTransferData(DataFlavor flavor
) {
1317 }, dragSourceListener
);
1319 catch (InvalidDnDOperationException idoe
) {
1328 private class MyDragSourceListener
implements DragSourceListener
{
1329 public void dragEnter(DragSourceDragEvent dsde
) {
1333 public void dragOver(DragSourceDragEvent dsde
) {
1337 public void dropActionChanged(DragSourceDragEvent dsde
) {
1338 dsde
.getDragSourceContext().setCursor(null);//setCursor (dsde.getDragSourceContext());
1341 private void updateCursor(final DragSourceDragEvent dsde
) {
1342 final DragSourceContext context
= dsde
.getDragSourceContext();
1343 final Point screenPoint
= dsde
.getLocation();
1344 if (screenPoint
!= null) {
1345 final Point gutterPoint
= new Point(screenPoint
);
1346 SwingUtilities
.convertPointFromScreen(gutterPoint
, EditorGutterComponentImpl
.this);
1347 if (contains(gutterPoint
)){
1348 final Point editorPoint
= new Point(screenPoint
);
1349 SwingUtilities
.convertPointFromScreen(editorPoint
, myEditor
.getContentComponent());
1350 int line
= myEditor
.xyToLogicalPosition(new Point(0, (int)editorPoint
.getY())).line
;
1351 final Cursor cursor
= myGutterDraggableObject
.getCursor(line
);
1352 context
.setCursor(cursor
);
1356 context
.setCursor(null);
1359 public void dragDropEnd(DragSourceDropEvent dsde
) {
1360 if (!dsde
.getDropSuccess()) return;
1362 if (dsde
.getDropAction() == DnDConstants
.ACTION_MOVE
) {
1363 myGutterDraggableObject
.removeSelf();
1367 public void dragExit(DragSourceEvent dse
) {}
1370 private class MyDropTargetListener
implements DropTargetListener
{
1371 public void dragEnter(DropTargetDragEvent dtde
) {}
1373 public void dragOver(DropTargetDragEvent dtde
) {}
1375 public void dropActionChanged(DropTargetDragEvent dtde
) {}
1377 public void drop(DropTargetDropEvent dtde
) {
1378 if (myGutterDraggableObject
!= null) {
1379 int dropAction
= dtde
.getDropAction();
1380 if ((dropAction
& DnDConstants
.ACTION_MOVE
) != 0) {
1381 int line
= myEditor
.xyToLogicalPosition(new Point(0, (int)dtde
.getLocation().getY())).line
;
1382 dtde
.dropComplete(myGutterDraggableObject
.copy(line
));
1390 public void dragExit(DropTargetEvent dte
) {}