/*
* Copyright 2003-2016 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.
*/
package jetbrains.mps.nodeEditor.leftHighlighter;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.util.containers.SortedList;
import com.intellij.util.ui.UIUtil;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectProcedure;
import jetbrains.mps.ide.actions.MPSActions;
import jetbrains.mps.ide.actions.MPSCommonDataKeys;
import jetbrains.mps.ide.editor.MPSEditorDataKeys;
import jetbrains.mps.ide.tooltips.MPSToolTipManager;
import jetbrains.mps.ide.tooltips.TooltipComponent;
import jetbrains.mps.nodeEditor.EditorComponent;
import jetbrains.mps.nodeEditor.EditorMessage;
import jetbrains.mps.nodeEditor.EditorMessageIconRenderer;
import jetbrains.mps.nodeEditor.EditorMessageIconRenderer.IconRendererType;
import jetbrains.mps.nodeEditor.EditorSettings;
import jetbrains.mps.nodeEditor.cells.APICellAdapter;
import jetbrains.mps.nodeEditor.cells.EditorCell_Label;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.update.UpdaterListenerAdapter;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.workbench.action.ActionUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeUtil;
import org.jetbrains.mps.openapi.module.SRepository;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
/**
* This class should be called in UI (EventDispatch) thread only
*/
public class LeftEditorHighlighter extends JComponent implements TooltipComponent {
private static final Logger LOG = LogManager.getLogger(LeftEditorHighlighter.class);
public static final String ICON_AREA = "LeftEditorHighlighterIconArea";
private static final int MIN_LEFT_TEXT_WIDTH = 0;
private static final int MIN_ICON_RENDERERS_WIDTH = 14;
private static final int MIN_LEFT_FOLDING_AREA_WIDTH = 7;
private static final int MIN_RIGHT_FOLDING_AREA_WIDTH = 4;
private static final int GAP_BETWEEN_ICONS = 3;
private static final int LEFT_GAP = 1;
private static final int FOLDING_LINE_WIDTH = 1;
private static final Comparator<AbstractFoldingAreaPainter> FOLDING_AREA_PAINTERS_COMPARATOR = new Comparator<AbstractFoldingAreaPainter>() {
@Override
public int compare(AbstractFoldingAreaPainter afap1, AbstractFoldingAreaPainter afap2) {
if (afap1.getWeight() == afap2.getWeight() && !afap1.equals(afap2)) {
return System.identityHashCode(afap1) - System.identityHashCode(afap2);
}
return afap1.getWeight() - afap2.getWeight();
}
};
private EditorComponent myEditorComponent;
private NavigableSet<AbstractFoldingAreaPainter> myFoldingAreaPainters = new TreeSet<AbstractFoldingAreaPainter>(FOLDING_AREA_PAINTERS_COMPARATOR);
private BracketsPainter myBracketsPainter;
private FoldingButtonsPainter myFoldingButtonsPainter;
private List<AbstractLeftColumn> myLeftColumns = new ArrayList<AbstractLeftColumn>();
private Set<EditorMessageIconRenderer> myIconRenderers = new HashSet<EditorMessageIconRenderer>();
private TIntObjectHashMap<List<IconRendererLayoutConstraint>> myLineToRenderersMap = new TIntObjectHashMap<List<IconRendererLayoutConstraint>>();
private Comparator myIconRenderersComparator = new IconRendererLayoutConstraintComparator();
private EditorMessageIconRenderer myRendererUnderMouse;
private int myMaxIconHeight = 0;
private boolean myMouseIsInFoldingArea;
private int myFoldingLineX;
private int myIconRenderersWidth;
private int myTextColumnWidth;
private int myLeftFoldingAreaWidth;
private int myRightFoldingAreaWidth;
private int myWidth;
private int myHeight;
private boolean myRightToLeft;
public LeftEditorHighlighter(@NotNull EditorComponent editorComponent, boolean rightToLeft) {
setBackground(EditorSettings.getInstance().getLeftHighlighterBackgroundColor());
myEditorComponent = editorComponent;
myRightToLeft = rightToLeft;
addMouseListener(new MouseAdapter() {
@Override
public void mouseExited(MouseEvent e) {
mouseExitedFoldingArea(e);
mouseExitedIconsArea(e);
}
@Override
public void mouseEntered(MouseEvent e) {
if (isInFoldingArea(e)) {
mouseMovedInFoldingArea(e);
} else if (isInTextArea(e)) {
mouseMovedInTextArea(e);
} else {
mouseMovedInIconsArea(e);
}
}
});
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if (isInFoldingArea(e)) {
mouseExitedIconsArea(e);
mouseMovedInFoldingArea(e);
} else if (isInTextArea(e)) {
mouseExitedFoldingArea(e);
mouseExitedIconsArea(e);
mouseMovedInTextArea(e);
} else {
mouseExitedFoldingArea(e);
mouseMovedInIconsArea(e);
}
}
});
if (MPSToolTipManager.getInstance() != null) {
MPSToolTipManager.getInstance().registerComponent(this);
}
editorComponent.getUpdater().addListener(new UpdaterListenerAdapter() {
@Override
public void editorUpdated(jetbrains.mps.openapi.editor.EditorComponent editorComponent) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter$RebuildListener should be called in eventDispatchThread";
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
painter.editorRebuilt();
}
relayout(true);
}
});
myBracketsPainter = new BracketsPainter(this, myRightToLeft);
myFoldingButtonsPainter = new FoldingButtonsPainter(this);
myFoldingAreaPainters.add(myBracketsPainter);
myFoldingAreaPainters.add(myFoldingButtonsPainter);
}
@NotNull
public EditorComponent getEditorComponent() {
return myEditorComponent;
}
public int getRightFoldingAreaWidth() {
return myRightFoldingAreaWidth;
}
public int getFoldingLineX() {
return myFoldingLineX;
}
public int getIconRenderersOffset() {
return myRightToLeft ? getFoldingAreaWidth() : myTextColumnWidth;
}
private int getFoldingAreaOffset() {
return myRightToLeft ? 0 : myTextColumnWidth + myIconRenderersWidth;
}
private int getTextColumnOffset() {
return myRightToLeft ? getFoldingAreaWidth() + myIconRenderersWidth : 0;
}
private int getFoldingAreaWidth() {
return myLeftFoldingAreaWidth + FOLDING_LINE_WIDTH + myRightFoldingAreaWidth;
}
private boolean isInside(int offset, int width, int x) {
return offset <= x && x < offset + width;
}
private boolean hasIntersection(int x1, int width1, Rectangle rectangle) {
int x2 = rectangle.x;
int width2 = rectangle.width;
return x1 <= x2 ? x1 + width1 >= x2 : x2 + width2 >= x1;
}
public void dispose() {
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
painter.dispose();
}
for (AbstractLeftColumn column : myLeftColumns) {
column.dispose();
}
if (MPSToolTipManager.getInstance() != null) {
MPSToolTipManager.getInstance().unregisterComponent(this);
}
}
public void setDefaultFoldingAreaPaintersEnabled(boolean enabled) {
if (enabled) {
addFoldingAreaPainter(myBracketsPainter);
addFoldingAreaPainter(myFoldingButtonsPainter);
} else {
removeFoldingAreaPainter(myBracketsPainter);
removeFoldingAreaPainter(myFoldingButtonsPainter);
}
}
public void addFoldingAreaPainter(AbstractFoldingAreaPainter painter) {
if (myFoldingAreaPainters.contains(painter)) {
return;
}
myFoldingAreaPainters.add(painter);
relayout(true);
repaint();
}
public void removeFoldingAreaPainter(AbstractFoldingAreaPainter painter) {
if (!myFoldingAreaPainters.contains(painter)) {
return;
}
myFoldingAreaPainters.remove(painter);
relayout(true);
repaint();
}
public void addLeftColumn(AbstractLeftColumn column) {
myLeftColumns.add(column);
relayoutOnLeftColumnChange();
repaint();
}
public void removeLeftColumn(AbstractLeftColumn column) {
myLeftColumns.remove(column);
relayoutOnLeftColumnChange();
repaint();
}
public List<AbstractLeftColumn> getLeftColumns() {
return myLeftColumns;
}
@Override
public void paintComponent(Graphics g) {
Rectangle clipBounds = g.getClipBounds();
paintBackgroundAndFoldingLine(g, clipBounds);
paintTextColumns(g, clipBounds);
paintIconRenderers(g, clipBounds);
paintFoldingArea(g, clipBounds);
}
private void paintFoldingArea(Graphics g, Rectangle clipBounds) {
if (!hasIntersection(getFoldingAreaOffset(), getFoldingAreaWidth(), clipBounds)) {
return;
}
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
painter.paint(g);
}
}
private void paintBackgroundAndFoldingLine(Graphics g, Rectangle clipBounds) {
Graphics2D g2d = (Graphics2D) g;
g.setColor(getBackground());
g.fillRect(clipBounds.x, clipBounds.y, Math.min(clipBounds.width, myFoldingLineX - clipBounds.x), clipBounds.height);
g.setColor(getEditorComponent().getBackground());
g.fillRect(Math.max(clipBounds.x, myFoldingLineX), clipBounds.y, clipBounds.width - Math.max(0, myFoldingLineX - clipBounds.x), clipBounds.height);
// same as in EditorComponent.paint() method
EditorCell deepestCell = myEditorComponent.getDeepestSelectedCell();
if (deepestCell instanceof EditorCell_Label) {
int selectedCellY = deepestCell.getY();
int selectedCellHeight = deepestCell.getHeight() - deepestCell.getTopInset() - deepestCell.getBottomInset();
if (g.hitClip(clipBounds.x, selectedCellY, clipBounds.width, selectedCellHeight)) {
g.setColor(EditorSettings.getInstance().getCaretRowColor());
g.fillRect(clipBounds.x, selectedCellY, clipBounds.width, selectedCellHeight);
// Drawing folding line
UIUtil.drawVDottedLine(g2d, myFoldingLineX, clipBounds.y, selectedCellY, getBackground(),
EditorSettings.getInstance().getLeftHighlighterTearLineColor());
UIUtil.drawVDottedLine(g2d, myFoldingLineX, selectedCellY, selectedCellY + selectedCellHeight, EditorSettings.getInstance().getCaretRowColor(),
EditorSettings.getInstance().getLeftHighlighterTearLineColor());
UIUtil.drawVDottedLine(g2d, myFoldingLineX, selectedCellY + selectedCellHeight, clipBounds.y + clipBounds.height, getBackground(),
EditorSettings.getInstance().getLeftHighlighterTearLineColor());
return;
}
}
// Drawing folding line
// COLORS: Remove hardcoded color
UIUtil.drawVDottedLine(g2d, myFoldingLineX, clipBounds.y, clipBounds.y + clipBounds.height, getBackground(), Color.gray);
}
private void paintIconRenderers(final Graphics g, Rectangle clipBounds) {
if (!hasIntersection(getIconRenderersOffset(), myIconRenderersWidth, clipBounds)) {
return;
}
final int startY = clipBounds.y;
final int endY = clipBounds.y + clipBounds.height;
myLineToRenderersMap.forEachEntry(new TIntObjectProcedure<List<IconRendererLayoutConstraint>>() {
@Override
public boolean execute(int y, List<IconRendererLayoutConstraint> rendererConstraints) {
if (startY <= y && y <= endY) {
for (IconRendererLayoutConstraint constraint : rendererConstraints) {
constraint.getIconRenderer().getIcon().paintIcon(LeftEditorHighlighter.this, g, constraint.getX(), y);
}
}
return true;
}
});
}
private void paintTextColumns(Graphics g, Rectangle clipBounds) {
if (!hasIntersection(getTextColumnOffset(), myTextColumnWidth, clipBounds)) {
return;
}
for (AbstractLeftColumn column : myLeftColumns) {
if (clipBounds.x > column.getX() + column.getWidth()) {
continue;
}
column.paint(g);
// COLORS: find out where it is and remove hardcoded color
UIUtil.drawVDottedLine((Graphics2D) g, myRightToLeft ? column.getX() : column.getX() + column.getWidth() - 1,
(int) clipBounds.getMinY(), (int) clipBounds.getMaxY(), getBackground(), Color.GRAY);
}
}
public void unHighlight(EditorCell cell) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.unHighlight() should be called in eventDispatchThread";
myBracketsPainter.removeBracket(cell);
relayout(true);
repaint();
}
public void highlight(EditorCell cell, EditorCell cell2, Color c) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.highlight() should be called in eventDispatchThread";
assert cell.getEditorComponent() == myEditorComponent : "cell must be from my editor";
myBracketsPainter.addBracket(cell, cell2, c);
relayout(true);
repaint();
}
public void relayout(boolean updateFolding) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.relayout() should be executed in eventDispatchThread";
final SNode editedNode = myEditorComponent.getEditedNode();
// additional check - during editor dispose process some Folding area painters can be removed calling relayout()..
if (myEditorComponent.isDisposed()) return;
if (editedNode != null) {
SRepository repository = myEditorComponent.getEditorContext().getRepository();
if (new ModelAccessHelper(repository).runReadAction(() -> !SNodeUtil.isAccessible(editedNode, repository))) {
return;
}
}
if (myRightToLeft) {
recalculateFoldingAreaWidth();
updateSeparatorLinePosition();
if (updateFolding) {
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
painter.relayout();
}
// wee need to recalculateIconRenderersWidth only if one of collections was folded/unfolded
recalculateIconRenderersWidth();
}
recalculateTextColumnWidth();
} else {
recalculateTextColumnWidth();
if (updateFolding) {
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
painter.relayout();
}
// wee need to recalculateIconRenderersWidth only if one of collections was folded/unfolded
recalculateIconRenderersWidth();
}
recalculateFoldingAreaWidth();
updateSeparatorLinePosition();
}
updatePreferredSize();
}
// Optimization: partly layouting
private void relayoutOnIconRendererChanges() {
if (myRightToLeft) {
recalculateIconRenderersWidth();
recalculateTextColumnWidth();
} else {
recalculateIconRenderersWidth();
updateSeparatorLinePosition();
}
updatePreferredSize();
}
// Optimization: partly layouting
private void relayoutOnLeftColumnChange() {
if (myRightToLeft) {
recalculateTextColumnWidth();
} else {
recalculateTextColumnWidth();
recalculateIconRenderersWidth();
updateSeparatorLinePosition();
}
updatePreferredSize();
}
private void recalculateFoldingAreaWidth() {
myLeftFoldingAreaWidth = myRightToLeft ? MIN_RIGHT_FOLDING_AREA_WIDTH : MIN_LEFT_FOLDING_AREA_WIDTH;
myRightFoldingAreaWidth = myRightToLeft ? MIN_LEFT_FOLDING_AREA_WIDTH : MIN_RIGHT_FOLDING_AREA_WIDTH;
// Layouting painters
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
myLeftFoldingAreaWidth = Math.max(myLeftFoldingAreaWidth, painter.getLeftAreaWidth());
myRightFoldingAreaWidth = Math.max(myRightFoldingAreaWidth, painter.getRightAreaWidth());
}
}
public void addIconRenderer(EditorMessageIconRenderer renderer) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.addIconRenderer() should be called in eventDispatchThread";
myIconRenderers.add(renderer);
relayoutOnIconRendererChanges();
}
public void addAllIconRenderers(Collection<EditorMessageIconRenderer> renderers) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.addAllIconRenderers() should be called in eventDispatchThread";
myIconRenderers.addAll(renderers);
relayoutOnIconRendererChanges();
}
public void removeIconRenderer(EditorMessageIconRenderer renderer) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.removeIconRenderer() should be called in eventDispatchThread";
if (myIconRenderers.remove(renderer)) {
relayoutOnIconRendererChanges();
}
}
public void removeIconRenderer(SNode snode, IconRendererType type) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.removeIconRenderer() should be called in eventDispatchThread";
boolean wasModified = false;
for (Iterator<EditorMessageIconRenderer> it = myIconRenderers.iterator(); it.hasNext(); ) {
EditorMessageIconRenderer renderer = it.next();
// XXX I would prefer removeIconRenderer that takes SNodeReference instead of an SNode (e.g. BreakpointsUiComponentEx won't need to record
// renderer instances then).
if (renderer.getNode() == snode && (type == null || renderer.getType() == type)) {
it.remove();
wasModified = true;
}
}
if (wasModified) {
relayoutOnIconRendererChanges();
}
}
public void removeAllIconRenderers(Collection<? extends EditorMessageIconRenderer> renderers) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.removeAllIconRenderers() should be called in eventDispatchThread";
if (myIconRenderers.removeAll(renderers)) {
relayoutOnIconRendererChanges();
}
}
public void removeAllIconRenderers(IconRendererType type) {
assert SwingUtilities.isEventDispatchThread() : "LeftEditorHighlighter.removeAllIconRenderers() should be called in eventDispatchThread";
boolean wasModified = false;
for (Iterator<EditorMessageIconRenderer> it = myIconRenderers.iterator(); it.hasNext(); ) {
EditorMessageIconRenderer renderer = it.next();
if (renderer.getType() == type) {
it.remove();
wasModified = true;
}
}
if (wasModified) {
relayoutOnIconRendererChanges();
}
}
private void recalculateIconRenderersWidth() {
myLineToRenderersMap.clear();
for (EditorMessageIconRenderer renderer : myIconRenderers) {
int yCoordinate = getIconCoordinate(renderer);
if (yCoordinate < 0) {
continue;
}
List<IconRendererLayoutConstraint> renderersForLine = myLineToRenderersMap.get(yCoordinate);
if (renderersForLine == null) {
renderersForLine = new SortedList(myIconRenderersComparator);
myLineToRenderersMap.put(yCoordinate, renderersForLine);
}
renderersForLine.add(new IconRendererLayoutConstraint(renderer));
}
myIconRenderersWidth = MIN_ICON_RENDERERS_WIDTH;
myMaxIconHeight = 0;
int[] sortedYCoordinates = myLineToRenderersMap.keys();
Arrays.sort(sortedYCoordinates);
int initialOffset = getIconRenderersOffset();
for (int y : sortedYCoordinates) {
List<IconRendererLayoutConstraint> row = myLineToRenderersMap.get(y);
assert row.size() != 0;
int maxIconHeight = 0;
for (IconRendererLayoutConstraint rendererConstraint : row) {
maxIconHeight = Math.max(maxIconHeight, rendererConstraint.getIconRenderer().getIcon().getIconHeight());
}
myMaxIconHeight = Math.max(myMaxIconHeight, maxIconHeight);
int offset = initialOffset + LEFT_GAP;
for (Iterator<IconRendererLayoutConstraint> it = row.iterator(); it.hasNext(); ) {
IconRendererLayoutConstraint rendererConstraint = it.next();
rendererConstraint.setX(offset);
offset += rendererConstraint.getIconRenderer().getIcon().getIconWidth();
if (it.hasNext()) {
offset += GAP_BETWEEN_ICONS;
}
}
myIconRenderersWidth = Math.max(myIconRenderersWidth, offset - initialOffset);
}
}
private void recalculateTextColumnWidth() {
int initialOffset = getTextColumnOffset();
int offset = initialOffset;
for (AbstractLeftColumn column : myLeftColumns) {
column.setX(offset);
column.relayout();
offset += column.getWidth();
}
myTextColumnWidth = Math.max(MIN_LEFT_TEXT_WIDTH, offset - initialOffset);
}
private void updateSeparatorLinePosition() {
myFoldingLineX = getFoldingAreaOffset() + myLeftFoldingAreaWidth;
}
private void updatePreferredSize() {
int newWidth = myTextColumnWidth + myIconRenderersWidth + getFoldingAreaWidth();
int newHeight = myEditorComponent.getPreferredSize().height;
if (myWidth != newWidth || myHeight != newHeight) {
myWidth = newWidth;
myHeight = newHeight;
firePreferredSizeChanged();
}
}
private void firePreferredSizeChanged() {
processComponentEvent(new ComponentEvent(this, ComponentEvent.COMPONENT_RESIZED));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(myWidth + 1, myEditorComponent.getPreferredSize().height);
}
public int getIconCoordinate(EditorMessageIconRenderer renderer) {
jetbrains.mps.openapi.editor.cells.EditorCell anchorCell = getAnchorCell(renderer);
if (anchorCell == null || APICellAdapter.isUnderFolded(anchorCell)) {
// no anchorCell
return -1;
}
if (renderer.getIcon() == null) {
LOG.error("null icon was returned by renderer: " + renderer);
return -1;
}
return anchorCell.getY() + anchorCell.getHeight() / 2 - renderer.getIcon().getIconHeight() / 2;
}
private jetbrains.mps.openapi.editor.cells.EditorCell getAnchorCell(final EditorMessageIconRenderer renderer) {
final jetbrains.mps.openapi.editor.cells.EditorCell[] cell = new jetbrains.mps.openapi.editor.cells.EditorCell[1];
myEditorComponent.getEditorContext().getRepository().getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
SNode rendererNode = renderer.getNode();
EditorCell nodeCell = myEditorComponent.findNodeCell(rendererNode);
if (nodeCell != null) {
cell[0] = renderer.getAnchorCell(nodeCell);
}
// no cell for node?..
}
});
return cell[0];
}
@Override
public String getMPSTooltipText(MouseEvent e) {
if (isInFoldingArea(e)) {
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
if (painter.getToolTipText() != null) {
return painter.getToolTipText();
}
}
} else if (isInTextArea(e)) {
AbstractLeftColumn column = getTextColumnByX(e.getX());
if (column != null) {
return column.getTooltipText(e);
}
} else {
EditorMessageIconRenderer iconRenderer = getIconRendererUnderMouse(e);
if (iconRenderer != null) {
return iconRenderer.getTooltipText();
}
}
return null;
}
@Override
protected void processMouseEvent(MouseEvent e) {
switch (e.getID()) {
case MouseEvent.MOUSE_PRESSED:
case MouseEvent.MOUSE_RELEASED:
case MouseEvent.MOUSE_CLICKED:
if (isInFoldingArea(e)) {
mousePressedInFoldingArea(e);
} else if (isInTextArea(e)) {
mousePressedInTextArea(e);
} else {
mousePressedInIconsArea(e);
}
if (!e.isConsumed() && e.getButton() == MouseEvent.BUTTON3 && e.getID() == MouseEvent.MOUSE_PRESSED) {
DefaultActionGroup actionGroup = ActionUtils.getDefaultGroup(MPSActions.EDITOR_LEFTPANEL_GROUP);
if (actionGroup != null) {
ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.EDITOR_POPUP, actionGroup);
popupMenu.getComponent().show(e.getComponent(), e.getX(), e.getY());
e.consume();
}
}
}
// suppressing future event processig in case event was consumed by one of LeftHighlighter elements
if (!e.isConsumed()) {
super.processMouseEvent(e);
}
}
private void mousePressedInIconsArea(MouseEvent e) {
EditorMessageIconRenderer iconRenderer = getIconRendererUnderMouse(e);
if (iconRenderer != null) {
if (e.getButton() == MouseEvent.BUTTON3) {
JPopupMenu popupMenu = iconRenderer.getPopupMenu();
if (popupMenu != null && e.getID() == MouseEvent.MOUSE_PRESSED) {
e.consume();
Component component = e.getComponent();
popupMenu.show(component == null ? myEditorComponent : component, e.getX(), e.getY());
}
return;
}
AnAction action = iconRenderer.getClickAction();
if (e.getButton() == MouseEvent.BUTTON1 && action != null) {
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
AnActionEvent actionEvent =
new AnActionEvent(e, new LeftEditorHighlighterDataContext(myEditorComponent, iconRenderer.getNode()), ICON_AREA, action.getTemplatePresentation(),
ActionManager.getInstance(), e.getModifiers());
action.update(actionEvent);
action.actionPerformed(actionEvent);
}
e.consume();
}
}
}
private void mousePressedInFoldingArea(MouseEvent e) {
for (Iterator<AbstractFoldingAreaPainter> it = myFoldingAreaPainters.descendingIterator(); it.hasNext(); ) {
AbstractFoldingAreaPainter painter = it.next();
painter.mousePressed(e);
if (e.isConsumed()) {
break;
}
}
}
private void mousePressedInTextArea(MouseEvent e) {
if (e.isConsumed()) return;
AbstractLeftColumn column = getTextColumnByX(e.getX());
if (column != null) {
column.mousePressed(e);
e.consume();
}
}
private void mouseExitedFoldingArea(MouseEvent e) {
if (myMouseIsInFoldingArea) {
for (AbstractFoldingAreaPainter painter : myFoldingAreaPainters) {
painter.mouseExited(e);
}
}
}
private void mouseMovedInFoldingArea(MouseEvent e) {
myMouseIsInFoldingArea = true;
for (Iterator<AbstractFoldingAreaPainter> it = myFoldingAreaPainters.descendingIterator(); it.hasNext(); ) {
AbstractFoldingAreaPainter painter = it.next();
painter.mouseMoved(e);
if (e.isConsumed()) {
break;
}
}
}
private void mouseExitedIconsArea(MouseEvent e) {
if (!myMouseIsInFoldingArea && myRendererUnderMouse != null && !isInTextArea(e)) {
setCursor(null);
}
}
private void mouseMovedInIconsArea(MouseEvent e) {
myMouseIsInFoldingArea = false;
EditorMessageIconRenderer newRendererUnderMouse = getIconRendererUnderMouse(e);
if (newRendererUnderMouse != null) {
setCursor(newRendererUnderMouse.getMouseOverCursor());
} else if (myRendererUnderMouse != null) {
setCursor(null);
}
myRendererUnderMouse = newRendererUnderMouse;
}
private void mouseMovedInTextArea(MouseEvent e) {
myMouseIsInFoldingArea = false;
AbstractLeftColumn textColumn = getTextColumnByX(e.getX());
if (textColumn != null) {
setCursor(textColumn.getCursor(e));
} else {
setCursor(null);
}
}
private boolean isInFoldingArea(MouseEvent e) {
return isInside(getFoldingAreaOffset(), getFoldingAreaWidth(), e.getX());
}
private boolean isInTextArea(MouseEvent e) {
return isInside(getTextColumnOffset(), myTextColumnWidth, e.getX());
}
private EditorMessageIconRenderer getIconRendererUnderMouse(MouseEvent e) {
final int mouseX = e.getX();
final int mouseY = e.getY();
final EditorMessageIconRenderer[] theRenderer = new EditorMessageIconRenderer[]{null};
myLineToRenderersMap.forEachEntry(new TIntObjectProcedure<List<IconRendererLayoutConstraint>>() {
@Override
public boolean execute(int y, List<IconRendererLayoutConstraint> layoutConstraints) {
if (y <= mouseY && mouseY <= y + myMaxIconHeight) {
for (IconRendererLayoutConstraint constraint : layoutConstraints) {
int x = constraint.getX();
if (y <= mouseY && mouseY <= y + constraint.getIconRenderer().getIcon().getIconHeight() &&
x <= mouseX && mouseX <= x + constraint.getIconRenderer().getIcon().getIconWidth()) {
theRenderer[0] = constraint.getIconRenderer();
return false;
}
}
}
return true;
}
});
return theRenderer[0];
}
private AbstractLeftColumn getTextColumnByX(int x) {
for (AbstractLeftColumn column : myLeftColumns) {
int columnX = column.getX();
if (columnX <= x && columnX + column.getWidth() > x) {
return column;
}
}
return null;
}
private class IconRendererLayoutConstraintComparator implements Comparator<IconRendererLayoutConstraint> {
@Override
public int compare(IconRendererLayoutConstraint constraint1, IconRendererLayoutConstraint constraint2) {
if (constraint1 == constraint2) {
return 0;
}
EditorMessageIconRenderer renderer1 = constraint1.getIconRenderer();
EditorMessageIconRenderer renderer2 = constraint2.getIconRenderer();
if (renderer1.getType() != renderer2.getType()) {
return renderer1.getType().getWeight() - renderer2.getType().getWeight();
}
jetbrains.mps.openapi.editor.cells.EditorCell anchorCell1 = getAnchorCell(renderer1);
jetbrains.mps.openapi.editor.cells.EditorCell anchorCell2 = getAnchorCell(renderer2);
// [++] Debugging assertion
if (anchorCell1 == anchorCell2 && renderer1 instanceof EditorMessage && renderer2 instanceof EditorMessage) {
EditorMessage editorMessage1 = (EditorMessage) renderer1;
EditorMessage editorMessage2 = (EditorMessage) renderer2;
assert false :
"Two EditorMessages with same type are attached to the same EditorCell: m1 = " +
editorMessage1 + ", m2 = " + editorMessage2 +
"; owner1 = " + editorMessage1.getOwner() + ", owner2 = " + editorMessage2.getOwner();
}
// [--] Debugging assertion
if (anchorCell1 != null) {
if (anchorCell2 == null) {
return 1;
} else {
return anchorCell1.getX() - anchorCell2.getX();
}
} else if (anchorCell2 != null) {
return -1;
}
return 0;
}
}
private static class IconRendererLayoutConstraint {
private EditorMessageIconRenderer myIconRenderer;
private int myX;
public IconRendererLayoutConstraint(EditorMessageIconRenderer iconRenderer) {
myIconRenderer = iconRenderer;
}
public void setX(int x) {
myX = x;
}
public int getX() {
return myX;
}
public EditorMessageIconRenderer getIconRenderer() {
return myIconRenderer;
}
}
private static class LeftEditorHighlighterDataContext implements DataContext {
private DataContext myEditorDataContext;
private SNode mySelectedNode;
private EditorCell myNodeCell;
public LeftEditorHighlighterDataContext(@NotNull EditorComponent editorComponent, SNode selectedNode) {
myEditorDataContext = DataManager.getInstance().getDataContext(editorComponent);
mySelectedNode = selectedNode;
myNodeCell = editorComponent.findNodeCell(mySelectedNode);
}
@Override
public Object getData(@NonNls String dataId) {
if (MPSCommonDataKeys.NODE.getName().equals(dataId)) {
return mySelectedNode;
}
if (MPSEditorDataKeys.EDITOR_CELL.getName().equals(dataId)) {
return myNodeCell;
}
return myEditorDataContext.getData(dataId);
}
}
}