/*
* Copyright 2003-2011 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.cells;
import jetbrains.mps.editor.runtime.cells.AbstractCellAction;
import jetbrains.mps.editor.runtime.style.Padding;
import jetbrains.mps.editor.runtime.style.StyleAttributes;
import jetbrains.mps.editor.runtime.style.StyleImpl;
import jetbrains.mps.nodeEditor.EditorCell_WithComponent;
import jetbrains.mps.nodeEditor.EditorComponent;
import jetbrains.mps.nodeEditor.cellLayout.CellLayout;
import jetbrains.mps.nodeEditor.cellLayout.CellLayout_Flow;
import jetbrains.mps.nodeEditor.cellLayout.CellLayout_Horizontal;
import jetbrains.mps.nodeEditor.cellLayout.CellLayout_Indent;
import jetbrains.mps.nodeEditor.cellLayout.CellLayout_Superscript;
import jetbrains.mps.nodeEditor.cellLayout.CellLayout_Table;
import jetbrains.mps.nodeEditor.cellLayout.CellLayout_Vertical;
import jetbrains.mps.nodeEditor.cellProviders.AbstractCellListHandler;
import jetbrains.mps.nodeEditor.cells.collections.Container;
import jetbrains.mps.nodeEditor.cells.collections.EmptyContainer;
import jetbrains.mps.nodeEditor.cells.collections.SingletonContainer;
import jetbrains.mps.nodeEditor.cells.collections.UnmodifiableIterator;
import jetbrains.mps.openapi.editor.EditorContext;
import jetbrains.mps.openapi.editor.TextBuilder;
import jetbrains.mps.openapi.editor.cells.CellAction;
import jetbrains.mps.openapi.editor.cells.CellActionType;
import jetbrains.mps.openapi.editor.cells.CellInfo;
import jetbrains.mps.openapi.editor.cells.CellTraversalUtil;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.SubstituteInfo;
import jetbrains.mps.openapi.editor.cells.traversal.CellTreeIterable;
import jetbrains.mps.openapi.editor.selection.Selection;
import jetbrains.mps.openapi.editor.selection.SelectionListener;
import jetbrains.mps.openapi.editor.style.Style;
import jetbrains.mps.util.ConditionalIterable;
import jetbrains.mps.util.NameUtil;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.util.TreeIterator;
import org.jetbrains.mps.util.Condition;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Author: Sergey Dmitriev
* Created Sep 14, 2003
*/
public class EditorCell_Collection extends EditorCell_Basic implements jetbrains.mps.openapi.editor.cells.EditorCell_Collection, SynchronizeableEditorCell {
private static Logger LOG = LogManager.getLogger(EditorCell_Collection.class);
public static final String FOLDED_TEXT = "...";
private Container<EditorCell> myEditorCells;
private EditorCell myFoldedCell;
@NotNull
protected CellLayout myCellLayout;
private AbstractCellListHandler myCellListHandler;
private EditorCell_Brace myOpeningBrace;
private EditorCell_Brace myClosingBrace;
private MyLastCellSelectionListener myLastCellSelectionListener;
private boolean myUsesBraces = false;
private boolean myBracesEnabled = false;
private int myArtificialBracesIndent = 0;
private Boolean myCollapsed;
private boolean myInitiallyCollapsed = false;
private boolean myCanBeFolded = false;
private int myAscent = -1;
private int myDescent = -1;
private MouseListener myUnfoldCollectionMouseListener;
private boolean myCanBeSynchronized;
public static EditorCell_Collection createVertical(EditorContext editorContext, SNode node) {
return new EditorCell_Collection(editorContext, node, new CellLayout_Vertical(), null);
}
public static EditorCell_Collection createHorizontal(EditorContext editorContext, SNode node) {
return new EditorCell_Collection(editorContext, node, new CellLayout_Horizontal(), null);
}
public static EditorCell_Collection createIndent2(EditorContext editorContext, SNode node) {
return new EditorCell_Collection(editorContext, node, new CellLayout_Indent(), null);
}
public static EditorCell_Collection createSuperscript(EditorContext editorContext, SNode node) {
return new EditorCell_Collection(editorContext, node, new CellLayout_Superscript(), null);
}
public static EditorCell_Collection createTable(EditorContext editorContext, SNode node) {
return new EditorCell_Collection(editorContext, node, new CellLayout_Table(), null);
}
public static EditorCell_Collection createFlow(EditorContext editorContext, SNode node) {
return new EditorCell_Collection(editorContext, node, new CellLayout_Flow(), null);
}
public static EditorCell_Collection create(EditorContext editorContext, SNode node, CellLayout cellLayout, AbstractCellListHandler handler) {
return new EditorCell_Collection(editorContext, node, cellLayout, handler);
}
public boolean isCollapsed() {
return isDefaultCollapsedValueChanged() ? myCollapsed : myInitiallyCollapsed;
}
/**
* visibility: package-local for testing purposes only
*/
@NotNull
Container<EditorCell> getEditorCells() {
if (myEditorCells == null) {
myEditorCells = new EditorCell_Collection_Container(this);
}
return myEditorCells;
}
@NotNull
private Container<EditorCell> getFoldedCellCollection() {
return hasFoldedCell() ? SingletonContainer.getInstance(getFoldedCell()) : EmptyContainer.getInstance();
}
private EditorCell getFoldedCell() {
assert hasFoldedCell();
if (myFoldedCell == null) {
EditorCell_Constant foldedCell = new EditorCell_Constant(getContext(), getSNode(), FOLDED_TEXT);
Style style = foldedCell.getStyle();
// COLORS: Remove hardcoded colors & font
style.set(StyleAttributes.FONT_STYLE, Font.BOLD);
style.set(StyleAttributes.TEXT_BACKGROUND_COLOR, Color.lightGray);
style.set(StyleAttributes.TEXT_COLOR, Color.darkGray);
style.set(StyleAttributes.SELECTABLE, Boolean.FALSE);
setFoldedCell(foldedCell);
}
return myFoldedCell;
}
public void setFoldedCell(EditorCell foldedCell) {
if (myFoldedCell != null) {
((EditorCell_Basic) myFoldedCell).setParent(null);
getStyle().remove(myFoldedCell.getStyle());
}
myFoldedCell = foldedCell;
((EditorCell_Basic) myFoldedCell).setParent(this);
getStyle().add(myFoldedCell.getStyle());
}
private boolean hasFoldedCell() {
return myCanBeFolded;
}
private Container<EditorCell> getVisibleChildCells() {
return isCollapsed() ? getFoldedCellCollection() : getEditorCells();
}
/**
* @deprecated since MPS 3.4 not used
*/
@Deprecated
@Override
public int indexOf(EditorCell cell) {
int i = 0;
for (EditorCell editorCell : getVisibleChildCells()) {
if (editorCell.equals(cell)) {
return i;
}
i++;
}
return -1;
}
@NotNull
@Override
public CellLayout getCellLayout() {
return myCellLayout;
}
@Override
public boolean isLeaf() {
return false;
}
@SuppressWarnings({"UnusedDeclaration"})
public AbstractCellListHandler getCellListHandler() {
return myCellListHandler;
}
public boolean hasCellListHandler() {
return myCellListHandler != null;
}
public String getCellNodesRole() {
if (myCellListHandler == null) {
return null;
}
return myCellListHandler.getElementRole();
}
@Override
public EditorCell getCellAt(int number) {
if (number < 0 || number >= getVisibleChildCells().size()) {
return null;
}
int i = 0;
for (EditorCell editorCell : getVisibleChildCells()) {
if (i == number) {
return editorCell;
}
i++;
}
// The cell should be found above, so this exception will not be thrown in standard situation
throw new IndexOutOfBoundsException("Size: " + getVisibleChildCells().size() + ", Index: " + number);
}
public void setGridLayout(boolean gridLayout) {
if (myCellLayout instanceof CellLayout_Vertical) {
((CellLayout_Vertical) myCellLayout).setGridLayout(gridLayout);
}
}
@Override
public void setArtificialBracesIndent(int indent) {
myArtificialBracesIndent = indent;
}
@Override
public boolean isAncestorOf(EditorCell cell) {
while (cell != null) {
cell = cell.getParent();
if (cell == this) {
return true;
}
}
return false;
}
@Override
public int getBracesIndent() {
int naturalIndent = usesBraces() ? myOpeningBrace.getWidth() : 0;
return Math.max(myArtificialBracesIndent, naturalIndent);
}
public EditorCell_Collection(EditorContext editorContext, SNode node, @NotNull CellLayout cellLayout) {
super(editorContext, node);
myCellLayout = cellLayout;
this.setAction(CellActionType.LOCAL_HOME, new SelectFirstChild());
this.setAction(CellActionType.LOCAL_END, new SelectLastChild());
}
public EditorCell_Collection(EditorContext editorContext, SNode node, @NotNull CellLayout cellLayout, AbstractCellListHandler handler) {
this(editorContext, node, cellLayout);
myCellListHandler = handler;
}
private void addBraces() {
myOpeningBrace = new EditorCell_Brace(getContext(), getSNode(), true);
myClosingBrace = new EditorCell_Brace(getContext(), getSNode(), false);
if (myLastCellSelectionListener == null) {
myLastCellSelectionListener = new MyLastCellSelectionListener();
}
addEditorCellBefore(myOpeningBrace, firstCell());
addEditorCellAfter(myClosingBrace, lastCell());
}
private void removeBraces() {
removeCell(myOpeningBrace);
removeCell(myClosingBrace);
getEditor().getSelectionManager().removeSelectionListener(myLastCellSelectionListener);
myOpeningBrace = null;
myClosingBrace = null;
myLastCellSelectionListener = null;
}
private void setBracesEnabled(boolean enabled) {
myBracesEnabled = enabled;
getEditor().setBracesEnabled(this, enabled);
}
public void enableBraces() {
setBracesEnabled(true);
myOpeningBrace.setEnabled(true);
myClosingBrace.setEnabled(true);
// COLORS: Remove hardcoded color
getEditor().leftHighlightCell(this, new Color(80, 0, 120));
}
public void disableBraces() {
setBracesEnabled(false);
myOpeningBrace.setEnabled(false);
myClosingBrace.setEnabled(false);
getEditor().leftUnhighlightCell(this);
}
public boolean areBracesEnabled() {
return myBracesEnabled;
}
@Override
public EditorCell getOpeningBrace() {
return myOpeningBrace;
}
@Override
public EditorCell getClosingBrace() {
return myClosingBrace;
}
@Override
public int getAscent() {
return myAscent;
}
@Override
public void setAscent(int newAscent) {
myAscent = newAscent;
}
@Override
public int getDescent() {
return myDescent;
}
@Override
public void setDescent(int newDescent) {
myDescent = newDescent;
}
/**
* @deprecated since MPS 3.4 use getContentCells();
*/
@Deprecated
public Iterable<EditorCell> contentCells() {
if (usesBraces()) {
return () -> new Iterator<EditorCell>() {//iterates from second to before last
private Iterator<EditorCell> myIterator = EditorCell_Collection.this.iterator();
private EditorCell myNext;
{
myIterator.next();
myNext = myIterator.next();
}
@Override
public boolean hasNext() {
return myIterator.hasNext();
}
@Override
public EditorCell next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
EditorCell result = myNext;
myNext = myIterator.next();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
} else {
return this;
}
}
@Override
public Iterator<EditorCell> iterator() {
return new UnmodifiableIterator<>(getVisibleChildCells().iterator());
}
public Iterator<EditorCell> iterator(EditorCell anchor, boolean forward) {
return new UnmodifiableIterator<>(getVisibleChildCells().iterator(anchor, forward));
}
@Override
public Iterator<EditorCell> reverseIterator() {
return new UnmodifiableIterator<>(getVisibleChildCells().iterator(null, false));
}
@Override
public boolean isEmpty() {
return getVisibleChildCells().isEmpty();
}
@Override
public EditorCell firstCell() {
Container<EditorCell> visibleChildCells = getVisibleChildCells();
return visibleChildCells.isEmpty() ? null : visibleChildCells.getFirst();
}
@Override
public EditorCell lastCell() {
Container<EditorCell> visibleChildCells = getVisibleChildCells();
return visibleChildCells.isEmpty() ? null : visibleChildCells.getLast();
}
@Override
public void addEditorCell(EditorCell editorCell) {
if (editorCell == null) {
return;
}
detachFromParent(editorCell);
getEditorCells().add(editorCell);
}
private void detachFromParent(EditorCell editorCell) {
if (editorCell.getParent() != null) {
editorCell.getParent().removeCell(editorCell);
}
}
@Override
public void addEditorCellBefore(EditorCell editorCell, EditorCell anchor) {
detachFromParent(editorCell);
getEditorCells().addBefore(editorCell, anchor);
}
@Override
public void addEditorCellAfter(EditorCell editorCell, EditorCell anchor) {
detachFromParent(editorCell);
Iterator<EditorCell> iterator = getEditorCells().iterator(anchor, true);
getEditorCells().addBefore(editorCell, iterator.hasNext() ? iterator.next() : null);
}
@Override
public void removeCell(EditorCell cellToRemove) {
getEditorCells().remove(cellToRemove);
}
@Override
public int getCellsCount() {
return getVisibleChildCells().size();
}
@Override
public Iterable<EditorCell> getContentCells() {
if (usesBraces() && !isCollapsed()) {
return new ConditionalIterable<>(this, item -> getEditorCells().isEmpty() || getEditorCells().getFirst() != item && getEditorCells().getLast() != item);
} else {
// TODO: either return getEditorCells() or use getVisibleChildCells() in all other content-related methods
return this;
}
}
public jetbrains.mps.nodeEditor.cells.EditorCell[] getCells() {
jetbrains.mps.nodeEditor.cells.EditorCell[] result = new jetbrains.mps.nodeEditor.cells.EditorCell[getVisibleChildCells().size()];
int i = 0;
for (EditorCell editorCell : getVisibleChildCells()) {
result[i++] = (jetbrains.mps.nodeEditor.cells.EditorCell) editorCell;
}
return result;
}
public List<jetbrains.mps.nodeEditor.cells.EditorCell> dfsCells() {
List<jetbrains.mps.nodeEditor.cells.EditorCell> result = new ArrayList<>();
for (EditorCell cell : getVisibleChildCells()) {
if (cell instanceof EditorCell_Collection) {
result.add((jetbrains.mps.nodeEditor.cells.EditorCell) cell);
result.addAll(((EditorCell_Collection) cell).dfsCells());
} else {
result.add((jetbrains.mps.nodeEditor.cells.EditorCell) cell);
}
}
return result;
}
public boolean containsCell(jetbrains.mps.nodeEditor.cells.EditorCell editorCell) {
for (EditorCell cell : getVisibleChildCells()) {
if (cell.equals(editorCell)) {
return true;
}
}
return false;
}
@Override
public int getContentCellsCount() {
return usesBraces() ? getCellsCount() - 2 : getCellsCount();
}
@Override
protected void relayoutImpl() {
myCellLayout.doLayout(this);
myAscent = myCellLayout.getAscent(this);
myDescent = myCellLayout.getDescent(this);
for (EditorCell childCell : this) {
if (childCell.wasRelayoutRequested()) {
LOG.error("Some child cells of " + this + " cell with the layout: " + myCellLayout + " still needs re-layout", new Throwable());
return;
}
}
}
public void setInitiallyCollapsed(boolean collapsed) {
if (myInitiallyCollapsed == collapsed) {
return;
}
myInitiallyCollapsed = collapsed;
if (isDefaultCollapsedValueChanged()) {
// Collapsed value was changed by user. After initiallyCollapsed value change user value become "default",
// so should be removed from editor "folded" map. Editor state will not change in this case.
myCollapsed = null;
if (isInTree()) {
getEditor().resetCollapseState(this);
}
} else {
if (isInTree()) {
requestRelayout();
collapsedStateChanged();
}
}
}
@Override
public boolean isInitiallyCollapsed() {
return myInitiallyCollapsed;
}
private boolean isDefaultCollapsedValueChanged() {
return myCollapsed != null;
}
public void fold() {
toggleCollapsed(true);
}
public void unfold() {
toggleCollapsed(false);
}
public void toggleCollapsed(boolean collapsed) {
if (!isFoldable() || isCollapsed() == collapsed) {
return;
}
myCollapsed = collapsed == myInitiallyCollapsed ? null : collapsed;
if (!isInTree()) {
return;
}
getEditor().setCollapseState(this, myCollapsed);
requestRelayout();
collapsedStateChanged();
updateSelectionOnCollapseChange();
}
private void collapsedStateChanged() {
// should be called only is this cell isInTree()
if (isUnderFolded()) {
return;
}
boolean collapsed = isCollapsed();
if (collapsed) {
addUnfoldingListener();
} else {
removeUnfoldingListener();
}
updateSubtreeOnCollapsedStateChange(getEditorCells().iterator(), !collapsed);
updateSubtreeOnCollapsedStateChange(getFoldedCellCollection().iterator(), collapsed);
}
private void updateSelectionOnCollapseChange() {
// should be called only is this cell isInTree()
getContext().flushEvents();
getEditor().relayout();
if (!isDescendantCellSelected(getEditor())) {
return;
}
getEditor().clearSelectionStack();
for (EditorCell nextCell : new CellTreeIterable(this, CellTraversalUtil.getFirstLeaf(this), true)) {
if (nextCell.isSelectable()) {
getEditor().changeSelection(nextCell);
nextCell.home();
return;
}
}
getEditor().changeSelection(this);
home();
}
protected boolean isUnderFolded() {
return CellTraversalUtil.getFoldedParent(this) != null;
}
private void updateSubtreeOnCollapsedStateChange(Iterator<EditorCell> subtreeRootsIterator, boolean visible) {
while (subtreeRootsIterator.hasNext()) {
EditorCell nextSubtreeRootCell = subtreeRootsIterator.next();
for (TreeIterator<EditorCell> iterator = new CellTreeIterable(nextSubtreeRootCell, nextSubtreeRootCell, true).iterator(); iterator.hasNext(); ) {
EditorCell child = iterator.next();
if (child instanceof EditorCell_WithComponent) {
((EditorCell_WithComponent) child).getComponent().setVisible(visible);
}
if (child instanceof EditorCell_Collection) {
EditorCell_Collection childCollection = (EditorCell_Collection) child;
if (childCollection.isCollapsed()) {
if (visible) {
childCollection.addUnfoldingListener();
} else {
childCollection.removeUnfoldingListener();
}
iterator.skipChildren();
}
}
}
}
}
private void removeUnfoldingListener() {
if (myUnfoldCollectionMouseListener == null) {
return;
}
getEditor().removeMouseListener(myUnfoldCollectionMouseListener);
myUnfoldCollectionMouseListener = null;
}
private void addUnfoldingListener() {
if (myUnfoldCollectionMouseListener != null) {
return;
}
final EditorComponent editorComponent = getEditor();
editorComponent.addMouseListener(myUnfoldCollectionMouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (GeometryUtil.contains(EditorCell_Collection.this, e.getX(), e.getY())) {
editorComponent.clearSelectionStack();
editorComponent.changeSelection(getFoldedCell());
unfold();
}
}
});
}
private boolean isDescendantCellSelected(jetbrains.mps.openapi.editor.EditorComponent editorComponent) {
EditorCell selectedCell = editorComponent.getDeepestSelectedCell();
return selectedCell != null && CellFinderUtil.findParent(selectedCell, object -> object == EditorCell_Collection.this) != null;
}
@Override
public boolean isFoldable() {
return myCanBeFolded && myCellLayout.canBeFolded();
}
public void setFoldable(boolean foldable) {
boolean wasPossiblyFolded = isFoldable();
myCanBeFolded = foldable;
if (isInTree()) {
if (wasPossiblyFolded && !isFoldable()) {
getEditor().getCellTracker().removeFoldableCell(this);
}
if (!wasPossiblyFolded && isFoldable()) {
getEditor().getCellTracker().addFoldableCell(this);
}
}
}
@Override
public void paintCell(Graphics g, ParentSettings parentSettings) {
ParentSettings settings = fillBackground(g, parentSettings);
paintContent(g, parentSettings);
paintChildCells(g, settings);
}
protected void paintChildCells(Graphics g, ParentSettings settings) {
for (EditorCell child : this) {
jetbrains.mps.nodeEditor.cells.EditorCell childInternal = (jetbrains.mps.nodeEditor.cells.EditorCell) child;
if (childInternal.isInClipRegion(g)) {
childInternal.paintCell(g, settings);
}
}
}
@Override
public void paintDecorations(Graphics g) {
super.paintDecorations(g);
paintChildDecorations(g);
}
protected void paintChildDecorations(Graphics g) {
for (EditorCell child : this) {
jetbrains.mps.nodeEditor.cells.EditorCell childInternal = (jetbrains.mps.nodeEditor.cells.EditorCell) child;
if (childInternal.isInClipRegion(g)) {
childInternal.paintDecorations(g);
}
}
}
@Override
public void moveTo(int x, int y) {
if (x == myX && y == myY) {
return;
}
int xOld = myX;
int yOld = myY;
super.moveTo(x, y);
if (wasRelayoutRequested()) {
return;
}
for (EditorCell myEditorCell : this) {
myEditorCell.moveTo(myEditorCell.getX() + x - xOld, myEditorCell.getY() + y - yOld);
}
getCellLayout().move(this, x - xOld, y - yOld);
}
@Override
protected void paintContent(Graphics g, ParentSettings parentSettings) {
}
@Override
public void paintSelection(Graphics g, Color c, boolean drawBorder, ParentSettings parentSettings) {
List<? extends EditorCell> selectionCells = myCellLayout.getSelectionCells(this);
if (selectionCells != null) {
ParentSettings selection = isSelectionPaintedOnAncestor(parentSettings);
for (EditorCell cell : selectionCells) {
jetbrains.mps.nodeEditor.cells.EditorCell cellInternal = (jetbrains.mps.nodeEditor.cells.EditorCell) cell;
if (cellInternal.isInClipRegion(g)) {
cellInternal.paintSelection(g, c, false, selection);
}
}
} else {
List<Rectangle> selection = myCellLayout.getSelectionBounds(this);
g.setColor(c);
for (Rectangle part : selection) {
g.fillRect(part.x, part.y, part.width, part.height);
}
}
}
@Override
protected void paintBackground(Graphics g) {
List<Rectangle> selection = myCellLayout.getSelectionBounds(this);
for (Rectangle part : selection) {
g.fillRect(part.x, part.y, part.width, part.height);
}
}
@Override
public TextBuilder renderText() {
return myCellLayout.doLayoutText(this);
}
@Override
public void synchronizeViewWithModel() {
for (EditorCell myEditorCell : getEditorCells()) {
((jetbrains.mps.nodeEditor.cells.EditorCell) myEditorCell).synchronizeViewWithModel();
}
if (hasFoldedCell()) {
((jetbrains.mps.nodeEditor.cells.EditorCell) getFoldedCell()).synchronizeViewWithModel();
}
}
@Override
public EditorCell findLeaf(int x, int y) {
if (getX() <= x && x < getX() + getWidth() && getY() <= y && y < getY() + getHeight()) {
for (EditorCell child : this) {
EditorCell result = child.findLeaf(x, y);
if (result != null) {
return result;
}
}
}
return null;
}
@Override
public EditorCell findNearestLeafOnLine(int x, int y, Condition<EditorCell> condition) {
if (getY() <= y && y < getY() + getHeight()) {
List<EditorCell> candidates = new ArrayList<>();
for (EditorCell child : this) {
EditorCell nextCandidate = child.findNearestLeafOnLine(x, y, condition);
if (nextCandidate != null) {
candidates.add(nextCandidate);
}
}
EditorCell best = null;
int bestDistance = Integer.MAX_VALUE;
for (EditorCell next : candidates) {
int distance = GeometryUtil.getHorizontalDistance(next, x);
if (distance < bestDistance) {
best = next;
bestDistance = distance;
}
}
return best;
}
return null;
}
/**
* @deprecated since MPS 3.4 is deprecated. Use addEditorCellAt(EditorCell cellToAdd, int index).
*/
@Deprecated
@Override
public void addEditorCellAt(int i, EditorCell cellToAdd, boolean ignoreBraces) {
int j = i;
if (usesBraces() && !ignoreBraces) {
j = i - 1;
}
addEditorCellAt(cellToAdd, j);
}
/**
* @deprecated since MPS 3.4 use addEditorCellBefore()/addEditorCellAfter() methods
*/
@Deprecated
@Override
public void addEditorCellAt(EditorCell cellToAdd, int index) {
detachFromParent(cellToAdd);
Iterator<EditorCell> iterator = getEditorCells().iterator();
int i = 0;
for (; i < index && iterator.hasNext(); i++) {
iterator.next();
}
if (i < index) {
throw new IndexOutOfBoundsException("Size: " + getEditorCells().size() + ", Index: " + index);
}
EditorCell anchor = iterator.hasNext() ? iterator.next() : null;
getEditorCells().addBefore(cellToAdd, anchor);
}
/**
* @deprecated since MPS 3.4 not used
*/
@Deprecated
public void removeAllCells() {
for (EditorCell cell : this) {
removeCell(cell);
}
}
@Override
public boolean usesBraces() {
return !isCollapsed() && myUsesBraces;
}
public void setUsesBraces(boolean b) {
if (myUsesBraces != b) {
myUsesBraces = b;
if (myUsesBraces) {
addBraces();
} else {
removeBraces();
}
}
}
@Override
@SuppressWarnings({"UnusedDeclaration"})
public EditorCell firstContentCell() {
if (!usesBraces()) {
return firstCell();
}
Iterator<EditorCell> iterator = getEditorCells().iterator();
if (!iterator.hasNext()) {
return null;
}
iterator.next();
if (!iterator.hasNext()) {
return null;
}
EditorCell result = iterator.next();
return iterator.hasNext() ? result : null;
}
@Override
public EditorCell lastContentCell() {
if (!usesBraces()) {
return lastCell();
}
Iterator<EditorCell> iterator = getEditorCells().iterator(null, false);
if (!iterator.hasNext()) {
return null;
}
iterator.next();
if (!iterator.hasNext()) {
return null;
}
EditorCell result = iterator.next();
return iterator.hasNext() ? result : null;
}
/**
* @deprecated since MPS 3.4 not used
*/
@Deprecated
@Override
public jetbrains.mps.nodeEditor.cells.EditorCell getFirstLeaf() {
return isEmpty() ? this : ((jetbrains.mps.nodeEditor.cells.EditorCell) firstCell()).getFirstLeaf();
}
/**
* @deprecated since MPS 3.4 not used
*/
@Deprecated
@Override
public jetbrains.mps.nodeEditor.cells.EditorCell getLastLeaf() {
return isEmpty() ? this : ((jetbrains.mps.nodeEditor.cells.EditorCell) lastCell()).getLastLeaf();
}
@Override
public jetbrains.mps.nodeEditor.cells.EditorCell getLastChild() {
return (jetbrains.mps.nodeEditor.cells.EditorCell) lastCell();
}
@Override
public jetbrains.mps.nodeEditor.cells.EditorCell getFirstChild() {
return (jetbrains.mps.nodeEditor.cells.EditorCell) firstCell();
}
public String toString() {
return NameUtil.shortNameFromLongName(getClass().getName());
}
@Override
public void onAdd() {
super.onAdd();
for (EditorCell child : getEditorCells()) {
((EditorCell_Basic) child).onAdd();
}
if (hasFoldedCell()) {
((EditorCell_Basic) getFoldedCell()).onAdd();
}
if (myLastCellSelectionListener != null) {
getEditor().getSelectionManager().addSelectionListener(myLastCellSelectionListener);
}
if (isFoldable()) {
getEditor().getCellTracker().addFoldableCell(this);
if (isDefaultCollapsedValueChanged()) {
getEditor().setCollapseState(this, isCollapsed());
}
if (!isUnderFolded() && isCollapsed()) {
addUnfoldingListener();
}
}
}
@Override
public void onRemove() {
removeUnfoldingListener();
if (isDefaultCollapsedValueChanged()) {
getEditor().resetCollapseState(this);
}
if (isFoldable()) {
getEditor().getCellTracker().removeFoldableCell(this);
}
if (myLastCellSelectionListener != null) {
setBracesEnabled(false);
getEditor().getSelectionManager().removeSelectionListener(myLastCellSelectionListener);
}
if (hasFoldedCell()) {
((EditorCell_Basic) getFoldedCell()).onRemove();
}
for (EditorCell child : getEditorCells()) {
((EditorCell_Basic) child).onRemove();
}
super.onRemove();
}
@Override
public void synchronize() {
for (EditorCell cell : getEditorCells()) {
((SynchronizeableEditorCell) cell).synchronize();
}
if (hasFoldedCell()) {
((SynchronizeableEditorCell) getFoldedCell()).synchronize();
}
}
@Override
public boolean canBeSynchronized() {
return myCanBeSynchronized;
}
public void setCanBeSynchronized(boolean canBeSynchronized) {
myCanBeSynchronized = canBeSynchronized;
}
private class SelectFirstChild extends AbstractCellAction {
@Override
public boolean canExecute(EditorContext context) {
return EditorCell_Collection.this.isSelected() && CellFinderUtil.findFirstSelectableLeaf(EditorCell_Collection.this) != null;
}
@Override
public void execute(EditorContext context) {
EditorComponent editorComponent = (EditorComponent) context.getEditorComponent();
editorComponent.clearSelectionStack();
editorComponent.changeSelection(CellFinderUtil.findFirstSelectableLeaf(EditorCell_Collection.this));
}
}
private class SelectLastChild extends AbstractCellAction {
@Override
public boolean canExecute(EditorContext context) {
return EditorCell_Collection.this.isSelected() && CellFinderUtil.findLastSelectableLeaf(EditorCell_Collection.this) != null;
}
@Override
public void execute(EditorContext context) {
EditorComponent editorComponent = (EditorComponent) context.getEditorComponent();
editorComponent.clearSelectionStack();
editorComponent.changeSelection(CellFinderUtil.findLastSelectableLeaf(EditorCell_Collection.this));
}
}
@Override
public void setSubstituteInfo(SubstituteInfo substitueInfo) {
super.setSubstituteInfo(substitueInfo);
if (isTransparentCollection()) {
for (EditorCell child : this) {
if (child.getSNode() == getSNode()) {
child.setSubstituteInfo(substitueInfo);
}
}
}
}
@Override
public void setAction(CellActionType type, CellAction action) {
super.setAction(type, action);
if (isTransparentCollection()) {
for (EditorCell child : this) {
if (child.getSNode() == getSNode()) {
child.setAction(type, action);
}
}
}
}
@Override
public void requestRelayout() {
if (wasRelayoutRequested()) {
return;
}
super.requestRelayout();
getCellLayout().requestRelayout(this);
}
@Override
public boolean isTransparentCollection() {
return getCellsCount() == 1 && getStyle().get(StyleAttributes.SELECTABLE);
}
class EditorCell_Brace extends EditorCell_Constant {
public static final String OPENING_TEXT = "(";
public static final String CLOSING_TEXT = ")";
private boolean myIsOpening = false;
private boolean myIsEnabled = false;
private TextLine myBraceTextLine;
protected EditorCell_Brace(EditorContext editorContext, SNode node, boolean isOpening) {
super(editorContext, node, "");
myIsOpening = isOpening;
String text = getBraceText();
Style style = new StyleImpl();
// COLORS: Remove hardcoded color & font
style.set(StyleAttributes.TEXT_COLOR, Color.BLUE);
style.set(StyleAttributes.FONT_STYLE, Font.BOLD);
style.set(StyleAttributes.PADDING_LEFT, new Padding(0.0));
style.set(StyleAttributes.PADDING_RIGHT, new Padding(0.0));
myBraceTextLine = new TextLine(text, style, false);
myBraceTextLine.setCaretEnabled(false);
setEditable(false);
setEnabled(false);
setSelectable(false);
}
@Override
public CellInfo getCellInfo() {
return new BraceCellInfo(EditorCell_Brace.this);
}
private String getBraceText() {
return myIsOpening ? OPENING_TEXT : CLOSING_TEXT;
}
public void setEnabled(boolean enabled) {
myIsEnabled = enabled;
}
@Override
protected void paintContent(Graphics g, ParentSettings parentSettings) {
if (!myIsEnabled) {
return;
}
TextLine textLine = getRenderedTextLine();
boolean toShowCaret = toShowCaret();
int overlapping = getOverlapping();
myBraceTextLine.setSelected(false);
myBraceTextLine.setShowCaret(false);
textLine.setSelected(isSelected());
textLine.setShowCaret(toShowCaret);
if (myIsOpening) {
myBraceTextLine.paint(g, myX + textLine.getWidth() - overlapping, myY);
textLine.paint(g, myX, myY);
} else {
myBraceTextLine.paint(g, myX, myY);
textLine.paint(g, myX + myBraceTextLine.getWidth() - overlapping, myY);
}
}
@Override
public void relayoutImpl() {
super.relayoutImpl();
myBraceTextLine.relayout();
myWidth += myBraceTextLine.getWidth() - getOverlapping();
}
private int getOverlapping() {
if (myIsOpening) {
return myBraceTextLine.getPaddingLeft() + myTextLine.getPaddingRight();
} else {
return myBraceTextLine.getPaddingRight() + myTextLine.getPaddingLeft();
}
}
}
private static class BraceCellInfo extends DefaultCellInfo {
private CellInfo myCollectionCellInfo;
private boolean myOpeningBrace;
public BraceCellInfo(EditorCell_Brace cell) {
super(cell);
myOpeningBrace = cell.myIsOpening;
myCollectionCellInfo = cell.getParent().getCellInfo();
}
@Override
public EditorCell findCell(@NotNull jetbrains.mps.openapi.editor.EditorComponent editorComponent) {
EditorCell cell = myCollectionCellInfo.findCell(editorComponent);
if (!(cell instanceof EditorCell_Collection)) {
return null;
}
EditorCell_Collection parent = (EditorCell_Collection) cell;
if (myOpeningBrace) {
return parent.myOpeningBrace;
} else {
return parent.myClosingBrace;
}
}
public int hashCode() {
return myCollectionCellInfo.hashCode() + (myOpeningBrace ? 50 : -50);
}
public boolean equals(Object o) {
if (!(o instanceof BraceCellInfo)) {
return false;
}
BraceCellInfo cellInfo = ((BraceCellInfo) o);
return myCollectionCellInfo.equals(cellInfo.myCollectionCellInfo) && myOpeningBrace == cellInfo.myOpeningBrace;
}
}
private class MyLastCellSelectionListener implements SelectionListener {
public final Condition<EditorCell> SELECTABLE_LEAF_EXCLUDING_BRACE =
cell -> myOpeningBrace != cell && myClosingBrace != cell && cell.isSelectable() && !(cell instanceof EditorCell_Collection);
@Override
public void selectionChanged(jetbrains.mps.openapi.editor.EditorComponent editorComponent, Selection oldSelection, Selection newSelection) {
if (oldSelection == newSelection) {
return;
}
EditorCell deepestSelection = editorComponent.getDeepestSelectedCell();
if (CellFinderUtil.findChildByCondition(EditorCell_Collection.this, SELECTABLE_LEAF_EXCLUDING_BRACE, true) == deepestSelection ||
CellFinderUtil.findChildByCondition(EditorCell_Collection.this, SELECTABLE_LEAF_EXCLUDING_BRACE, false) == deepestSelection) {
enableBraces();
} else {
disableBraces();
}
}
}
}