IDEA-51739
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / EditorGutterComponentImpl.java
blob9a9b5b0f89d727ac61d9339c281903ae990631cd
1 /*
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.
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).
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;
54 import javax.swing.*;
55 import javax.swing.plaf.ComponentUI;
56 import java.awt.*;
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;
65 import java.util.Map;
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) {
91 myEditor = 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());
97 setOpaque(true);
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) {
110 super.setUI(newUI);
111 reinitSettings();
114 public void updateUI() {
115 super.updateUI();
116 reinitSettings();
119 public void reinitSettings() {
120 myBackgroundColor = null;
121 repaint();
124 public void paint(Graphics g) {
125 ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart();
127 try {
128 Rectangle clip = g.getClipBounds();
129 if (clip.height < 0) return;
131 final Graphics2D g2 = (Graphics2D)g;
132 final AffineTransform old = g2.getTransform();
134 if (isMirrored()) {
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);
148 try {
149 paintFoldingBackground(g);
150 paintLineMarkers(g, clip);
151 paintFoldingTree(g, clip);
153 finally {
154 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
157 g2.setTransform(old);
159 finally {
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);
170 e.consume();
171 return;
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();
183 e.consume();
184 break;
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))
208 .line;
209 endLineNumber = Math.min(endLineNumber, lastLine + 1);
210 if (startLineNumber >= endLineNumber) {
211 return;
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);
219 if (bg != null) {
220 g.setColor(bg);
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));
225 if (s != null) {
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);
238 else {
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);
275 paintLineNumbers(g);
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;
285 else {
286 LafManager lafManager = LafManager.getInstance();
287 if (lafManager != null && lafManager.isUnderAquaLookAndFeel()) {
288 myBackgroundColor = new Color(0xF0F0F0);
290 else {
291 myBackgroundColor = super.getBackground();
295 return myBackgroundColor;
298 private void paintLineNumbers(Graphics g) {
299 if (!isLineNumbersShown()) {
300 return;
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))
308 .line;
309 endLineNumber = Math.min(endLineNumber, lastLine + 1);
310 if (startLineNumber >= endLineNumber) {
311 return;
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();
321 if (isMirrored()) {
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);
331 g.drawString(s,
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;
357 while (true) {
358 if (lastDocHighlighter == null && docHighlighters != null && docHighlighters.hasNext()) {
359 lastDocHighlighter = docHighlighters.next();
360 if (!lastDocHighlighter.isValid() || lastDocHighlighter.getAffectedAreaStartOffset() > endOffset) {
361 lastDocHighlighter = null;
362 continue;
364 if (lastDocHighlighter.getAffectedAreaEndOffset() < startOffset) {
365 lastDocHighlighter = null;
366 //docHighlighters = null;
367 continue;
371 if (lastEditorHighlighter == null && editorHighlighters != null && editorHighlighters.hasNext()) {
372 lastEditorHighlighter = editorHighlighters.next();
373 if (!lastEditorHighlighter.isValid() || lastEditorHighlighter.getAffectedAreaStartOffset() > endOffset) {
374 lastEditorHighlighter = null;
375 continue;
377 if (lastEditorHighlighter.getAffectedAreaEndOffset() < startOffset) {
378 lastEditorHighlighter = null;
379 //editorHighlighters = null;
380 continue;
384 if (lastDocHighlighter == null && lastEditorHighlighter == null) return;
386 final RangeHighlighterImpl lowerHighlighter;
388 if (less(lastDocHighlighter, lastEditorHighlighter)) {
389 lowerHighlighter = lastDocHighlighter;
390 lastDocHighlighter = null;
392 else {
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() {
417 updateSize();
420 public void updateSize() {
421 int oldIconsWidth = myLineMarkerAreaWidth;
422 int oldAnnotationsWidth = myTextAnnotationGuttersSize;
423 calcIconAreaWidth();
424 calcAnnotationsSize();
425 if (oldIconsWidth != myLineMarkerAreaWidth || oldAnnotationsWidth != myTextAnnotationGuttersSize) {
426 fireResized();
428 repaint();
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);
437 int gutterSize = 0;
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) {
479 int width = 1;
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;
488 return true;
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);
508 try {
509 processRangeHighlighters(new RangeHighlighterProcessor() {
510 public void process(RangeHighlighter highlighter) {
511 paintLineMarkerRenderer(highlighter, g);
513 }, firstVisibleOffset, lastVisibleOffset);
515 finally {
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);
531 return true;
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);
553 if (rect != null) {
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)) {
568 return null;
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) {
585 int middleCount = 0;
586 int middleSize = 0;
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;
597 else {
598 if (alignment == GutterIconRenderer.Alignment.CENTER) {
599 middleCount++;
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);
645 updateSize();
648 public void registerTextAnnotation(@NotNull TextAnnotationGutterProvider provider, @NotNull EditorGutterAction action) {
649 myTextAnnotationGutters.add(provider);
650 myProviderToListener.put(provider, action);
651 myTextAnnotationGutterSizes.add(0);
652 updateSize();
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;
732 repaint();
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() -
740 width / 2;
742 return y;
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)) {
754 return;
757 int y = myEditor.visibleLineNumberToYPosition(foldStart.line) + myEditor.getLineHeight() - myEditor.getDescent() -
758 width;
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) {
766 if (drawTop) {
767 drawSquareWithPlus(g, anchorX, y, width, active, paintBackground);
771 else {
772 int endY = myEditor.visibleLineNumberToYPosition(foldEnd.line) + myEditor.getLineHeight() -
773 myEditor.getDescent();
775 if (y <= clip.y + clip.height && y + height >= clip.y) {
776 if (drawTop) {
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,
794 int anchorX,
795 int y,
796 int width,
797 int height,
798 int baseHeight,
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);
805 try {
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);
814 else {
815 g.setColor(getFoldingColor(active));
816 g.drawPolygon(xPoints, yPoints, 5);
818 //Minus
819 int minusHeight = y + baseHeight / 2 + (height - baseHeight) / 4;
820 UIUtil.drawLine(g, anchorX + 2, minusHeight, anchorX + width - 2, minusHeight);
823 finally {
824 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
828 private void drawSquareWithPlus(Graphics2D g,
829 int anchorX,
830 int y,
831 int width,
832 boolean active,
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,
840 int anchorX,
841 int y,
842 int width,
843 boolean active,
844 boolean paintBackground) {
845 if (paintBackground) {
846 g.setColor(myEditor.getBackroundColor());
847 g.fillRect(anchorX, y, width, width);
849 else {
850 g.setColor(getFoldingColor(active));
851 g.drawRect(anchorX, y, width, width);
853 // Draw plus
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;
908 else {
909 return 0;
913 public int getLineMarkerAreaWidth() {
914 return isLineMarkersShown() ? myLineMarkerAreaWidth : 0;
917 public void setLineNumberAreaWidth(int lineNumberAriaWidth) {
918 if (myLineNumberAreaWidth != lineNumberAriaWidth) {
919 myLineNumberAreaWidth = lineNumberAriaWidth;
920 fireResized();
924 public int getLineNumberAreaOffset() {
925 return 0;
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) {
958 continue;
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)) {
966 continue;
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;
973 return null;
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));
996 else {
997 ActiveGutterRenderer lineRenderer = getActiveRendererByMouseEvent(e);
998 if (lineRenderer != null) {
999 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
1002 else {
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);
1024 else {
1025 controller.cancelTooltip(GUTTER_TOOLTIP_GROUP);
1029 public void mouseClicked(MouseEvent e) {
1030 if (e.isPopupTrigger()) {
1031 invokePopup(e);
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) {
1042 return;
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);
1068 return null;
1071 public void mousePressed(MouseEvent e) {
1072 if (e.isPopupTrigger()) {
1073 invokePopup(e);
1074 myPopupInvokedOnPressed = true;
1075 } else if (UIUtil.isCloseClick(e)) {
1076 processClose(e);
1080 public void mouseReleased(final MouseEvent e) {
1081 if (e.isPopupTrigger()) {
1082 invokePopup(e);
1083 return;
1086 if (myPopupInvokedOnPressed) {
1087 myPopupInvokedOnPressed = false;
1088 return;
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(),
1101 e.getModifiers()));
1102 e.consume();
1103 repaint();
1105 else {
1106 ActiveGutterRenderer lineRenderer = getActiveRendererByMouseEvent(e);
1107 if (lineRenderer != null) {
1108 lineRenderer.doAction(myEditor, e);
1109 } else {
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();
1157 revalidateSizes();
1160 private void revalidateSizes() {
1161 myTextAnnotationGutters = new ArrayList<TextAnnotationGutterProvider>();
1162 myTextAnnotationGutterSizes = new TIntArrayList();
1163 updateSize();
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);
1184 if (list != null) {
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());
1198 else {
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,
1204 actionGroup);
1205 popupMenu.getComponent().show(this, e.getX(), e.getY());
1206 e.consume();
1209 else {
1210 ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN,
1211 (ActionGroup) actionManager.getAction("EditorGutterPopupMenu"));
1212 popupMenu.getComponent().show(this, e.getX(), e.getY());
1213 e.consume();
1218 public void mouseEntered(MouseEvent e) {
1221 public void mouseExited(MouseEvent e) {
1222 TooltipController.getInstance().cancelTooltip(GUTTER_TOOLTIP_GROUP);
1225 @Nullable
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;
1252 return result[0];
1255 @Nullable
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;
1273 static {
1274 DataFlavor[] flavors;
1275 try {
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];
1286 FLAVORS = flavors;
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) {
1296 try {
1297 myGutterDraggableObject = draggableObject;
1298 final MyDragSourceListener dragSourceListener = new MyDragSourceListener();
1299 dge.startDrag(DragSource.DefaultMoveNoDrop, new Transferable () {
1300 public DataFlavor[] getTransferDataFlavors() {
1301 return FLAVORS;
1304 public boolean isDataFlavorSupported(DataFlavor flavor) {
1305 DataFlavor[] flavors = getTransferDataFlavors();
1306 for (DataFlavor flavor1 : flavors) {
1307 if (flavor.equals(flavor1)) {
1308 return true;
1311 return false;
1314 public Object getTransferData(DataFlavor flavor) {
1315 return null;
1317 }, dragSourceListener);
1319 catch (InvalidDnDOperationException idoe) {
1320 // OK, can't dnd
1328 private class MyDragSourceListener implements DragSourceListener{
1329 public void dragEnter(DragSourceDragEvent dsde) {
1330 updateCursor(dsde);
1333 public void dragOver(DragSourceDragEvent dsde) {
1334 updateCursor(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);
1353 return;
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));
1383 return;
1387 dtde.rejectDrop();
1390 public void dragExit(DropTargetEvent dte) {}