blob: f773f842f32315fe06a2c13651e6d7fae58106fe [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created by IntelliJ IDEA.
* User: max
* Date: Jun 6, 2002
* Time: 8:37:03 PM
* To change template for new class use
* Code Style | Class Templates options (Tools | IDE Options).
*/
package com.intellij.openapi.editor.impl;
import com.intellij.codeInsight.daemon.GutterMark;
import com.intellij.codeInsight.hint.TooltipController;
import com.intellij.codeInsight.hint.TooltipGroup;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.dnd.*;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.customization.CustomActionsSchema;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.EditorFontType;
import com.intellij.openapi.editor.event.EditorMouseEventArea;
import com.intellij.openapi.editor.ex.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.HintHint;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Function;
import com.intellij.util.IconUtil;
import com.intellij.util.NullableFunction;
import com.intellij.util.SmartList;
import com.intellij.util.containers.HashMap;
import com.intellij.util.ui.UIUtil;
import gnu.trove.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.util.*;
import java.util.List;
class EditorGutterComponentImpl extends EditorGutterComponentEx implements MouseListener, MouseMotionListener {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorGutterComponentImpl");
private static final int START_ICON_AREA_WIDTH = 15;
private static final int FREE_PAINTERS_AREA_WIDTH = 5;
private static final int GAP_BETWEEN_ICONS = 3;
private static final TooltipGroup GUTTER_TOOLTIP_GROUP = new TooltipGroup("GUTTER_TOOLTIP_GROUP", 0);
private static final Color COLOR_F0F0 = new Color(0xF0F0F0);
public static final TIntFunction ID = new TIntFunction() {
@Override
public int execute(int value) {
return value;
}
};
private final EditorImpl myEditor;
private final FoldingAnchorsOverlayStrategy myAnchorsDisplayStrategy;
private int myLineMarkerAreaWidth = START_ICON_AREA_WIDTH + FREE_PAINTERS_AREA_WIDTH;
private int myIconsAreaWidth = START_ICON_AREA_WIDTH;
private int myLineNumberAreaWidth = 0;
private FoldRegion myActiveFoldRegion;
private boolean myPopupInvokedOnPressed;
private int myTextAnnotationGuttersSize = 0;
private int myTextAnnotationExtraSize = 0;
private TIntArrayList myTextAnnotationGutterSizes = new TIntArrayList();
private ArrayList<TextAnnotationGutterProvider> myTextAnnotationGutters = new ArrayList<TextAnnotationGutterProvider>();
private final Map<TextAnnotationGutterProvider, EditorGutterAction> myProviderToListener = new HashMap<TextAnnotationGutterProvider, EditorGutterAction>();
private static final int GAP_BETWEEN_ANNOTATIONS = 6;
private Color myBackgroundColor = null;
private String myLastGutterToolTip = null;
private int myLastPreferredHeight = -1;
@NotNull private TIntFunction myLineNumberConvertor;
private boolean myShowDefaultGutterPopup = true;
@SuppressWarnings("unchecked")
public EditorGutterComponentImpl(EditorImpl editor) {
myEditor = editor;
myLineNumberConvertor = ID;
if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
installDnD();
}
setOpaque(true);
myAnchorsDisplayStrategy = new FoldingAnchorsOverlayStrategy(editor);
}
@SuppressWarnings({"ConstantConditions"})
private void installDnD() {
DnDSupport.createBuilder(this)
.setBeanProvider(new Function<DnDActionInfo, DnDDragStartBean>() {
@Override
public DnDDragStartBean fun(DnDActionInfo info) {
final GutterMark renderer = getGutterRenderer(info.getPoint());
return renderer != null && (info.isCopy() || info.isMove()) ? new DnDDragStartBean(renderer) : null;
}
})
.setDropHandler(new DnDDropHandler() {
@Override
public void drop(DnDEvent e) {
final Object attachedObject = e.getAttachedObject();
if (attachedObject instanceof GutterIconRenderer) {
final GutterDraggableObject draggableObject = ((GutterIconRenderer)attachedObject).getDraggableObject();
if (draggableObject != null) {
final int line = convertPointToLineNumber(e.getPoint());
if (line != -1) {
draggableObject.copy(line, myEditor.getVirtualFile());
}
}
}
}
})
.setImageProvider(new NullableFunction<DnDActionInfo, DnDImage>() {
@Override
public DnDImage fun(DnDActionInfo info) {
return new DnDImage(IconUtil.toImage(getGutterRenderer(info.getPoint()).getIcon()));
}
})
.install();
}
private void fireResized() {
processComponentEvent(new ComponentEvent(this, ComponentEvent.COMPONENT_RESIZED));
}
@Override
public Dimension getPreferredSize() {
int w = getLineNumberAreaWidth() +
getAnnotationsAreaWidthEx() +
getLineMarkerAreaWidth() +
getFoldingAreaWidth();
return new Dimension(w, myLastPreferredHeight);
}
@Override
protected void setUI(ComponentUI newUI) {
super.setUI(newUI);
reinitSettings();
}
@Override
public void updateUI() {
super.updateUI();
reinitSettings();
}
public void reinitSettings() {
myBackgroundColor = null;
revalidateMarkup();
repaint();
}
@Override
public void paint(Graphics g) {
((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart();
try {
Rectangle clip = g.getClipBounds();
if (clip.height < 0) return;
final Graphics2D g2 = (Graphics2D)g;
final AffineTransform old = g2.getTransform();
if (isMirrored()) {
final AffineTransform transform = new AffineTransform(old);
transform.scale(-1, 1);
transform.translate(-getWidth(), 0);
g2.setTransform(transform);
}
UISettings.setupAntialiasing(g);
paintLineNumbersBackground(g, clip);
paintAnnotations(g, clip);
Object hint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
if (!UIUtil.isRetina()) g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
try {
int firstVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y - myEditor.getLineHeight())));
int lastVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y + clip.height + myEditor.getLineHeight())));
paintFoldingBackground(g, clip);
paintFoldingLines((Graphics2D)g, clip);
paintLineMarkers(g, clip, firstVisibleOffset, lastVisibleOffset);
paintFoldingTree(g, clip, firstVisibleOffset, lastVisibleOffset);
paintLineNumbers(g, clip);
}
finally {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint);
}
g2.setTransform(old);
}
finally {
((ApplicationImpl)ApplicationManager.getApplication()).editorPaintFinish();
}
}
private void processClose(final MouseEvent e) {
final IdeEventQueue queue = IdeEventQueue.getInstance();
// See IDEA-59553 for rationale on why this feature is disabled
//if (isLineNumbersShown()) {
// if (e.getX() >= getLineNumberAreaOffset() && getLineNumberAreaOffset() + getLineNumberAreaWidth() >= e.getX()) {
// queue.blockNextEvents(e);
// myEditor.getSettings().setLineNumbersShown(false);
// e.consume();
// return;
// }
//}
if (getGutterRenderer(e) != null) return;
int x = getAnnotationsAreaOffset();
for (int i = 0; i < myTextAnnotationGutters.size(); i++) {
final int size = myTextAnnotationGutterSizes.get(i);
if (x <= e.getX() && e.getX() <= x + size + GAP_BETWEEN_ANNOTATIONS) {
queue.blockNextEvents(e);
closeAllAnnotations();
e.consume();
break;
}
x += size + GAP_BETWEEN_ANNOTATIONS;
}
}
private void paintAnnotations(Graphics g, Rectangle clip) {
int x = getAnnotationsAreaOffset();
int w = getAnnotationsAreaWidthEx();
if (w == 0) return;
final Color background = myEditor.isInDistractionFreeMode() ? myEditor.getBackgroundColor() : getBackground();
paintBackground(g, clip, getAnnotationsAreaOffset(), w, background);
Color color = myEditor.getColorsScheme().getColor(EditorColors.ANNOTATIONS_COLOR);
g.setColor(color != null ? color : JBColor.blue);
g.setFont(myEditor.getColorsScheme().getFont(EditorFontType.PLAIN));
for (int i = 0; i < myTextAnnotationGutters.size(); i++) {
TextAnnotationGutterProvider gutterProvider = myTextAnnotationGutters.get(i);
int lineHeight = myEditor.getLineHeight();
int startLineNumber = clip.y / lineHeight;
int endLineNumber = (clip.y + clip.height) / lineHeight + 1;
int lastLine = myEditor.logicalToVisualPosition(
new LogicalPosition(endLineNumber(), 0))
.line;
endLineNumber = Math.min(endLineNumber, lastLine + 1);
if (startLineNumber >= endLineNumber) {
break;
}
for (int j = startLineNumber; j < endLineNumber; j++) {
int logLine = myEditor.visualToLogicalPosition(new VisualPosition(j, 0)).line;
String s = gutterProvider.getLineText(logLine, myEditor);
final EditorFontType style = gutterProvider.getStyle(logLine, myEditor);
final Color bg = gutterProvider.getBgColor(logLine, myEditor);
if (bg != null) {
g.setColor(bg);
g.fillRect(x, j * lineHeight, w, lineHeight);
}
g.setColor(myEditor.getColorsScheme().getColor(gutterProvider.getColor(logLine, myEditor)));
g.setFont(myEditor.getColorsScheme().getFont(style));
if (s != null) {
g.drawString(s, x, (j+1) * lineHeight - myEditor.getDescent());
}
}
x += myTextAnnotationGutterSizes.get(i);
}
if (!myEditor.isInDistractionFreeMode()) {
UIUtil.drawVDottedLine((Graphics2D)g, getAnnotationsAreaOffset() + w - 1, clip.y, clip.y + clip.height, null, getOutlineColor(false));
}
}
private void paintFoldingTree(Graphics g, Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
if (isFoldingOutlineShown()) {
doPaintFoldingTree((Graphics2D)g, clip, firstVisibleOffset, lastVisibleOffset);
}
else {
UIUtil.drawVDottedLine((Graphics2D)g, clip.x + clip.width - 1, clip.y, clip.y + clip.height, null, getOutlineColor(false));
}
}
private void paintLineMarkers(Graphics g, Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
if (isLineMarkersShown()) {
paintBackground(g, clip, getLineMarkerAreaOffset(), getLineMarkerAreaWidth());
paintGutterRenderers(g, firstVisibleOffset, lastVisibleOffset);
}
}
private void paintBackground(final Graphics g, final Rectangle clip, final int x, final int width) {
paintBackground(g, clip, x, width, getBackground());
}
private void paintBackground(final Graphics g,
final Rectangle clip,
final int x,
final int width,
Color background) {
g.setColor(background);
g.fillRect(x, clip.y, width, clip.height);
paintCaretRowBackground(g, x, width);
}
private void paintCaretRowBackground(final Graphics g, final int x, final int width) {
final VisualPosition visCaret = myEditor.getCaretModel().getVisualPosition();
Color caretRowColor = myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
if (caretRowColor != null) {
g.setColor(caretRowColor);
final Point caretPoint = myEditor.visualPositionToXY(visCaret);
g.fillRect(x, caretPoint.y, width, myEditor.getLineHeight());
}
}
private void paintLineNumbers(Graphics g, Rectangle clip) {
if (isLineNumbersShown()) {
int x = getLineNumberAreaOffset() + getLineNumberAreaWidth() - 2;
UIUtil.drawVDottedLine((Graphics2D)g, x, clip.y, clip.y + clip.height, null, getOutlineColor(false));
doPaintLineNumbers(g, clip);
}
}
private void paintLineNumbersBackground(Graphics g, Rectangle clip) {
if (isLineNumbersShown()) {
paintBackground(g, clip, getLineNumberAreaOffset(), getLineNumberAreaWidth());
}
}
@Override
public Color getBackground() {
if (myBackgroundColor == null) {
EditorColorsScheme colorsScheme = myEditor.getColorsScheme();
boolean distractionMode = myEditor.isInDistractionFreeMode();
Color color = distractionMode ? colorsScheme.getDefaultBackground() : colorsScheme.getColor(EditorColors.GUTTER_BACKGROUND);
myBackgroundColor = color == null ? COLOR_F0F0 : color;
}
return myBackgroundColor;
}
private void doPaintLineNumbers(Graphics g, Rectangle clip) {
if (!isLineNumbersShown()) {
return;
}
int lineHeight = myEditor.getLineHeight();
int startLineNumber = clip.y / lineHeight;
int endLineNumber = (clip.y + clip.height) / lineHeight + 1;
int lastLine = myEditor.logicalToVisualPosition(
new LogicalPosition(endLineNumber(), 0))
.line;
endLineNumber = Math.min(endLineNumber, lastLine + 1);
if (startLineNumber >= endLineNumber) {
return;
}
Color color = myEditor.getColorsScheme().getColor(EditorColors.LINE_NUMBERS_COLOR);
g.setColor(color != null ? color : JBColor.blue);
g.setFont(myEditor.getColorsScheme().getFont(EditorFontType.PLAIN));
Graphics2D g2 = (Graphics2D)g;
AffineTransform old = g2.getTransform();
if (isMirrored()) {
AffineTransform originalTransform = new AffineTransform(old);
originalTransform.scale(-1, 1);
originalTransform.translate(-getLineNumberAreaWidth() + 2, 0);
g2.setTransform(originalTransform);
}
for (int i = startLineNumber; i < endLineNumber; i++) {
LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(new VisualPosition(i, 0));
if (logicalPosition.softWrapLinesOnCurrentLogicalLine > 0) {
continue;
}
int logLine = myLineNumberConvertor.execute(logicalPosition.line);
if (logLine >= 0) {
String s = String.valueOf(logLine + 1);
g.drawString(s,
getLineNumberAreaOffset() + getLineNumberAreaWidth() -
myEditor.getFontMetrics(Font.PLAIN).stringWidth(s) -
4,
(i + 1) * lineHeight - myEditor.getDescent());
}
}
g2.setTransform(old);
}
private int endLineNumber() {
return Math.max(0, myEditor.getDocument().getLineCount() - 1);
}
private interface RangeHighlighterProcessor {
void process(@NotNull RangeHighlighter highlighter);
}
private void processRangeHighlighters(int startOffset, int endOffset, @NotNull RangeHighlighterProcessor processor) {
Document document = myEditor.getDocument();
final MarkupModelEx docMarkup = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myEditor.getProject(), true);
// we limit highlighters to process to between line starting at startOffset and line ending at endOffset
DisposableIterator<RangeHighlighterEx> docHighlighters = docMarkup.overlappingIterator(startOffset, endOffset);
DisposableIterator<RangeHighlighterEx> editorHighlighters = myEditor.getMarkupModel().overlappingIterator(startOffset, endOffset);
try {
RangeHighlighterEx lastDocHighlighter = null;
RangeHighlighterEx lastEditorHighlighter = null;
while (true) {
if (lastDocHighlighter == null && docHighlighters.hasNext()) {
lastDocHighlighter = docHighlighters.next();
if (!lastDocHighlighter.isValid() || lastDocHighlighter.getAffectedAreaStartOffset() > endOffset) {
lastDocHighlighter = null;
continue;
}
if (lastDocHighlighter.getAffectedAreaEndOffset() < startOffset) {
lastDocHighlighter = null;
continue;
}
}
if (lastEditorHighlighter == null && editorHighlighters.hasNext()) {
lastEditorHighlighter = editorHighlighters.next();
if (!lastEditorHighlighter.isValid() || lastEditorHighlighter.getAffectedAreaStartOffset() > endOffset) {
lastEditorHighlighter = null;
continue;
}
if (lastEditorHighlighter.getAffectedAreaEndOffset() < startOffset) {
lastEditorHighlighter = null;
continue;
}
}
if (lastDocHighlighter == null && lastEditorHighlighter == null) return;
final RangeHighlighterEx lowerHighlighter;
if (less(lastDocHighlighter, lastEditorHighlighter)) {
lowerHighlighter = lastDocHighlighter;
lastDocHighlighter = null;
}
else {
lowerHighlighter = lastEditorHighlighter;
lastEditorHighlighter = null;
}
assert lowerHighlighter != null;
if (!lowerHighlighter.isValid()) continue;
int startLineIndex = lowerHighlighter.getDocument().getLineNumber(startOffset);
if (startLineIndex < 0 || startLineIndex >= document.getLineCount()) continue;
int endLineIndex = lowerHighlighter.getDocument().getLineNumber(endOffset);
if (endLineIndex < 0 || endLineIndex >= document.getLineCount()) continue;
if (lowerHighlighter.getEditorFilter().avaliableIn(myEditor)) {
processor.process(lowerHighlighter);
}
}
}
finally {
docHighlighters.dispose();
editorHighlighters.dispose();
}
}
private static boolean less(RangeHighlighter h1, RangeHighlighter h2) {
return h1 != null && (h2 == null || h1.getStartOffset() < h2.getStartOffset());
}
@Override
public void revalidateMarkup() {
updateSize();
}
public void updateSize() {
int prevHash = sizeHash();
updateSizeInner();
if (prevHash != sizeHash()) {
fireResized();
}
repaint();
}
private void updateSizeInner() {
myLastPreferredHeight = myEditor.getPreferredHeight();
calcIconAreaWidth();
calcAnnotationsSize();
calcAnnotationExtraSize();
}
private int sizeHash() {
int result = myLastPreferredHeight;
result = 31 * result + myLineMarkerAreaWidth;
result = 31 * result + myTextAnnotationGuttersSize;
result = 31 * result + myTextAnnotationExtraSize;
return result;
}
private void calcAnnotationsSize() {
myTextAnnotationGuttersSize = 0;
final FontMetrics fontMetrics = myEditor.getFontMetrics(Font.PLAIN);
final int lineCount = myEditor.getDocument().getLineCount();
for (int j = 0; j < myTextAnnotationGutters.size(); j++) {
TextAnnotationGutterProvider gutterProvider = myTextAnnotationGutters.get(j);
int gutterSize = 0;
for (int i = 0; i < lineCount; i++) {
final String lineText = gutterProvider.getLineText(i, myEditor);
if (lineText != null) {
gutterSize = Math.max(gutterSize, fontMetrics.stringWidth(lineText));
}
}
if (gutterSize > 0) gutterSize += GAP_BETWEEN_ANNOTATIONS;
myTextAnnotationGutterSizes.set(j, gutterSize);
myTextAnnotationGuttersSize += gutterSize;
}
}
private void calcAnnotationExtraSize() {
myTextAnnotationExtraSize = 0;
if (!myEditor.isInDistractionFreeMode() || isMirrored()) return;
Window frame = SwingUtilities.getWindowAncestor(myEditor.getComponent());
if (frame == null) return;
EditorSettings settings = myEditor.getSettings();
int rightMargin = settings.getRightMargin(myEditor.getProject());
if (rightMargin <= 0) return;
JComponent editorComponent = myEditor.getComponent();
RelativePoint point = new RelativePoint(editorComponent, new Point(0, 0));
Point editorLocationInWindow = point.getPoint(frame);
int editorLocationX = (int)editorLocationInWindow.getX();
int rightMarginX = rightMargin * EditorUtil.getSpaceWidth(Font.PLAIN, myEditor) + editorLocationX;
int width = editorLocationX + editorComponent.getWidth();
if (rightMarginX < width && editorLocationX < width - rightMarginX) {
int centeredSize = (width - rightMarginX - editorLocationX) / 2 - (myLineMarkerAreaWidth + myLineNumberAreaWidth);
myTextAnnotationExtraSize = Math.max(0, centeredSize - myTextAnnotationGuttersSize);
}
}
private TIntObjectHashMap<List<GutterMark>> myLineToGutterRenderers;
private void calcIconAreaWidth() {
myLineToGutterRenderers = new TIntObjectHashMap<List<GutterMark>>();
processRangeHighlighters(0, myEditor.getDocument().getTextLength(), new RangeHighlighterProcessor() {
@Override
public void process(@NotNull RangeHighlighter highlighter) {
GutterMark renderer = highlighter.getGutterIconRenderer();
if (renderer == null) {
return;
}
if (myEditor.getFoldingModel().isOffsetCollapsed(highlighter.getStartOffset())) {
return;
}
VisualPosition visualPosition = myEditor.offsetToVisualPosition(highlighter.getStartOffset());
int line = EditorUtil.calcSurroundingRange(myEditor, visualPosition, visualPosition).getFirst().line;
List<GutterMark> renderers = myLineToGutterRenderers.get(line);
if (renderers == null) {
renderers = new SmartList<GutterMark>();
myLineToGutterRenderers.put(line, renderers);
}
if (renderers.size() < 5) { // Don't allow more than 5 icons per line
renderers.add(renderer);
}
}
});
myIconsAreaWidth = START_ICON_AREA_WIDTH;
myLineToGutterRenderers.forEachValue(new TObjectProcedure<List<GutterMark>>() {
@Override
public boolean execute(List<GutterMark> renderers) {
int width = 1;
for (int i = 0; i < renderers.size(); i++) {
GutterMark renderer = renderers.get(i);
width += renderer.getIcon().getIconWidth();
if (i > 0) width += GAP_BETWEEN_ICONS;
}
if (myIconsAreaWidth < width) {
myIconsAreaWidth = width;
}
return true;
}
});
myLineMarkerAreaWidth = myIconsAreaWidth + FREE_PAINTERS_AREA_WIDTH +
// if folding outline is shown, there will be enough place for change markers, otherwise add place for it.
(isFoldingOutlineShown() ? 0 : getFoldingAnchorWidth() / 2);
}
private void paintGutterRenderers(final Graphics g, int firstVisibleOffset, int lastVisibleOffset) {
Graphics2D g2 = (Graphics2D)g;
Object hint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
try {
processRangeHighlighters(firstVisibleOffset, lastVisibleOffset, new RangeHighlighterProcessor() {
@Override
public void process(@NotNull RangeHighlighter highlighter) {
paintLineMarkerRenderer(highlighter, g);
}
});
}
finally {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint);
}
int firstVisibleLine = myEditor.getDocument().getLineNumber(firstVisibleOffset);
int lastVisibleLine = myEditor.getDocument().getLineNumber(lastVisibleOffset);
paintIcons(firstVisibleLine, lastVisibleLine, g);
}
private void paintIcons(final int firstVisibleLine, final int lastVisibleLine, final Graphics g) {
myLineToGutterRenderers.forEachKey(new TIntProcedure() {
@Override
public boolean execute(int line) {
if (firstVisibleLine > line || lastVisibleLine < line) return true;
if (isLineCollapsed(line)) return true;
List<GutterMark> renderers = myLineToGutterRenderers.get(line);
paintIconRow(line, renderers, g);
return true;
}
});
}
private boolean isLineCollapsed(final int line) {
int startOffset = myEditor.getDocument().getLineStartOffset(line);
final FoldRegion region = myEditor.getFoldingModel().getCollapsedRegionAtOffset(startOffset);
return region != null && region.getEndOffset() >= myEditor.getDocument().getLineEndOffset(line);
}
private void paintIconRow(int line, List<GutterMark> row, final Graphics g) {
processIconsRow(line, row, new LineGutterIconRendererProcessor() {
@Override
public void process(int x, int y, GutterMark renderer) {
Icon icon = renderer.getIcon();
icon.paintIcon(EditorGutterComponentImpl.this, g, x, y);
}
});
}
private void paintLineMarkerRenderer(RangeHighlighter highlighter, Graphics g) {
Rectangle rectangle = getLineRendererRectangle(highlighter);
if (rectangle != null) {
final LineMarkerRenderer lineMarkerRenderer = highlighter.getLineMarkerRenderer();
assert lineMarkerRenderer != null;
lineMarkerRenderer.paint(myEditor, g, rectangle);
}
}
@Nullable
private Rectangle getLineRendererRectangle(RangeHighlighter highlighter) {
LineMarkerRenderer renderer = highlighter.getLineMarkerRenderer();
if (renderer == null) return null;
int startOffset = highlighter.getStartOffset();
int endOffset = highlighter.getEndOffset();
if (myEditor.getFoldingModel().isOffsetCollapsed(startOffset) &&
myEditor.getFoldingModel().isOffsetCollapsed(endOffset)) {
return null;
}
int startY = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(startOffset)).y;
// top edge of the last line of the highlighted area
int endY = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(endOffset)).y;
// => add one line height to make height correct (bottom edge of the highlighted area)
DocumentEx document = myEditor.getDocument();
if (document.getLineStartOffset(document.getLineNumber(endOffset)) != endOffset) {
// but if the highlighter ends with the end of line, its line number is the next line, but that line should not be highlighted
endY += myEditor.getLineHeight();
}
int height = endY - startY;
int w = FREE_PAINTERS_AREA_WIDTH;
int x = getLineMarkerAreaOffset() + myIconsAreaWidth;
return new Rectangle(x, startY, w, height);
}
private interface LineGutterIconRendererProcessor {
void process(int x, int y, GutterMark renderer);
}
private void processIconsRow(int line, List<GutterMark> row, LineGutterIconRendererProcessor processor) {
int middleCount = 0;
int middleSize = 0;
int x = getLineMarkerAreaOffset() + 1;
final int y = myEditor.logicalPositionToXY(new LogicalPosition(line, 0)).y;
for (GutterMark r : row) {
final GutterIconRenderer.Alignment alignment = ((GutterIconRenderer)r).getAlignment();
final Icon icon = r.getIcon();
if (alignment == GutterIconRenderer.Alignment.LEFT) {
processor.process(x, y + getTextAlignmentShift(icon), r);
x += icon.getIconWidth() + GAP_BETWEEN_ICONS;
}
else {
if (alignment == GutterIconRenderer.Alignment.CENTER) {
middleCount++;
middleSize += icon.getIconWidth() + GAP_BETWEEN_ICONS;
}
}
}
final int leftSize = x - getLineMarkerAreaOffset();
x = getLineMarkerAreaOffset() + myIconsAreaWidth;
for (GutterMark r : row) {
if (((GutterIconRenderer)r).getAlignment() == GutterIconRenderer.Alignment.RIGHT) {
Icon icon = r.getIcon();
x -= icon.getIconWidth();
processor.process(x, y + getTextAlignmentShift(icon), r);
x -= GAP_BETWEEN_ICONS;
}
}
int rightSize = myIconsAreaWidth + getLineMarkerAreaOffset() - x;
if (middleCount > 0) {
middleSize -= GAP_BETWEEN_ICONS;
x = getLineMarkerAreaOffset() + leftSize + (myIconsAreaWidth - leftSize - rightSize - middleSize) / 2;
for (GutterMark r : row) {
if (((GutterIconRenderer)r).getAlignment() == GutterIconRenderer.Alignment.CENTER) {
Icon icon = r.getIcon();
processor.process(x, y + getTextAlignmentShift(icon), r);
x += icon.getIconWidth() + GAP_BETWEEN_ICONS;
}
}
}
}
private int getTextAlignmentShift(Icon icon) {
return (myEditor.getLineHeight() - icon.getIconHeight()) /2;
}
@Override
public Color getOutlineColor(boolean isActive) {
ColorKey key = isActive ? EditorColors.SELECTED_TEARLINE_COLOR : EditorColors.TEARLINE_COLOR;
Color color = myEditor.getColorsScheme().getColor(key);
return color != null ? color : JBColor.black;
}
@Override
public void registerTextAnnotation(@NotNull TextAnnotationGutterProvider provider) {
myTextAnnotationGutters.add(provider);
myTextAnnotationGutterSizes.add(0);
updateSize();
}
@Override
public void registerTextAnnotation(@NotNull TextAnnotationGutterProvider provider, @NotNull EditorGutterAction action) {
myTextAnnotationGutters.add(provider);
myProviderToListener.put(provider, action);
myTextAnnotationGutterSizes.add(0);
updateSize();
}
private void doPaintFoldingTree(final Graphics2D g, final Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) {
final int anchorX = getFoldingAreaOffset();
final int width = getFoldingAnchorWidth();
Collection<DisplayedFoldingAnchor> anchorsToDisplay =
myAnchorsDisplayStrategy.getAnchorsToDisplay(firstVisibleOffset, lastVisibleOffset, myActiveFoldRegion);
for (DisplayedFoldingAnchor anchor : anchorsToDisplay) {
drawAnchor(width, clip, g, anchorX, anchor.visualLine, anchor.type, anchor.foldRegion == myActiveFoldRegion);
}
}
private void paintFoldingBackground(Graphics g, Rectangle clip) {
int lineX = getWhitespaceSeparatorOffset();
paintBackground(g, clip, getFoldingAreaOffset(), getFoldingAreaWidth());
g.setColor(myEditor.getBackgroundColor());
g.fillRect(lineX, clip.y, getFoldingAreaWidth(), clip.height);
paintCaretRowBackground(g, lineX, getFoldingAnchorWidth());
}
private void paintFoldingLines(final Graphics2D g, final Rectangle clip) {
if (!isFoldingOutlineShown()) return;
UIUtil.drawVDottedLine(g, getWhitespaceSeparatorOffset(), clip.y, clip.y + clip.height, null, getOutlineColor(false));
final int anchorX = getFoldingAreaOffset();
final int width = getFoldingAnchorWidth();
if (myActiveFoldRegion != null && myActiveFoldRegion.isExpanded() && myActiveFoldRegion.isValid()) {
int foldStart = myEditor.offsetToVisualLine(myActiveFoldRegion.getStartOffset());
int foldEnd = myEditor.offsetToVisualLine(getEndOffset(myActiveFoldRegion));
int startY = myEditor.visibleLineToY(foldStart + 1) - myEditor.getDescent();
int endY = myEditor.visibleLineToY(foldEnd) + myEditor.getLineHeight() -
myEditor.getDescent();
if (startY <= clip.y + clip.height && endY + 1 + myEditor.getDescent() >= clip.y) {
int lineX = anchorX + width / 2;
g.setColor(getOutlineColor(true));
UIUtil.drawLine(g, lineX, startY, lineX, endY);
}
}
}
@Override
public int getWhitespaceSeparatorOffset() {
return getFoldingAreaOffset() + getFoldingAnchorWidth() / 2;
}
public void setActiveFoldRegion(FoldRegion activeFoldRegion) {
if (myActiveFoldRegion != activeFoldRegion) {
myActiveFoldRegion = activeFoldRegion;
repaint();
}
}
public int getHeadCenterY(FoldRegion foldRange) {
int width = getFoldingAnchorWidth();
int foldStart = myEditor.offsetToVisualLine(foldRange.getStartOffset());
return myEditor.visibleLineToY(foldStart) + myEditor.getLineHeight() - myEditor.getDescent() - width / 2;
}
private void drawAnchor(int width, Rectangle clip, Graphics2D g, int anchorX, int visualLine,
DisplayedFoldingAnchor.Type type, boolean active) {
int height = width + 2;
int y;
switch (type) {
case COLLAPSED:
y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent() - width;
if (y <= clip.y + clip.height && y + height >= clip.y) {
drawSquareWithPlus(g, anchorX, y, width, active);
}
break;
case EXPANDED_TOP:
y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent() - width;
if (y <= clip.y + clip.height && y + height >= clip.y) {
drawDirectedBox(g, anchorX, y, width, height, width - 2, active);
}
break;
case EXPANDED_BOTTOM:
y = myEditor.visibleLineToY(visualLine) + myEditor.getLineHeight() - myEditor.getDescent();
if (y - height <= clip.y + clip.height && y >= clip.y) {
drawDirectedBox(g, anchorX, y, width, -height, -width + 2, active);
}
break;
}
}
private int getEndOffset(FoldRegion foldRange) {
LOG.assertTrue(foldRange.isValid(), foldRange);
FoldingGroup group = foldRange.getGroup();
return group == null ? foldRange.getEndOffset() : myEditor.getFoldingModel().getEndOffset(group);
}
private void drawDirectedBox(Graphics2D g,
int anchorX,
int y,
int width,
int height,
int baseHeight,
boolean active) {
Object antialiasing = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
if (SystemInfo.isMac && SystemInfo.JAVA_VERSION.startsWith("1.4.1") || UIUtil.isRetina()) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
try {
int[] xPoints = {anchorX, anchorX + width, anchorX + width, anchorX + width / 2, anchorX};
int[] yPoints = {y, y, y + baseHeight, y + height, y + baseHeight};
g.setColor(myEditor.getBackgroundColor());
g.fillPolygon(xPoints, yPoints, 5);
g.setColor(getOutlineColor(active));
g.drawPolygon(xPoints, yPoints, 5);
//Minus
int minusHeight = y + baseHeight / 2 + (height - baseHeight) / 4;
UIUtil.drawLine(g, anchorX + 2, minusHeight, anchorX + width - 2, minusHeight);
}
finally {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
}
}
private void drawSquareWithPlus(Graphics2D g,
int anchorX,
int y,
int width,
boolean active) {
drawSquareWithMinus(g, anchorX, y, width, active);
UIUtil.drawLine(g, anchorX + width / 2, y + 2, anchorX + width / 2, y + width - 2);
}
@SuppressWarnings("SuspiciousNameCombination")
private void drawSquareWithMinus(Graphics2D g,
int anchorX,
int y,
int width,
boolean active) {
g.setColor(myEditor.getBackgroundColor());
g.fillRect(anchorX, y, width, width);
g.setColor(getOutlineColor(active));
g.drawRect(anchorX, y, width, width);
// Draw plus
if (!active) g.setColor(getOutlineColor(true));
UIUtil.drawLine(g, anchorX + 2, y + width / 2, anchorX + width - 2, y + width / 2);
}
private int getFoldingAnchorWidth() {
return Math.min(4, myEditor.getLineHeight() / 2 - 2) * 2;
}
public int getFoldingAreaOffset() {
return getLineMarkerAreaOffset() +
getLineMarkerAreaWidth();
}
public int getFoldingAreaWidth() {
return isFoldingOutlineShown()
? getFoldingAnchorWidth() + 2
: isLineNumbersShown() ? getFoldingAnchorWidth() / 2 : 0;
}
@Override
public boolean isLineMarkersShown() {
return myEditor.getSettings().isLineMarkerAreaShown();
}
public boolean isLineNumbersShown() {
return myEditor.getSettings().isLineNumbersShown();
}
@Override
public boolean isAnnotationsShown() {
return !myTextAnnotationGutters.isEmpty();
}
@Override
public boolean isFoldingOutlineShown() {
return myEditor.getSettings().isFoldingOutlineShown() &&
myEditor.getFoldingModel().isFoldingEnabled() &&
!myEditor.isInPresentationMode();
}
public int getLineNumberAreaWidth() {
return isLineNumbersShown() ? myLineNumberAreaWidth : 0;
}
public int getLineMarkerAreaWidth() {
return isLineMarkersShown() ? myLineMarkerAreaWidth : 0;
}
public void setLineNumberAreaWidth(@NotNull TIntFunction calculator) {
int maxLineNumber = 0;
for (int i = endLineNumber(); i >= 0; i--) {
int number = myLineNumberConvertor.execute(i);
if (number >= 0) {
maxLineNumber = number;
break;
}
}
final int lineNumberAreaWidth = calculator.execute(maxLineNumber);
if (myLineNumberAreaWidth != lineNumberAreaWidth) {
myLineNumberAreaWidth = lineNumberAreaWidth;
fireResized();
}
}
@Nullable
public EditorMouseEventArea getEditorMouseAreaByOffset(int offset) {
int x = offset - getLineNumberAreaOffset();
if (x >= 0 && (x -= getLineNumberAreaWidth()) < 0) {
return EditorMouseEventArea.LINE_NUMBERS_AREA;
}
if (x >= 0 && (x -= getAnnotationsAreaWidth()) < 0) {
return EditorMouseEventArea.ANNOTATIONS_AREA;
}
if ((x -= myTextAnnotationExtraSize * 3 / 5) >= 0 && (x -= myTextAnnotationExtraSize * 2 / 5) < 0) {
return EditorMouseEventArea.LINE_MARKERS_AREA;
}
if (x >= 0 && (x -= getLineMarkerAreaWidth()) < 0) {
return EditorMouseEventArea.LINE_MARKERS_AREA;
}
if (x >= 0 && (x -= getFoldingAreaWidth()) < 0) {
return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
}
return null;
}
public static int getLineNumberAreaOffset() {
return 0;
}
public int getAnnotationsAreaOffset() {
return getLineNumberAreaOffset() + getLineNumberAreaWidth();
}
public int getAnnotationsAreaWidth() {
return myTextAnnotationGuttersSize;
}
public int getAnnotationsAreaWidthEx() {
return myTextAnnotationGuttersSize + myTextAnnotationExtraSize;
}
@Override
public int getLineMarkerAreaOffset() {
return getAnnotationsAreaOffset() + getAnnotationsAreaWidthEx();
}
@Override
public int getIconsAreaWidth() {
return myIconsAreaWidth;
}
private boolean isMirrored() {
return myEditor.getVerticalScrollbarOrientation() != EditorEx.VERTICAL_SCROLLBAR_RIGHT;
}
@Nullable
@Override
public FoldRegion findFoldingAnchorAt(int x, int y) {
if (!myEditor.getSettings().isFoldingOutlineShown()) return null;
int anchorX = getFoldingAreaOffset();
int anchorWidth = getFoldingAnchorWidth();
int neighbourhoodStartOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y - myEditor.getLineHeight())));
int neighbourhoodEndOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y + myEditor.getLineHeight())));
Collection<DisplayedFoldingAnchor> displayedAnchors = myAnchorsDisplayStrategy.getAnchorsToDisplay(neighbourhoodStartOffset, neighbourhoodEndOffset, null);
for (DisplayedFoldingAnchor anchor : displayedAnchors) {
if (rectangleByFoldOffset(anchor.visualLine, anchorWidth, anchorX).contains(x, y)) return anchor.foldRegion;
}
return null;
}
@SuppressWarnings("SuspiciousNameCombination")
private Rectangle rectangleByFoldOffset(int foldStart, int anchorWidth, int anchorX) {
int anchorY = myEditor.visibleLineToY(foldStart) + myEditor.getLineHeight() -
myEditor.getDescent() - anchorWidth;
return new Rectangle(anchorX, anchorY, anchorWidth, anchorWidth);
}
@Override
public void mouseDragged(MouseEvent e) {
TooltipController.getInstance().cancelTooltips();
}
@Override
public void mouseMoved(final MouseEvent e) {
String toolTip = null;
final GutterIconRenderer renderer = getGutterRenderer(e);
TooltipController controller = TooltipController.getInstance();
if (renderer != null) {
toolTip = renderer.getTooltipText();
if (renderer.isNavigateAction()) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
else {
ActiveGutterRenderer lineRenderer = getActiveRendererByMouseEvent(e);
if (lineRenderer != null) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
else {
TextAnnotationGutterProvider provider = getProviderAtPoint(e.getPoint());
if (provider != null) {
final int line = getLineNumAtPoint(e.getPoint());
toolTip = provider.getToolTip(line, myEditor);
if (!Comparing.equal(toolTip, myLastGutterToolTip)) {
controller.cancelTooltip(GUTTER_TOOLTIP_GROUP, e, true);
myLastGutterToolTip = toolTip;
}
if (myProviderToListener.containsKey(provider)) {
final EditorGutterAction action = myProviderToListener.get(provider);
if (action != null) {
setCursor(action.getCursor(line));
}
}
}
}
}
if (toolTip != null && !toolTip.isEmpty()) {
final Ref<Point> t = new Ref<Point>(e.getPoint());
int line = EditorUtil.yPositionToLogicalLine(myEditor, e);
List<GutterMark> row = myLineToGutterRenderers.get(line);
Balloon.Position ballPosition = Balloon.Position.atRight;
if (row != null) {
final TreeMap<Integer, GutterMark> xPos = new TreeMap<Integer, GutterMark>();
final int[] currentPos = {0};
processIconsRow(line, row, new LineGutterIconRendererProcessor() {
@Override
public void process(int x, int y, GutterMark r) {
xPos.put(x, r);
if (renderer == r && r != null) {
currentPos[0] = x;
Icon icon = r.getIcon();
t.set(new Point(x + icon.getIconWidth() / 2, y + icon.getIconHeight() / 2));
}
}
});
List<Integer> xx = new ArrayList<Integer>(xPos.keySet());
int posIndex = xx.indexOf(currentPos[0]);
if (xPos.size() > 1 && posIndex == 0) {
ballPosition = Balloon.Position.below;
}
}
RelativePoint showPoint = new RelativePoint(this, t.get());
controller.showTooltipByMouseMove(myEditor, showPoint, ((EditorMarkupModel)myEditor.getMarkupModel()).getErrorStripTooltipRendererProvider().calcTooltipRenderer(toolTip), false, GUTTER_TOOLTIP_GROUP,
new HintHint(this, t.get()).setAwtTooltip(true).setPreferredPosition(ballPosition));
}
else {
controller.cancelTooltip(GUTTER_TOOLTIP_GROUP, e, false);
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger()) {
invokePopup(e);
}
}
private void fireEventToTextAnnotationListeners(final MouseEvent e) {
if (myEditor.getMouseEventArea(e) == EditorMouseEventArea.ANNOTATIONS_AREA) {
final Point clickPoint = e.getPoint();
final TextAnnotationGutterProvider provider = getProviderAtPoint(clickPoint);
if (provider == null) {
return;
}
if (myProviderToListener.containsKey(provider)) {
int line = getLineNumAtPoint(clickPoint);
if (line >= 0 && line < myEditor.getDocument().getLineCount() && UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED)) {
myProviderToListener.get(provider).doAction(line);
}
}
}
}
private int getLineNumAtPoint(final Point clickPoint) {
return EditorUtil.yPositionToLogicalLine(myEditor, clickPoint);
}
@Nullable
private TextAnnotationGutterProvider getProviderAtPoint(final Point clickPoint) {
int current = getAnnotationsAreaOffset();
if (clickPoint.x < current) return null;
for (int i = 0; i < myTextAnnotationGutterSizes.size(); i++) {
current += myTextAnnotationGutterSizes.get(i);
if (clickPoint.x <= current) return myTextAnnotationGutters.get(i);
}
return null;
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
invokePopup(e);
myPopupInvokedOnPressed = true;
} else if (UIUtil.isCloseClick(e)) {
processClose(e);
}
}
@Override
public void mouseReleased(final MouseEvent e) {
if (e.isPopupTrigger()) {
invokePopup(e);
return;
}
if (myPopupInvokedOnPressed) {
myPopupInvokedOnPressed = false;
return;
}
GutterIconRenderer renderer = getGutterRenderer(e);
AnAction clickAction = null;
if (renderer != null && e.getButton() < 4) {
clickAction = (InputEvent.BUTTON2_MASK & e.getModifiers()) > 0
? renderer.getMiddleButtonClickAction()
: renderer.getClickAction();
}
if (clickAction != null) {
clickAction.actionPerformed(new AnActionEvent(e, myEditor.getDataContext(), "ICON_NAVIGATION", clickAction.getTemplatePresentation(),
ActionManager.getInstance(),
e.getModifiers()));
e.consume();
repaint();
}
else {
ActiveGutterRenderer lineRenderer = getActiveRendererByMouseEvent(e);
if (lineRenderer != null) {
lineRenderer.doAction(myEditor, e);
} else {
fireEventToTextAnnotationListeners(e);
}
}
}
@Nullable
private ActiveGutterRenderer getActiveRendererByMouseEvent(final MouseEvent e) {
if (findFoldingAnchorAt(e.getX(), e.getY()) != null) {
return null;
}
if (e.isConsumed() || e.getX() > getWhitespaceSeparatorOffset()) {
return null;
}
final ActiveGutterRenderer[] gutterRenderer = {null};
Rectangle clip = myEditor.getScrollingModel().getVisibleArea();
int firstVisibleOffset = myEditor.logicalPositionToOffset(
myEditor.xyToLogicalPosition(new Point(0, clip.y - myEditor.getLineHeight())));
int lastVisibleOffset = myEditor.logicalPositionToOffset(
myEditor.xyToLogicalPosition(new Point(0, clip.y + clip.height + myEditor.getLineHeight())));
processRangeHighlighters(firstVisibleOffset, lastVisibleOffset, new RangeHighlighterProcessor() {
@Override
public void process(@NotNull RangeHighlighter highlighter) {
if (gutterRenderer[0] != null) return;
Rectangle rectangle = getLineRendererRectangle(highlighter);
if (rectangle == null) return;
int startY = rectangle.y;
int endY = startY + rectangle.height;
if (startY == endY) {
endY += myEditor.getLineHeight();
}
if (startY < e.getY() && e.getY() <= endY) {
final LineMarkerRenderer renderer = highlighter.getLineMarkerRenderer();
if (renderer instanceof ActiveGutterRenderer && ((ActiveGutterRenderer)renderer).canDoAction(e)) {
gutterRenderer[0] = (ActiveGutterRenderer)renderer;
}
}
}
});
return gutterRenderer[0];
}
@Override
public void closeAllAnnotations() {
for (TextAnnotationGutterProvider provider : myTextAnnotationGutters) {
provider.gutterClosed();
}
revalidateSizes();
}
private void revalidateSizes() {
myTextAnnotationGutters = new ArrayList<TextAnnotationGutterProvider>();
myTextAnnotationGutterSizes = new TIntArrayList();
updateSize();
}
private class CloseAnnotationsAction extends DumbAwareAction {
public CloseAnnotationsAction() {
super(EditorBundle.message("close.editor.annotations.action.name"));
}
@Override
public void actionPerformed(AnActionEvent e) {
closeAllAnnotations();
}
}
@Override
@Nullable
public Point getPoint(final GutterIconRenderer renderer) {
final Ref<Point> result = Ref.create();
for (int line : myLineToGutterRenderers.keys()) {
processIconsRow(line, myLineToGutterRenderers.get(line), new LineGutterIconRendererProcessor() {
@Override
public void process(int x, int y, GutterMark r) {
if (result.isNull() && r.equals(renderer)) {
result.set(new Point(x, y));
}
}
});
if (!result.isNull()) {
return result.get();
}
}
return null;
}
@Override
public void setLineNumberConvertor(@NotNull TIntFunction lineNumberConvertor) {
myLineNumberConvertor = lineNumberConvertor;
}
@Override
public void setShowDefaultGutterPopup(boolean show) {
myShowDefaultGutterPopup = show;
}
private void invokePopup(MouseEvent e) {
final ActionManager actionManager = ActionManager.getInstance();
if (myEditor.getMouseEventArea(e) == EditorMouseEventArea.ANNOTATIONS_AREA) {
DefaultActionGroup actionGroup = new DefaultActionGroup(EditorBundle.message("editor.annotations.action.group.name"), true);
actionGroup.add(new CloseAnnotationsAction());
final List<AnAction> addActions = new ArrayList<AnAction>();
final Point p = e.getPoint();
int line = EditorUtil.yPositionToLogicalLine(myEditor, p);
//if (line >= myEditor.getDocument().getLineCount()) return;
for (TextAnnotationGutterProvider gutterProvider : myTextAnnotationGutters) {
final List<AnAction> list = gutterProvider.getPopupActions(line, myEditor);
if (list != null) {
for (AnAction action : list) {
if (! addActions.contains(action)) {
addActions.add(action);
}
}
}
}
for (AnAction addAction : addActions) {
actionGroup.add(addAction);
}
JPopupMenu menu = actionManager.createActionPopupMenu("", actionGroup).getComponent();
menu.show(this, e.getX(), e.getY());
}
else {
GutterIconRenderer renderer = getGutterRenderer(e);
if (renderer != null) {
ActionGroup actionGroup = renderer.getPopupMenuActions();
if (actionGroup != null) {
ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN,
actionGroup);
popupMenu.getComponent().show(this, e.getX(), e.getY());
e.consume();
} else {
AnAction rightButtonAction = renderer.getRightButtonClickAction();
if (rightButtonAction != null) {
rightButtonAction.actionPerformed(new AnActionEvent(e, myEditor.getDataContext(), "ICON_NAVIGATION_SECONDARY_BUTTON", rightButtonAction.getTemplatePresentation(),
ActionManager.getInstance(),
e.getModifiers()));
e.consume();
}
}
}
else {
if (myShowDefaultGutterPopup) {
ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_EDITOR_GUTTER);
ActionPopupMenu popupMenu = actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, group);
popupMenu.getComponent().show(this, e.getX(), e.getY());
}
e.consume();
}
}
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
TooltipController.getInstance().cancelTooltip(GUTTER_TOOLTIP_GROUP, e, false);
}
private int convertPointToLineNumber(final Point p) {
int line = EditorUtil.yPositionToLogicalLine(myEditor, p);
if (line >= myEditor.getDocument().getLineCount()) return -1;
int startOffset = myEditor.getDocument().getLineStartOffset(line);
final FoldRegion region = myEditor.getFoldingModel().getCollapsedRegionAtOffset(startOffset);
if (region != null) {
line = myEditor.getDocument().getLineNumber(region.getEndOffset());
if (line >= myEditor.getDocument().getLineCount()) return -1;
}
return line;
}
@Nullable
private GutterMark getGutterRenderer(final Point p) {
int line = convertPointToLineNumber(p);
if (line == -1) return null;
List<GutterMark> renderers = myLineToGutterRenderers.get(line);
if (renderers == null) {
return null;
}
final GutterMark[] result = {null};
processIconsRow(line, renderers, new LineGutterIconRendererProcessor() {
@Override
public void process(int x, int y, GutterMark renderer) {
final int ex = convertX((int)p.getX());
Icon icon = renderer.getIcon();
// Do not check y to extend the area where users could click
if (x <= ex && ex <= x + icon.getIconWidth()) {
result[0] = renderer;
}
}
});
return result[0];
}
@Nullable
private GutterIconRenderer getGutterRenderer(final MouseEvent e) {
return (GutterIconRenderer)getGutterRenderer(e.getPoint());
}
public int convertX(int x) {
if (!isMirrored()) return x;
return getWidth() - x;
}
public void dispose() {
for (TextAnnotationGutterProvider gutterProvider : myTextAnnotationGutters) {
gutterProvider.gutterClosed();
}
myProviderToListener.clear();
}
}