blob: 032573521b56228abda3a429c92a524ac17281a9 [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: Apr 19, 2002
* Time: 2:56:43 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.hint.*;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorFontType;
import com.intellij.openapi.editor.ex.*;
import com.intellij.openapi.editor.markup.ErrorStripeRenderer;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.fileEditor.impl.EditorWindowHolder;
import com.intellij.openapi.ui.GraphicsConfig;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
import com.intellij.ui.*;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Alarm;
import com.intellij.util.IJSwingUtilities;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.ButtonlessScrollBarUI;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashSet;
import gnu.trove.TIntIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.ScrollBarUI;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;
public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMarkupModel {
private static final TooltipGroup ERROR_STRIPE_TOOLTIP_GROUP = new TooltipGroup("ERROR_STRIPE_TOOLTIP_GROUP", 0);
private static final Icon ERRORS_FOUND_ICON = AllIcons.General.ErrorsFound;
private static final int ERROR_ICON_WIDTH = ERRORS_FOUND_ICON.getIconWidth();
private static final int ERROR_ICON_HEIGHT = ERRORS_FOUND_ICON.getIconHeight();
private static final int PREFERRED_WIDTH = ERROR_ICON_WIDTH + 3;
private final EditorImpl myEditor;
// null renderer means we should not show traffic light icon
private ErrorStripeRenderer myErrorStripeRenderer;
private final List<ErrorStripeListener> myErrorMarkerListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private boolean dimensionsAreValid;
private int myEditorScrollbarTop = -1;
private int myEditorTargetHeight = -1;
private int myEditorSourceHeight = -1;
private ProperTextRange myDirtyYPositions;
private static final ProperTextRange WHOLE_DOCUMENT = new ProperTextRange(0, 0);
@NotNull private ErrorStripTooltipRendererProvider myTooltipRendererProvider = new BasicTooltipRendererProvider();
private int myMinMarkHeight = 3;
private static final int myPreviewLines = 5;// Actually preview has myPreviewLines * 2 + 1 lines (above + below + current one)
private static final int myCachePreviewLines = 100;// Actually cache image has myCachePreviewLines * 2 + 1 lines (above + below + current one)
private LightweightHint myEditorPreviewHint = null;
private final EditorFragmentRenderer myEditorFragmentRenderer;
private int myRowAdjuster = 0;
private int myWheelAccumulator = 0;
EditorMarkupModelImpl(@NotNull EditorImpl editor) {
super(editor.getDocument());
myEditor = editor;
myEditorFragmentRenderer = new EditorFragmentRenderer();
}
private int offsetToLine(int offset, Document document) {
if (offset < 0) {
return 0;
}
if (offset > document.getTextLength()) {
return document.getLineCount();
}
return myEditor.offsetToVisualLine(offset);
}
public void repaintVerticalScrollBar() {
myEditor.getVerticalScrollBar().repaint();
}
void recalcEditorDimensions() {
EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar();
int scrollBarHeight = Math.max(0, scrollBar.getSize().height);
myEditorScrollbarTop = scrollBar.getDecScrollButtonHeight()/* + 1*/;
assert myEditorScrollbarTop>=0;
int editorScrollbarBottom = scrollBar.getIncScrollButtonHeight();
myEditorTargetHeight = scrollBarHeight - myEditorScrollbarTop - editorScrollbarBottom;
myEditorSourceHeight = myEditor.getPreferredHeight();
dimensionsAreValid = scrollBarHeight != 0;
}
public void repaintTrafficLightIcon() {
MyErrorPanel errorPanel = getErrorPanel();
if (errorPanel != null) {
errorPanel.myErrorStripeButton.repaint();
errorPanel.repaintTrafficTooltip();
}
}
private static class PositionedStripe {
private final Color color;
private int yEnd;
private final boolean thin;
private final int layer;
private PositionedStripe(Color color, int yEnd, boolean thin, int layer) {
this.color = color;
this.yEnd = yEnd;
this.thin = thin;
this.layer = layer;
}
}
private boolean showToolTipByMouseMove(final MouseEvent e) {
if (myEditor.getVisibleLineCount() == 0) return false;
MouseEvent me = new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(), 0, e.getY() + 1, e.getClickCount(),
e.isPopupTrigger());
final int visualLine = getVisualLineByEvent(e);
Rectangle area = myEditor.getScrollingModel().getVisibleArea();
int visualY = myEditor.getLineHeight() * visualLine;
boolean isVisible = area.contains(area.x, visualY) && myWheelAccumulator == 0;
TooltipRenderer bigRenderer;
if (IJSwingUtilities.findParentByInterface(myEditor.getComponent(), EditorWindowHolder.class) == null || isVisible || !UISettings.getInstance().SHOW_EDITOR_TOOLTIP) {
final Set<RangeHighlighter> highlighters = new THashSet<RangeHighlighter>();
getNearestHighlighters(this, me.getY(), highlighters);
getNearestHighlighters((MarkupModelEx)DocumentMarkupModel.forDocument(myEditor.getDocument(), getEditor().getProject(), true), me.getY(), highlighters);
if (highlighters.isEmpty()) return false;
int y = e.getY();
RangeHighlighter nearest = getNearestRangeHighlighter(e);
if (nearest != null) {
ProperTextRange range = offsetsToYPositions(nearest.getStartOffset(), nearest.getEndOffset());
int eachStartY = range.getStartOffset();
int eachEndY = range.getEndOffset();
y = eachStartY + (eachEndY - eachStartY) / 2;
}
me = new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(), me.getX(), y + 1, e.getClickCount(),
e.isPopupTrigger());
bigRenderer = myTooltipRendererProvider.calcTooltipRenderer(highlighters);
if (bigRenderer != null) {
showTooltip(me, bigRenderer, createHint(me));
return true;
}
return false;
} else {
float rowRatio = (float)visualLine /(myEditor.getVisibleLineCount() - 1);
int y = myRowAdjuster != 0 ? (int)(rowRatio * myEditor.getVerticalScrollBar().getHeight()) : me.getY();
me = new MouseEvent(me.getComponent(), me.getID(), me.getWhen(), me.getModifiers(), me.getX(), y, me.getClickCount(), me.isPopupTrigger());
final List<RangeHighlighterEx> highlighters = new ArrayList<RangeHighlighterEx>();
collectRangeHighlighters(this, visualLine, highlighters);
collectRangeHighlighters((MarkupModelEx)DocumentMarkupModel.forDocument(myEditor.getDocument(), getEditor().getProject(), true),
visualLine,
highlighters);
myEditorFragmentRenderer.update(visualLine, highlighters, me.isAltDown());
myEditorFragmentRenderer.show(myEditor, me.getPoint(), true, ERROR_STRIPE_TOOLTIP_GROUP, createHint(me));
return true;
}
}
private static HintHint createHint(MouseEvent me) {
return new HintHint(me).setAwtTooltip(true).setPreferredPosition(Balloon.Position.atLeft).setBorderInsets(new Insets(1, 1, 1, 1))
.setShowImmediately(true).setAnimationEnabled(false);
}
private int getVisualLineByEvent(MouseEvent e) {
return fitLineToEditor(myEditor.offsetToVisualLine(yPositionToOffset(e.getY() + myWheelAccumulator, true)));
}
private int fitLineToEditor(int visualLine) {
return Math.max(0, Math.min(myEditor.getVisibleLineCount() - 1, visualLine));
}
private int getOffset(int visualLine, boolean startLine) {
int logicalLine = myEditor.visualToLogicalPosition(new VisualPosition(visualLine, 0), true).line;
return startLine? myEditor.getDocument().getLineStartOffset(logicalLine) : myEditor.getDocument().getLineEndOffset(logicalLine);
}
private void collectRangeHighlighters(MarkupModelEx markupModel, final int visualLine, final Collection<RangeHighlighterEx> highlighters) {
final int startOffset = getOffset(fitLineToEditor(visualLine - myPreviewLines), true);
final int endOffset = getOffset(fitLineToEditor(visualLine + myPreviewLines), false);
markupModel.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
@Override
public boolean process(RangeHighlighterEx highlighter) {
if (!highlighter.getEditorFilter().avaliableIn(myEditor)) return true;
if (highlighter.getErrorStripeMarkColor() != null) {
if (highlighter.getStartOffset() < endOffset && highlighter.getEndOffset() > startOffset) {
highlighters.add(highlighter);
}
}
return true;
}
});
}
@Nullable
private RangeHighlighter getNearestRangeHighlighter(final MouseEvent e) {
List<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
getNearestHighlighters(this, e.getY(), highlighters);
getNearestHighlighters((MarkupModelEx)DocumentMarkupModel.forDocument(myEditor.getDocument(), myEditor.getProject(), true), e.getY(),
highlighters);
RangeHighlighter nearestMarker = null;
int yPos = 0;
for (RangeHighlighter highlighter : highlighters) {
final int newYPos = offsetsToYPositions(highlighter.getStartOffset(), highlighter.getEndOffset()).getStartOffset();
if (nearestMarker == null || Math.abs(yPos - e.getY()) > Math.abs(newYPos - e.getY())) {
nearestMarker = highlighter;
yPos = newYPos;
}
}
return nearestMarker;
}
private void getNearestHighlighters(MarkupModelEx markupModel,
final int scrollBarY,
final Collection<RangeHighlighter> nearest) {
int startOffset = yPositionToOffset(scrollBarY - myMinMarkHeight, true);
int endOffset = yPositionToOffset(scrollBarY + myMinMarkHeight, false);
markupModel.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
@Override
public boolean process(RangeHighlighterEx highlighter) {
if (!highlighter.getEditorFilter().avaliableIn(myEditor)) return true;
if (highlighter.getErrorStripeMarkColor() != null) {
ProperTextRange range = offsetsToYPositions(highlighter.getStartOffset(), highlighter.getEndOffset());
if (scrollBarY >= range.getStartOffset() - myMinMarkHeight * 2 &&
scrollBarY <= range.getEndOffset() + myMinMarkHeight * 2) {
nearest.add(highlighter);
}
}
return true;
}
});
}
private void doClick(final MouseEvent e) {
RangeHighlighter marker = getNearestRangeHighlighter(e);
int offset;
LogicalPosition logicalPositionToScroll = null;
if (marker == null) {
if (myEditorPreviewHint != null) {
logicalPositionToScroll = myEditor.visualToLogicalPosition(new VisualPosition(myEditorFragmentRenderer.myStartVisualLine, 0));
offset = myEditor.getDocument().getLineStartOffset(logicalPositionToScroll.line);
} else {
return;
}
} else {
offset = marker.getStartOffset();
}
final Document doc = myEditor.getDocument();
if (doc.getLineCount() > 0 && myEditorPreviewHint == null) {
// Necessary to expand folded block even if navigating just before one
// Very useful when navigating to first unused import statement.
int lineEnd = doc.getLineEndOffset(doc.getLineNumber(offset));
myEditor.getCaretModel().moveToOffset(lineEnd);
}
myEditor.getCaretModel().removeSecondaryCarets();
myEditor.getCaretModel().moveToOffset(offset);
myEditor.getSelectionModel().removeSelection();
ScrollingModel scrollingModel = myEditor.getScrollingModel();
scrollingModel.disableAnimation();
if (logicalPositionToScroll != null) {
int lineY = myEditor.logicalPositionToXY(logicalPositionToScroll).y;
int relativePopupOffset = myEditorFragmentRenderer.myRelativeY;
scrollingModel.scrollVertically(lineY - relativePopupOffset);
}
else {
scrollingModel.scrollToCaret(ScrollType.CENTER);
}
scrollingModel.enableAnimation();
if (marker != null) {
fireErrorMarkerClicked(marker, e);
}
}
@Override
public void setErrorStripeVisible(boolean val) {
if (val) {
myEditor.getVerticalScrollBar().setPersistentUI(new MyErrorPanel());
}
else {
myEditor.getVerticalScrollBar().setPersistentUI(ButtonlessScrollBarUI.createNormal());
}
}
@Nullable
private MyErrorPanel getErrorPanel() {
ScrollBarUI ui = myEditor.getVerticalScrollBar().getUI();
return ui instanceof MyErrorPanel ? (MyErrorPanel)ui : null;
}
@Override
public void setErrorPanelPopupHandler(@NotNull PopupHandler handler) {
ApplicationManager.getApplication().assertIsDispatchThread();
MyErrorPanel errorPanel = getErrorPanel();
if (errorPanel != null) {
errorPanel.setPopupHandler(handler);
}
}
@Override
public void setErrorStripTooltipRendererProvider(@NotNull final ErrorStripTooltipRendererProvider provider) {
myTooltipRendererProvider = provider;
}
@Override
@NotNull
public ErrorStripTooltipRendererProvider getErrorStripTooltipRendererProvider() {
return myTooltipRendererProvider;
}
@Override
@NotNull
public Editor getEditor() {
return myEditor;
}
@Override
public void setErrorStripeRenderer(@NotNull ErrorStripeRenderer renderer) {
assertIsDispatchThread();
if (myErrorStripeRenderer instanceof Disposable) {
Disposer.dispose((Disposable)myErrorStripeRenderer);
}
myErrorStripeRenderer = renderer;
//try to not cancel tooltips here, since it is being called after every writeAction, even to the console
//HintManager.getInstance().getTooltipController().cancelTooltips();
myEditor.getVerticalScrollBar()
.updateUI(); // re-create increase/decrease buttons, in case of not-null renderer it will show traffic light icon
repaintVerticalScrollBar();
}
private static void assertIsDispatchThread() {
ApplicationManagerEx.getApplicationEx().assertIsDispatchThread();
}
@Override
public ErrorStripeRenderer getErrorStripeRenderer() {
return myErrorStripeRenderer;
}
@Override
public void dispose() {
final MyErrorPanel panel = getErrorPanel();
if (panel != null) {
panel.uninstallListeners();
}
if (myErrorStripeRenderer instanceof Disposable) {
Disposer.dispose((Disposable)myErrorStripeRenderer);
}
myErrorStripeRenderer = null;
super.dispose();
}
// startOffset == -1 || endOffset == -1 means whole document
void repaint(int startOffset, int endOffset) {
ProperTextRange range = offsetsToYPositions(startOffset, endOffset);
markDirtied(range);
if (startOffset == -1 || endOffset == -1) {
myDirtyYPositions = WHOLE_DOCUMENT;
}
myEditor.getVerticalScrollBar().repaint(0, range.getStartOffset(), PREFERRED_WIDTH, range.getLength() + myMinMarkHeight);
}
private boolean isMirrored() {
return myEditor.isMirrored();
}
private static final Dimension STRIPE_BUTTON_PREFERRED_SIZE = new Dimension(ERROR_ICON_WIDTH + 4, ERROR_ICON_HEIGHT + 4);
private class ErrorStripeButton extends JButton {
private ErrorStripeButton() {
setFocusable(false);
}
@Override
public void paint(@NotNull Graphics g) {
((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart();
final Rectangle bounds = getBounds();
final Rectangle errorIconBounds = new Rectangle(0, 0, ERROR_ICON_WIDTH, ERROR_ICON_HEIGHT);
errorIconBounds.x = bounds.width / 2 - errorIconBounds.width / 2 + 1;
errorIconBounds.y = bounds.height / 2 - errorIconBounds.height / 2;
try {
if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) {
g.setColor(getEditor().getColorsScheme().getDefaultBackground());
g.fillRect(0, 0, bounds.width, bounds.height);
if (myErrorStripeRenderer != null) {
myErrorStripeRenderer.paint(this, g, errorIconBounds);
}
} else {
g.setColor(ButtonlessScrollBarUI.getTrackBackground());
g.fillRect(0, 0, bounds.width, bounds.height);
g.setColor(ButtonlessScrollBarUI.getTrackBorderColor());
int borderX = !isMirrored() ? 0 : bounds.width - 1;
g.drawLine(borderX, 0, borderX, bounds.height);
if (myErrorStripeRenderer != null) {
myErrorStripeRenderer.paint(this, g, errorIconBounds);
}
}
}
finally {
((ApplicationImpl)ApplicationManager.getApplication()).editorPaintFinish();
}
}
@Override
public Dimension getPreferredSize() {
return STRIPE_BUTTON_PREFERRED_SIZE;
}
}
private class MyErrorPanel extends ButtonlessScrollBarUI implements MouseMotionListener, MouseListener, MouseWheelListener, UISettingsListener {
private PopupHandler myHandler;
private JButton myErrorStripeButton;
private BufferedImage myCachedTrack;
@Override
protected JButton createDecreaseButton(int orientation) {
myErrorStripeButton = myErrorStripeRenderer == null ? super.createDecreaseButton(orientation) : new ErrorStripeButton();
return myErrorStripeButton;
}
@Override
public boolean alwaysShowTrack() {
if (scrollbar.getOrientation() == Adjustable.VERTICAL) return true;
return super.alwaysShowTrack();
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
myCachedTrack = null;
}
@Override
public void uninstallUI(@NotNull JComponent c) {
super.uninstallUI(c);
myCachedTrack = null;
}
@Override
protected void installListeners() {
super.installListeners();
scrollbar.addMouseMotionListener(this);
scrollbar.addMouseListener(this);
scrollbar.addMouseWheelListener(this);
myErrorStripeButton.addMouseMotionListener(this);
myErrorStripeButton.addMouseListener(this);
UISettings.getInstance().addUISettingsListener(this);
}
@Override
protected void uninstallListeners() {
scrollbar.removeMouseMotionListener(this);
scrollbar.removeMouseListener(this);
myErrorStripeButton.removeMouseMotionListener(this);
myErrorStripeButton.removeMouseListener(this);
UISettings.getInstance().removeUISettingsListener(this);
super.uninstallListeners();
}
@Override
public void uiSettingsChanged(UISettings source) {
if (!UISettings.getInstance().SHOW_EDITOR_TOOLTIP) {
hideMyEditorPreviewHint();
}
}
@Override
protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) {
super.paintThumb(g, c, thumbBounds);
return;
}
if (isMacOverlayScrollbar()) {
if (!isMirrored()) {
super.paintThumb(g, c, thumbBounds);
}
else {
Graphics2D g2d = (Graphics2D)g;
AffineTransform old = g2d.getTransform();
AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
tx.translate(-c.getWidth(), 0);
g2d.transform(tx);
g.translate(1, 0);
super.paintThumb(g, c, thumbBounds);
g2d.setTransform(old);
}
}
else {
int shift = isMirrored() ? -9 : 9;
g.translate(shift, 0);
super.paintThumb(g, c, thumbBounds);
g.translate(-shift, 0);
}
}
@Override
protected int adjustThumbWidth(int width) {
if (isMacOverlayScrollbar() || UISettings.getInstance().PRESENTATION_MODE) return super.adjustThumbWidth(width);
return width - 2;
}
@Override
protected int getThickness() {
if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) return super.getThickness();
return super.getThickness() + (isMacOverlayScrollbar() ? 2 : 7);
}
@Override
protected void doPaintTrack(Graphics g, JComponent c, Rectangle bounds) {
if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) {
g.setColor(getEditor().getColorsScheme().getDefaultBackground());
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
//return;
}
Rectangle clip = g.getClipBounds().intersection(bounds);
if (clip.height == 0) return;
Rectangle componentBounds = c.getBounds();
ProperTextRange docRange = ProperTextRange.create(0, (int)componentBounds.getHeight());
if (myCachedTrack == null || myCachedTrack.getHeight() != componentBounds.getHeight()) {
myCachedTrack = UIUtil.createImage(componentBounds.width, componentBounds.height, BufferedImage.TYPE_INT_ARGB);
myDirtyYPositions = docRange;
paintTrackBasement(myCachedTrack.getGraphics(), new Rectangle(0, 0, componentBounds.width, componentBounds.height));
}
if (myDirtyYPositions == WHOLE_DOCUMENT) {
myDirtyYPositions = docRange;
}
if (myDirtyYPositions != null) {
final Graphics2D imageGraphics = myCachedTrack.createGraphics();
((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart();
try {
myDirtyYPositions = myDirtyYPositions.intersection(docRange);
if (myDirtyYPositions == null) myDirtyYPositions = docRange;
repaint(imageGraphics, componentBounds.width, ERROR_ICON_WIDTH - 1, myDirtyYPositions);
myDirtyYPositions = null;
}
finally {
((ApplicationImpl)ApplicationManager.getApplication()).editorPaintFinish();
}
}
UIUtil.drawImage(g, myCachedTrack, null, 0, 0);
}
private void paintTrackBasement(Graphics g, Rectangle bounds) {
if (UISettings.getInstance().PRESENTATION_MODE || ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) {
g.setColor(EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground());
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
return;
}
g.setColor(ButtonlessScrollBarUI.getTrackBackground());
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height + 1);
g.setColor(ButtonlessScrollBarUI.getTrackBorderColor());
int border = isMirrored() ? bounds.x + bounds.width - 1 : bounds.x;
g.drawLine(border, bounds.y, border, bounds.y + bounds.height + 1);
}
@Override
protected Color adjustColor(Color c) {
if (isMacOverlayScrollbar()) return super.adjustColor(c);
if (UIUtil.isUnderDarcula()) {
return c;
}
return ColorUtil.withAlpha(ColorUtil.shift(super.adjustColor(c), 0.9), 0.85);
}
private void repaint(final Graphics g, int gutterWidth, final int stripeWidth, ProperTextRange yrange) {
final Rectangle clip = new Rectangle(0, yrange.getStartOffset(), gutterWidth, yrange.getLength() + myMinMarkHeight);
paintTrackBasement(g, clip);
Document document = myEditor.getDocument();
int startOffset = yPositionToOffset(clip.y - myMinMarkHeight, true);
int endOffset = yPositionToOffset(clip.y + clip.height, false);
drawMarkup(g, stripeWidth, startOffset, endOffset, EditorMarkupModelImpl.this);
drawMarkup(g, stripeWidth, startOffset, endOffset,
(MarkupModelEx)DocumentMarkupModel.forDocument(document, myEditor.getProject(), true));
}
private void drawMarkup(final Graphics g, final int width, int startOffset, int endOffset, MarkupModelEx markup) {
final Queue<PositionedStripe> thinEnds = new PriorityQueue<PositionedStripe>(5, new Comparator<PositionedStripe>() {
@Override
public int compare(@NotNull PositionedStripe o1, @NotNull PositionedStripe o2) {
return o1.yEnd - o2.yEnd;
}
});
final Queue<PositionedStripe> wideEnds = new PriorityQueue<PositionedStripe>(5, new Comparator<PositionedStripe>() {
@Override
public int compare(@NotNull PositionedStripe o1, @NotNull PositionedStripe o2) {
return o1.yEnd - o2.yEnd;
}
});
// sorted by layer
final List<PositionedStripe> thinStripes = new ArrayList<PositionedStripe>();
final List<PositionedStripe> wideStripes = new ArrayList<PositionedStripe>();
final int[] thinYStart = new int[1]; // in range 0..yStart all spots are drawn
final int[] wideYStart = new int[1]; // in range 0..yStart all spots are drawn
markup.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
@Override
public boolean process(RangeHighlighterEx highlighter) {
if (!highlighter.getEditorFilter().avaliableIn(myEditor)) return true;
Color color = highlighter.getErrorStripeMarkColor();
if (color == null) return true;
boolean isThin = highlighter.isThinErrorStripeMark();
int[] yStart = isThin ? thinYStart : wideYStart;
List<PositionedStripe> stripes = isThin ? thinStripes : wideStripes;
Queue<PositionedStripe> ends = isThin ? thinEnds : wideEnds;
ProperTextRange range = offsetsToYPositions(highlighter.getStartOffset(), highlighter.getEndOffset());
final int ys = range.getStartOffset();
int ye = range.getEndOffset();
if (ye - ys < myMinMarkHeight) ye = ys + myMinMarkHeight;
yStart[0] = drawStripesEndingBefore(ys, ends, stripes, g, width, yStart[0]);
final int layer = highlighter.getLayer();
PositionedStripe stripe = null;
int i;
for (i = 0; i < stripes.size(); i++) {
PositionedStripe s = stripes.get(i);
if (s.layer == layer) {
stripe = s;
break;
}
if (s.layer < layer) {
break;
}
}
if (stripe == null) {
// started new stripe, draw previous above
if (yStart[0] != ys) {
if (!stripes.isEmpty()) {
PositionedStripe top = stripes.get(0);
drawSpot(g, width, top.thin, yStart[0], ys, top.color, true, true);
}
yStart[0] = ys;
}
stripe = new PositionedStripe(color, ye, isThin, layer);
stripes.add(i, stripe);
ends.offer(stripe);
}
else {
// key changed, reinsert into queue
if (stripe.yEnd != ye) {
ends.remove(stripe);
stripe.yEnd = ye;
ends.offer(stripe);
}
}
return true;
}
});
drawStripesEndingBefore(Integer.MAX_VALUE, thinEnds, thinStripes, g, width, thinYStart[0]);
drawStripesEndingBefore(Integer.MAX_VALUE, wideEnds, wideStripes, g, width, wideYStart[0]);
}
private int drawStripesEndingBefore(int ys,
Queue<PositionedStripe> ends,
List<PositionedStripe> stripes,
Graphics g, int width, int yStart) {
while (!ends.isEmpty()) {
PositionedStripe endingStripe = ends.peek();
if (endingStripe.yEnd > ys) break;
ends.remove();
// check whether endingStripe got obscured in the range yStart..endingStripe.yEnd
int i = stripes.indexOf(endingStripe);
stripes.remove(i);
if (i == 0) {
// visible
drawSpot(g, width, endingStripe.thin, yStart, endingStripe.yEnd, endingStripe.color, true, true);
yStart = endingStripe.yEnd;
}
}
return yStart;
}
private void drawSpot(Graphics g,
int width,
boolean thinErrorStripeMark,
int yStart,
int yEnd,
Color color,
boolean drawTopDecoration,
boolean drawBottomDecoration) {
GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
int x = isMirrored() ? 3 : 5;
int paintWidth = width;
boolean flatStyle = Registry.is("ide.new.markup.markers");
if (thinErrorStripeMark) {
paintWidth /= 2;
paintWidth += flatStyle ? -2 : 1;
x = isMirrored() ? width + 2 : 0;
if (yEnd - yStart < 6) {
yStart -= 1;
yEnd += yEnd-yStart - 1;
}
}
if (color == null) return;
Color darker = color;
if (!UIUtil.isUnderDarcula()) {
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
hsb[2] = Math.min(1f, hsb[2] * 0.85f);
//noinspection UseJBColor
darker = new Color(Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]));
}
if (flatStyle) {
g.setColor(darker);
g.fillRoundRect(x, yStart, paintWidth, yEnd - yStart, 3,3);
config.restore();
return;
}
g.setColor(color);
g.fillRect(x + 1, yStart, paintWidth - 2, yEnd - yStart + 1);
Color brighter = color.brighter();
g.setColor(brighter);
//left decoration
UIUtil.drawLine(g, x, yStart, x, yEnd/* - 1*/);
if (drawTopDecoration) {
//top decoration
UIUtil.drawLine(g, x + 1, yStart, x + paintWidth - 2, yStart);
}
g.setColor(darker);
if (drawBottomDecoration) {
// bottom decoration
UIUtil.drawLine(g, x + 1, yEnd/* - 1*/, x + paintWidth - 2, yEnd/* - 1*/); // large bottom to let overwrite by hl below
}
//right decoration
UIUtil.drawLine(g, x + paintWidth - 2, yStart, x + paintWidth - 2, yEnd/* - 1*/);
config.restore();
}
// mouse events
@Override
public void mouseClicked(@NotNull final MouseEvent e) {
CommandProcessor.getInstance().executeCommand(myEditor.getProject(), new Runnable() {
@Override
public void run() {
doMouseClicked(e);
}
},
EditorBundle.message("move.caret.command.name"),
DocCommandGroupId.noneGroupId(getDocument()), UndoConfirmationPolicy.DEFAULT,
getDocument()
);
}
@Override
public void mousePressed(@NotNull MouseEvent e) {
}
@Override
public void mouseReleased(@NotNull MouseEvent e) {
}
private int getWidth() {
return scrollbar.getWidth();
}
private void doMouseClicked(MouseEvent e) {
myEditor.getContentComponent().requestFocus();
int lineCount = getDocument().getLineCount() + myEditor.getSettings().getAdditionalLinesCount();
if (lineCount == 0) {
return;
}
if (e.getX() > 0 && e.getX() <= getWidth()) {
doClick(e);
}
}
@Override
public void mouseMoved(@NotNull MouseEvent e) {
EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar();
int buttonHeight = scrollBar.getDecScrollButtonHeight();
int lineCount = getDocument().getLineCount() + myEditor.getSettings().getAdditionalLinesCount();
if (lineCount == 0) {
return;
}
if (e.getY() < buttonHeight && myErrorStripeRenderer != null) {
showTrafficLightTooltip(e);
return;
}
if (e.getX() > 0 && e.getX() <= getWidth() && showToolTipByMouseMove(e)) {
scrollbar.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return;
}
cancelMyToolTips(e, false);
if (scrollbar.getCursor().equals(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))) {
scrollbar.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (myEditorPreviewHint == null) return;
myWheelAccumulator += (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL ? e.getUnitsToScroll() * e.getScrollAmount() :
e.getWheelRotation() < 0 ? -e.getScrollAmount() : e.getScrollAmount());
myRowAdjuster = myWheelAccumulator / myEditor.getLineHeight();
showToolTipByMouseMove(e);
}
private TrafficTooltipRenderer myTrafficTooltipRenderer;
private void showTrafficLightTooltip(MouseEvent e) {
if (myTrafficTooltipRenderer == null) {
myTrafficTooltipRenderer = myTooltipRendererProvider.createTrafficTooltipRenderer(new Runnable() {
@Override
public void run() {
myTrafficTooltipRenderer = null;
}
}, myEditor);
}
showTooltip(e, myTrafficTooltipRenderer, new HintHint(e).setAwtTooltip(true).setMayCenterPosition(true).setContentActive(false)
.setPreferredPosition(Balloon.Position.atLeft));
}
private void repaintTrafficTooltip() {
if (myTrafficTooltipRenderer != null) {
myTrafficTooltipRenderer.repaintTooltipWindow();
}
}
private void cancelMyToolTips(final MouseEvent e, boolean checkIfShouldSurvive) {
hideMyEditorPreviewHint();
final TooltipController tooltipController = TooltipController.getInstance();
if (!checkIfShouldSurvive || !tooltipController.shouldSurvive(e)) {
tooltipController.cancelTooltip(ERROR_STRIPE_TOOLTIP_GROUP, e, true);
}
}
private void hideMyEditorPreviewHint() {
if (myEditorPreviewHint != null) {
myEditorPreviewHint.hide();
myEditorPreviewHint = null;
myRowAdjuster = 0;
myWheelAccumulator = 0;
}
}
@Override
public void mouseEntered(@NotNull MouseEvent e) {
}
@Override
public void mouseExited(@NotNull MouseEvent e) {
cancelMyToolTips(e, true);
}
@Override
public void mouseDragged(@NotNull MouseEvent e) {
cancelMyToolTips(e, true);
}
private void setPopupHandler(@NotNull PopupHandler handler) {
if (myHandler != null) {
scrollbar.removeMouseListener(myHandler);
myErrorStripeButton.removeMouseListener(myHandler);
}
myHandler = handler;
scrollbar.addMouseListener(handler);
myErrorStripeButton.addMouseListener(myHandler);
}
}
private void showTooltip(MouseEvent e, final TooltipRenderer tooltipObject, @NotNull HintHint hintHint) {
TooltipController tooltipController = TooltipController.getInstance();
tooltipController.showTooltipByMouseMove(myEditor, new RelativePoint(e), tooltipObject,
myEditor.getVerticalScrollbarOrientation() == EditorEx.VERTICAL_SCROLLBAR_RIGHT,
ERROR_STRIPE_TOOLTIP_GROUP, hintHint);
}
private void fireErrorMarkerClicked(RangeHighlighter marker, MouseEvent e) {
ApplicationManager.getApplication().assertIsDispatchThread();
ErrorStripeEvent event = new ErrorStripeEvent(getEditor(), e, marker);
for (ErrorStripeListener listener : myErrorMarkerListeners) {
listener.errorMarkerClicked(event);
}
}
@Override
public void addErrorMarkerListener(@NotNull final ErrorStripeListener listener, @NotNull Disposable parent) {
ContainerUtil.add(listener, myErrorMarkerListeners, parent);
}
public void markDirtied(@NotNull ProperTextRange yPositions) {
if (myDirtyYPositions != WHOLE_DOCUMENT) {
int start = Math.max(0, yPositions.getStartOffset() - myEditor.getLineHeight());
int end = myEditorScrollbarTop + myEditorTargetHeight == 0 ? yPositions.getEndOffset() + myEditor.getLineHeight()
: Math
.min(myEditorScrollbarTop + myEditorTargetHeight, yPositions.getEndOffset() + myEditor.getLineHeight());
ProperTextRange adj = new ProperTextRange(start, Math.max(end, start));
myDirtyYPositions = myDirtyYPositions == null ? adj : myDirtyYPositions.union(adj);
}
myEditorScrollbarTop = 0;
myEditorSourceHeight = 0;
myEditorTargetHeight = 0;
dimensionsAreValid = false;
}
@Override
public void setMinMarkHeight(final int minMarkHeight) {
myMinMarkHeight = minMarkHeight;
}
@Override
public boolean isErrorStripeVisible() {
return getErrorPanel() != null;
}
private static class BasicTooltipRendererProvider implements ErrorStripTooltipRendererProvider {
@Override
public TooltipRenderer calcTooltipRenderer(@NotNull final Collection<RangeHighlighter> highlighters) {
LineTooltipRenderer bigRenderer = null;
//do not show same tooltip twice
Set<String> tooltips = null;
for (RangeHighlighter highlighter : highlighters) {
final Object tooltipObject = highlighter.getErrorStripeTooltip();
if (tooltipObject == null) continue;
final String text = tooltipObject.toString();
if (tooltips == null) {
tooltips = new THashSet<String>();
}
if (tooltips.add(text)) {
if (bigRenderer == null) {
bigRenderer = new LineTooltipRenderer(text, new Object[]{highlighters});
}
else {
bigRenderer.addBelow(text);
}
}
}
return bigRenderer;
}
@NotNull
@Override
public TooltipRenderer calcTooltipRenderer(@NotNull final String text) {
return new LineTooltipRenderer(text, new Object[]{text});
}
@NotNull
@Override
public TooltipRenderer calcTooltipRenderer(@NotNull final String text, final int width) {
return new LineTooltipRenderer(text, width, new Object[]{text});
}
@NotNull
@Override
public TrafficTooltipRenderer createTrafficTooltipRenderer(@NotNull final Runnable onHide, @NotNull Editor editor) {
return new TrafficTooltipRenderer() {
@Override
public void repaintTooltipWindow() {
}
@Override
public LightweightHint show(@NotNull Editor editor,
@NotNull Point p,
boolean alignToRight,
@NotNull TooltipGroup group,
@NotNull HintHint hintHint) {
JLabel label = new JLabel("WTF");
return new LightweightHint(label) {
@Override
public void hide() {
super.hide();
onHide.run();
}
};
}
};
}
}
@NotNull
private ProperTextRange offsetsToYPositions(int start, int end) {
if (!dimensionsAreValid) {
recalcEditorDimensions();
}
Document document = myEditor.getDocument();
int startLineNumber = end == -1 ? 0 : offsetToLine(start, document);
int startY;
int lineCount;
int editorTargetHeight = Math.max(0, myEditorTargetHeight);
if (myEditorSourceHeight < editorTargetHeight) {
lineCount = 0;
startY = myEditorScrollbarTop + startLineNumber * myEditor.getLineHeight();
}
else {
lineCount = myEditorSourceHeight / myEditor.getLineHeight();
startY = myEditorScrollbarTop + (int)((float)startLineNumber / lineCount * editorTargetHeight);
}
int endY;
int endLineNumber = offsetToLine(end, document);
if (end == -1 || start == -1) {
endY = Math.min(myEditorSourceHeight, editorTargetHeight);
}
else if (start == end || offsetToLine(start, document) == endLineNumber) {
endY = startY; // both offsets are on the same line, no need to recalc Y position
}
else {
if (myEditorSourceHeight < editorTargetHeight) {
endY = myEditorScrollbarTop + endLineNumber * myEditor.getLineHeight();
}
else {
endY = myEditorScrollbarTop + (int)((float)endLineNumber / lineCount * editorTargetHeight);
}
}
if (endY < startY) endY = startY;
return new ProperTextRange(startY, endY);
}
private int yPositionToOffset(int y, boolean beginLine) {
if (!dimensionsAreValid) {
recalcEditorDimensions();
}
final int safeY = Math.max(0, y - myEditorScrollbarTop);
VisualPosition visual;
if (myEditorSourceHeight < myEditorTargetHeight) {
visual = myEditor.xyToVisualPosition(new Point(0, safeY));
}
else {
float fraction = Math.max(0, Math.min(1, safeY / (float)myEditorTargetHeight));
final int lineCount = myEditorSourceHeight / myEditor.getLineHeight();
visual = new VisualPosition((int)(fraction * lineCount), 0);
}
int line = myEditor.visualToLogicalPosition(visual).line;
Document document = myEditor.getDocument();
if (line < 0) return 0;
if (line >= document.getLineCount()) return document.getTextLength();
final FoldingModelEx foldingModel = myEditor.getFoldingModel();
if (beginLine) {
final int offset = document.getLineStartOffset(line);
final FoldRegion startCollapsed = foldingModel.getCollapsedRegionAtOffset(offset);
return startCollapsed != null ? Math.min(offset, startCollapsed.getStartOffset()) : offset;
}
else {
final int offset = document.getLineEndOffset(line);
final FoldRegion startCollapsed = foldingModel.getCollapsedRegionAtOffset(offset);
return startCollapsed != null ? Math.max(offset, startCollapsed.getEndOffset()) : offset;
}
}
private class EditorFragmentRenderer implements TooltipRenderer {
private int myVisualLine;
private boolean myShowInstantly;
private final List<RangeHighlighterEx> myHighlighters = new ArrayList<RangeHighlighterEx>();
private BufferedImage myCacheLevel1;
private BufferedImage myCacheLevel2;
private int myCacheStartLine;
private int myCacheEndLine;
private int myStartVisualLine;
private int myEndVisualLine;
private int myRelativeY;
private boolean myDelayed = false;
private boolean isDirty = false;
private final AtomicReference<Point> myPointHolder = new AtomicReference<Point>();
private final AtomicReference<HintHint> myHintHolder = new AtomicReference<HintHint>();
private EditorFragmentRenderer() {
update(-1, Collections.<RangeHighlighterEx>emptyList(), false);
}
void update(int visualLine, Collection<RangeHighlighterEx> rangeHighlighters, boolean showInstantly) {
myVisualLine = visualLine;
myShowInstantly = showInstantly;
myHighlighters.clear();
if (myVisualLine ==-1) return;
int oldStartLine = myStartVisualLine;
int oldEndLine = myEndVisualLine;
myStartVisualLine = fitLineToEditor(myVisualLine - myPreviewLines);
myEndVisualLine = fitLineToEditor(myVisualLine + myPreviewLines);
isDirty |= oldStartLine != myStartVisualLine || oldEndLine != myEndVisualLine;
for (RangeHighlighterEx rangeHighlighter : rangeHighlighters) {
myHighlighters.add(rangeHighlighter);
}
Collections.sort(myHighlighters, new Comparator<RangeHighlighterEx>() {
public int compare(@NotNull RangeHighlighterEx ex1, @NotNull RangeHighlighterEx ex2) {
LogicalPosition startPos1 = myEditor.offsetToLogicalPosition(ex1.getAffectedAreaStartOffset());
LogicalPosition startPos2 = myEditor.offsetToLogicalPosition(ex2.getAffectedAreaStartOffset());
if (startPos1.line != startPos2.line) return 0;
return startPos1.column - startPos2.column;
}
});
}
@Override
public LightweightHint show(@NotNull final Editor editor,
@NotNull Point p,
boolean alignToRight,
@NotNull TooltipGroup group,
@NotNull final HintHint hintInfo) {
final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
boolean needDelay = false;
if (myEditorPreviewHint == null) {
needDelay = true;
final JPanel editorFragmentPreviewPanel = new JPanel() {
private static final int R = 6;
@Override
public Dimension getPreferredSize() {
int width = myEditor.getGutterComponentEx().getWidth() + myEditor.getScrollingModel().getVisibleArea().width;
if (!ToolWindowManagerEx.getInstanceEx(myEditor.getProject()).getIdsOn(ToolWindowAnchor.LEFT).isEmpty()) width--;
return new Dimension(width - BalloonImpl.POINTER_WIDTH, myEditor.getLineHeight() * (myEndVisualLine - myStartVisualLine));
}
@Override
protected void paintComponent(@NotNull Graphics g) {
if (myVisualLine ==-1) return;
Dimension size = getPreferredSize();
EditorGutterComponentEx gutterComponentEx = myEditor.getGutterComponentEx();
int gutterWidth = gutterComponentEx.getWidth();
if (myCacheLevel2 == null || myCacheStartLine > myStartVisualLine || myCacheEndLine < myEndVisualLine) {
myCacheStartLine = fitLineToEditor(myVisualLine - myCachePreviewLines);
myCacheEndLine = fitLineToEditor(myCacheStartLine + 2 * myCachePreviewLines + 1);
if (myCacheLevel2 == null) {
myCacheLevel2 = UIUtil.createImage(size.width, myEditor.getLineHeight() * (2 * myCachePreviewLines + 1), BufferedImage.TYPE_INT_RGB);
}
Graphics2D cg = myCacheLevel2.createGraphics();
final AffineTransform t = cg.getTransform();
UISettings.setupAntialiasing(cg);
int lineShift = -myEditor.getLineHeight() * myCacheStartLine;
AffineTransform translateInstance = AffineTransform.getTranslateInstance(-3, lineShift);
translateInstance.preConcatenate(t);
cg.setTransform(translateInstance);
cg.setClip(0, -lineShift, gutterWidth, myCacheLevel2.getHeight());
gutterComponentEx.paint(cg);
translateInstance = AffineTransform.getTranslateInstance(gutterWidth - 3, lineShift);
translateInstance.preConcatenate(t);
cg.setTransform(translateInstance);
EditorComponentImpl contentComponent = myEditor.getContentComponent();
cg.setClip(0, -lineShift, contentComponent.getWidth(), myCacheLevel2.getHeight());
contentComponent.paint(cg);
}
if (myCacheLevel1 == null) {
myCacheLevel1 = UIUtil.createImage(size.width, myEditor.getLineHeight() * (2 * myPreviewLines + 1), BufferedImage.TYPE_INT_RGB);
isDirty = true;
}
if (isDirty) {
myRelativeY = SwingUtilities.convertPoint(this, 0, 0, myEditor.getScrollPane()).y;
Graphics2D g2d = myCacheLevel1.createGraphics();
final AffineTransform transform = g2d.getTransform();
UISettings.setupAntialiasing(g2d);
GraphicsUtil.setupAAPainting(g2d);
g2d.setColor(myEditor.getBackgroundColor());
g2d.fillRect(0, 0, getWidth(), getHeight());
AffineTransform translateInstance =
AffineTransform.getTranslateInstance(gutterWidth, myEditor.getLineHeight() * (myCacheStartLine - myStartVisualLine));
translateInstance.preConcatenate(transform);
g2d.setTransform(translateInstance);
UIUtil.drawImage(g2d, myCacheLevel2, -gutterWidth, 0, null);
TIntIntHashMap rightEdges = new TIntIntHashMap();
int h = myEditor.getLineHeight() - 2;
for (RangeHighlighterEx ex : myHighlighters) {
int hEndOffset = ex.getAffectedAreaEndOffset();
Object tooltip = ex.getErrorStripeTooltip();
if (tooltip == null) continue;
String s = String.valueOf(tooltip);
if (s.isEmpty()) continue;
s = s.replaceAll("&nbsp;", " ").replaceAll("\\s+", " ");
LogicalPosition logicalPosition = myEditor.offsetToLogicalPosition(hEndOffset);
int endOfLineOffset = myEditor.getDocument().getLineEndOffset(logicalPosition.line);
logicalPosition = myEditor.offsetToLogicalPosition(endOfLineOffset);
Point placeToShow = myEditor.logicalPositionToXY(logicalPosition);
logicalPosition = myEditor.xyToLogicalPosition(placeToShow);//wraps&foldings workaround
placeToShow.x += R * 3 / 2;
placeToShow.y -= myCacheStartLine * myEditor.getLineHeight() - 1;
Font font = myEditor.getColorsScheme().getFont(EditorFontType.PLAIN);
g2d.setFont(font.deriveFont(font.getSize() *.8F));
int w = g2d.getFontMetrics().stringWidth(s);
int rightEdge = rightEdges.get(logicalPosition.line);
placeToShow.x = Math.max(placeToShow.x, rightEdge);
rightEdge = Math.max(rightEdge, placeToShow.x + w + 3 * R);
rightEdges.put(logicalPosition.line, rightEdge);
g2d.setColor(MessageType.WARNING.getPopupBackground());
g2d.fillRoundRect(placeToShow.x, placeToShow.y, w + 2 * R, h, R, R);
g2d.setColor(new JBColor(JBColor.GRAY, Gray._200));
g2d.drawRoundRect(placeToShow.x, placeToShow.y, w + 2 * R, h, R, R);
g2d.setColor(JBColor.foreground());
g2d.drawString(s, placeToShow.x + R, placeToShow.y + h - g2d.getFontMetrics(g2d.getFont()).getDescent()/2 - 2);
}
isDirty = false;
}
Graphics2D g2 = (Graphics2D)g.create();
try {
GraphicsUtil.setupAAPainting(g2);
g2.setClip(new RoundRectangle2D.Double(0, 0, size.width-.5, size.height-.5, 2, 2));
UIUtil.drawImage(g2, myCacheLevel1, 0, 0, this);
if (UIUtil.isUnderDarcula()) {
//Add glass effect
Shape s = new Rectangle(0, 0, size.width, size.height);
double cx = size.width / 2;
double cy = 0;
double rx = size.width / 10;
int ry = myEditor.getLineHeight() * 3 / 2;
g2.setPaint(new GradientPaint(0, 0, Gray._255.withAlpha(75), 0, ry, Gray._255.withAlpha(10)));
double pseudoMajorAxis = size.width - rx * 9 / 5;
Shape topShape1 = new Ellipse2D.Double(cx - rx - pseudoMajorAxis / 2, cy - ry, 2 * rx, 2 * ry);
Shape topShape2 = new Ellipse2D.Double(cx - rx + pseudoMajorAxis / 2, cy - ry, 2 * rx, 2 * ry);
Area topArea = new Area(topShape1);
topArea.add(new Area(topShape2));
topArea.add(new Area(new Rectangle.Double(cx - pseudoMajorAxis / 2, cy, pseudoMajorAxis, ry)));
g2.fill(topArea);
Area bottomArea = new Area(s);
bottomArea.subtract(topArea);
g2.setPaint(new GradientPaint(0, size.height - ry, Gray._0.withAlpha(10), 0, size.height, Gray._255.withAlpha(30)));
g2.fill(bottomArea);
}
}
finally {
g2.dispose();
}
}
};
myEditorPreviewHint = new LightweightHint(editorFragmentPreviewPanel) {
@Override
public void hide(boolean ok) {
super.hide(ok);
myCacheLevel1 = null;
if (myCacheLevel2 != null) {
myCacheLevel2 = null;
myCacheStartLine = -1;
myCacheEndLine = -1;
}
myDelayed = false;
}
};
myEditorPreviewHint.setForceLightweightPopup(true);
}
Point point = new Point(hintInfo.getOriginalPoint());
hintInfo.setTextBg(myEditor.getColorsScheme().getDefaultBackground());
hintInfo.setBorderColor(myEditor.getColorsScheme().getDefaultForeground());
point = SwingUtilities.convertPoint(((EditorImpl)editor).getVerticalScrollBar(), point, myEditor.getComponent().getRootPane());
myPointHolder.set(point);
myHintHolder.set(hintInfo);
if (needDelay && !myShowInstantly) {
myDelayed = true;
Alarm alarm = new Alarm();
alarm.addRequest(new Runnable() {
@Override
public void run() {
if (myEditorPreviewHint == null || !myDelayed) return;
showEditorHint(hintManager, myPointHolder.get(), myHintHolder.get());
myDelayed = false;
}
}, /*Registry.intValue("ide.tooltip.initialDelay")*/300);
}
else if (!myDelayed) {
showEditorHint(hintManager, point, hintInfo);
}
return myEditorPreviewHint;
}
private void showEditorHint(HintManagerImpl hintManager, Point point, HintHint hintInfo) {
int flags = HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_MOUSEOVER |
HintManager.HIDE_BY_ESCAPE | HintManager.HIDE_BY_SCROLLING;
hintManager.showEditorHint(myEditorPreviewHint, myEditor, point, flags, 0, false, hintInfo);
}
}
}