From 83171d38b504661c58f4045f3cf8c0308e023552 Mon Sep 17 00:00:00 2001 From: Laurent Redor Date: Mon, 29 Jul 2024 11:36:20 +0200 Subject: [PATCH] [426] Handle List container and list items Before this commit, this kind of container was not correctly handled. Bug: https://github.com/eclipse-sirius/sirius-desktop/issues/426 --- .../ui/business/api/query/NodeQuery.java | 33 +--- .../ui/business/api/query/ViewQuery.java | 32 +++- .../ui/internal/refresh/GMFHelper.java | 156 ++++++++++++++---- .../sirius/ui/debug/SiriusDebugView.java | 8 +- 4 files changed, 165 insertions(+), 64 deletions(-) diff --git a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/NodeQuery.java b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/NodeQuery.java index 1713fdd63d..9333a02600 100644 --- a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/NodeQuery.java +++ b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/NodeQuery.java @@ -30,14 +30,7 @@ import org.eclipse.sirius.diagram.business.api.query.DDiagramElementQuery; import org.eclipse.sirius.diagram.business.api.query.DNodeQuery; import org.eclipse.sirius.diagram.ui.edit.internal.part.PortLayoutHelper; -import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNode2EditPart; -import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNode4EditPart; -import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeContainer2EditPart; -import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeContainerEditPart; -import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeList2EditPart; -import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListEditPart; import org.eclipse.sirius.diagram.ui.internal.refresh.GMFHelper; -import org.eclipse.sirius.diagram.ui.part.SiriusVisualIDRegistry; import org.eclipse.sirius.diagram.ui.tools.api.layout.LayoutUtils; import org.eclipse.sirius.ext.base.Option; import org.eclipse.sirius.ext.base.Options; @@ -51,7 +44,7 @@ * * @author lredor */ -public class NodeQuery { +public class NodeQuery extends ViewQuery { private Node node; @@ -62,6 +55,7 @@ public class NodeQuery { * the starting point. */ public NodeQuery(Node node) { + super(node); this.node = node; } @@ -166,33 +160,12 @@ protected Dimension getDefaultDim(DDiagramElement element) { return dim; } - /** - * Tests whether the queried Node corresponds to a bordered node. - * - * @return true if the queried View corresponds to a bordered node. - */ - public boolean isBorderedNode() { - int type = SiriusVisualIDRegistry.getVisualID(this.node.getType()); - boolean result = type == DNode2EditPart.VISUAL_ID || type == DNode4EditPart.VISUAL_ID; - return result; - } - - /** - * Tests whether the queried Node corresponds to a container (list or not). - * - * @return true if the queried View corresponds to a container node. - */ - public boolean isContainer() { - int type = SiriusVisualIDRegistry.getVisualID(this.node.getType()); - boolean result = type == DNodeContainer2EditPart.VISUAL_ID || type == DNodeContainerEditPart.VISUAL_ID || type == DNodeList2EditPart.VISUAL_ID || type == DNodeListEditPart.VISUAL_ID; - return result; - } - /** * Return the compartment of the GMF node container with "free form" layout. * * @return the compartment or Optional.empty if view is not container or compartment not found */ + @Override public Optional getFreeFormContainerCompartment() { if (new ViewQuery(this.node).isFreeFormContainer()) { List children = this.node.getChildren(); diff --git a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/ViewQuery.java b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/ViewQuery.java index f9d183de61..0b53869cb7 100644 --- a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/ViewQuery.java +++ b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/business/api/query/ViewQuery.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2021 THALES GLOBAL SERVICES and others. + * Copyright (c) 2012, 2024 THALES GLOBAL SERVICES and others. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -61,8 +61,11 @@ import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeEditPart; import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeList2EditPart; import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListEditPart; +import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListElementEditPart; import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListName2EditPart; import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListNameEditPart; +import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListViewNodeListCompartment2EditPart; +import org.eclipse.sirius.diagram.ui.internal.edit.parts.DNodeListViewNodeListCompartmentEditPart; import org.eclipse.sirius.diagram.ui.internal.edit.parts.NotationViewIDs; import org.eclipse.sirius.diagram.ui.part.SiriusVisualIDRegistry; import org.eclipse.sirius.diagram.ui.provider.DiagramUIPlugin; @@ -380,6 +383,31 @@ public boolean isFreeFormCompartment() { || type == DNodeContainerViewNodeContainerCompartment2EditPart.VISUAL_ID; } + /** + * Return if this GMF node is associated to DNodeList Sirius diagram element. + */ + public boolean isListContainer() { + int type = SiriusVisualIDRegistry.getVisualID(this.view.getType()); + return type == DNodeListEditPart.VISUAL_ID || type == DNodeList2EditPart.VISUAL_ID; + } + + /** + * Return if this GMF node is compartment of node corresponding to a Sirius list container. + */ + public boolean isListCompartment() { + int type = SiriusVisualIDRegistry.getVisualID(this.view.getType()); + return type == DNodeListViewNodeListCompartmentEditPart.VISUAL_ID // + || type == DNodeListViewNodeListCompartment2EditPart.VISUAL_ID; + } + + /** + * Return if this GMF node is a list item. + */ + public boolean isListItem() { + int type = SiriusVisualIDRegistry.getVisualID(this.view.getType()); + return type == DNodeListElementEditPart.VISUAL_ID; + } + /** * Return if this GMF node have vertical/horizontal stack layout. */ @@ -416,7 +444,7 @@ public boolean isNodeLabel() { * * @return the compartment or Optional.empty if view is not container or compartment not found */ - public Optional getFreeFormContainerCompartment() { + public Optional getFreeFormContainerCompartment() { if (this.isFreeFormContainer()) { List children = this.view.getChildren(); return children.stream() // diff --git a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/refresh/GMFHelper.java b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/refresh/GMFHelper.java index 9be9a8df1a..c6ba88f4f7 100644 --- a/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/refresh/GMFHelper.java +++ b/plugins/org.eclipse.sirius.diagram.ui/src-diag/org/eclipse/sirius/diagram/ui/internal/refresh/GMFHelper.java @@ -119,9 +119,11 @@ public final class GMFHelper { * see org.eclipse.sirius.diagram.ui.internal.edit.parts. AbstractDNodeContainerCompartmentEditPart.DEFAULT_MARGIN * the top value is the DEFAULT_MARGIN + the InvisibleResizableCompartmentFigure top Inset (1px) */ - private static Insets CONTAINER_INSETS = new Insets(IContainerLabelOffsets.LABEL_OFFSET, AbstractDNodeContainerCompartmentEditPart.DEFAULT_MARGIN, + private static final Insets FREEFORM_CONTAINER_INSETS = new Insets(IContainerLabelOffsets.LABEL_OFFSET, AbstractDNodeContainerCompartmentEditPart.DEFAULT_MARGIN, AbstractDNodeContainerCompartmentEditPart.DEFAULT_MARGIN, AbstractDNodeContainerCompartmentEditPart.DEFAULT_MARGIN); + private static final Insets LIST_CONTAINER_INSETS = new Insets(4, 0, 0, 0); + /** * The gap in pixels between the Label's icon and its text * (org.eclipse.sirius.ext.gmf.runtime.gef.ui.figures.SiriusWrapLabel.getIconTextGap()). @@ -181,8 +183,29 @@ public static Point getAbsoluteLocation(Node node, boolean insetsAware) { } } } - - if (node.eContainer() instanceof Node container) { + ViewQuery viewQuery = new ViewQuery(node); + if (viewQuery.isListCompartment()) { + // Translate the compartment to be just below the the title, the x coordinate is also the same (same parent + // insets) + Rectangle titleBounds = getAbsoluteBounds(getPreviousChild(node), true); + location.translate(titleBounds.preciseX(), titleBounds.preciseY() + titleBounds.preciseHeight()); + // Translate from the spacing (5 pixels) + location.translate(0, IContainerLabelOffsets.LABEL_OFFSET); + } else if (viewQuery.isListItem()) { + if (node.eContainer() instanceof Node container) { + if (container.getChildren().get(0) == node) { + Point parentNodeLocation = getAbsoluteLocation(container, insetsAware); + location.translate(parentNodeLocation); + if (insetsAware) { + translateWithInsets(location, node); + } + } else { + // Translate from the previous children + Rectangle previousChildBounds = getAbsoluteBounds(getPreviousChild(node), true); + location.translate(previousChildBounds.preciseX(), previousChildBounds.preciseY() + previousChildBounds.preciseHeight()); + } + } + } else if (node.eContainer() instanceof Node container) { Point parentNodeLocation = getAbsoluteLocation(container, insetsAware); location.translate(parentNodeLocation); if (insetsAware) { @@ -192,6 +215,26 @@ public static Point getAbsoluteLocation(Node node, boolean insetsAware) { return location; } + private static Node getPreviousChild(Node node) { + Node previousChild = null; + boolean found = false; + if (node.eContainer() instanceof Node container) { + for (Iterator children = Iterators.filter(container.getChildren().iterator(), Node.class); children.hasNext() && !found; /* */) { + Node child = children.next(); + if (node == child) { + found = true; + } else { + previousChild = child; + } + } + } + if (found) { + return previousChild; + } else { + return null; + } + } + /** * Return the top-left insets of this container. The insets also considers its border. * @@ -210,16 +253,24 @@ public static Dimension getTopLeftInsets(Node container) { // RegionContainer do not have containers insets if (ddec instanceof DNodeContainer) { if (new DNodeContainerExperimentalQuery((DNodeContainer) ddec).isRegionContainer() || hasFullLabelBorder(ddec)) { - result.setHeight(CONTAINER_INSETS.top + getLabelDimension(container, new Dimension(50, 20)).width() + AbstractDiagramElementContainerEditPart.DEFAULT_SPACING); + result.setHeight(FREEFORM_CONTAINER_INSETS.top + getLabelDimension(container, new Dimension(50, 20)).width() + AbstractDiagramElementContainerEditPart.DEFAULT_SPACING); } else { - result.setWidth(CONTAINER_INSETS.left); - result.setHeight(CONTAINER_INSETS.top); + result.setWidth(FREEFORM_CONTAINER_INSETS.left); + result.setHeight(FREEFORM_CONTAINER_INSETS.top); } + } else if (element instanceof DNodeList) { + result.setWidth(LIST_CONTAINER_INSETS.left); + result.setHeight(LIST_CONTAINER_INSETS.top); } Dimension borderSize = getBorderSize(ddec); result.setWidth(result.width() + borderSize.width()); result.setHeight(result.height() + borderSize.height()); } + } else if (nodeQuery.isListCompartment()) { + // Add the corresponding margin of {1, 4, 0, 4} of + // org.eclipse.sirius.diagram.ui.internal.edit.parts.AbstractDNodeListCompartmentEditPart.createFigure() + result.setWidth(4); + result.setHeight(1); } return result; } @@ -241,7 +292,7 @@ public static Dimension getContainerTopLeftInsets(Node node, boolean searchFirst if (nodeContainer instanceof Node) { Node parentNode = (Node) nodeContainer; NodeQuery nodeQuery = new NodeQuery(parentNode); - if (nodeQuery.isContainer()) { + if (nodeQuery.isContainer() || nodeQuery.isListCompartment()) { result = getTopLeftInsets(parentNode); } else if (searchFirstParentContainer) { result = getContainerTopLeftInsets(parentNode, searchFirstParentContainer); @@ -269,17 +320,29 @@ private static Dimension getBottomRightInsets(Node container) { if (ddec instanceof DNodeContainer) { if (new DNodeContainerExperimentalQuery((DNodeContainer) ddec).isRegionContainer() || hasFullLabelBorder(ddec)) { // TODO : Not sure about that, to verify - result.setHeight(CONTAINER_INSETS.bottom); + result.setHeight(FREEFORM_CONTAINER_INSETS.bottom); } else { - result.setWidth(CONTAINER_INSETS.right); - result.setHeight(CONTAINER_INSETS.bottom); + result.setWidth(FREEFORM_CONTAINER_INSETS.right); + result.setHeight(FREEFORM_CONTAINER_INSETS.bottom); } + Dimension borderSize = getBorderSize(ddec); + // Added twice as this insets is used to compute the "global" size including the border + result.setWidth(result.width() + (borderSize.width() * 2)); + result.setHeight(result.height() + (borderSize.height() * 2)); + } else if (ddec instanceof DNodeList) { + result.setWidth(LIST_CONTAINER_INSETS.right); + result.setHeight(LIST_CONTAINER_INSETS.bottom); + // TODO: to verify + Dimension borderSize = getBorderSize(ddec); + result.setWidth(result.width() + borderSize.width()); + result.setHeight(result.height() + borderSize.height()); } - Dimension borderSize = getBorderSize(ddec); - // Added twice as this insets is used to compute the "global" size including the border - result.setWidth(result.width() + (borderSize.width() * 2)); - result.setHeight(result.height() + (borderSize.height() * 2)); } + } else if (nodeQuery.isListCompartment()) { + // Add the corresponding margin of {1, 4, 0, 4} of + // org.eclipse.sirius.diagram.ui.internal.edit.parts.AbstractDNodeListCompartmentEditPart.createFigure() + result.setWidth(4); + result.setHeight(0); } return result; } @@ -304,8 +367,8 @@ public static Dimension getContainerTopLeftInsetsAfterLabel(Node node, boolean s if (nodeQuery.isContainer()) { EObject element = parentNode.getElement(); if (element instanceof DDiagramElementContainer) { - result.setWidth(CONTAINER_INSETS.left); - result.setHeight(CONTAINER_INSETS.top); + result.setWidth(FREEFORM_CONTAINER_INSETS.left); + result.setHeight(FREEFORM_CONTAINER_INSETS.top); Dimension borderSize = getBorderSize((DDiagramElementContainer) element); result.setWidth(result.width() + borderSize.width()); @@ -359,6 +422,12 @@ private static void translateWithInsets(Point locationToTranslate, Node currentN // Border nodes are not concerned by those insets. if (!nodeQuery.isBorderedNode()) { locationToTranslate.translate(getContainerTopLeftInsets(currentNode, false)); + if (currentNode.eContainer() instanceof Node container) { + if (new ViewQuery(currentNode).isListItem() && container.getChildren().get(0) == currentNode) { + // This is the first list item, add a one margin border over it. + locationToTranslate.translate(0, 1); + } + } } } @@ -648,8 +717,8 @@ public static Rectangle getBounds(Node node, boolean useFigureForAutoSizeConstra bounds.setWidth(-1); bounds.setHeight(-1); } - - if (new ViewQuery(node).isForNameEditPart()) { + ViewQuery viewQuery = new ViewQuery(node); + if (viewQuery.isForNameEditPart() || viewQuery.isListItem()) { if (abstractDNode.getName() == null || abstractDNode.getName().length() == 0) { if (bounds.width == -1) { bounds.setWidth(0); @@ -707,7 +776,7 @@ private static boolean isShadowBorderNeeded(Node node) { boolean needShadowBorder = false; EObject element = node.getElement(); ViewQuery viewQuery = new ViewQuery(node); - if (!viewQuery.isFreeFormCompartment() && element instanceof DDiagramElementContainer) { + if (!viewQuery.isFreeFormCompartment() && !viewQuery.isListCompartment() && !viewQuery.isForNameEditPart() && element instanceof DDiagramElementContainer) { DDiagramElementContainer ddec = (DDiagramElementContainer) element; needShadowBorder = !(new DDiagramElementContainerExperimentalQuery(ddec).isRegion() || ddec.getOwnedStyle() instanceof WorkspaceImage); } @@ -736,8 +805,15 @@ private static void replaceAutoSize(Node node, PrecisionRectangle bounds, boolea // if there is no default size, we compute it from the given // node. EObject element = node.getElement(); - if (new ViewQuery(node).isFreeFormCompartment()) { + ViewQuery nodeQuery = new ViewQuery(node); + if (nodeQuery.isFreeFormCompartment() || nodeQuery.isListCompartment()) { defaultSize = new Dimension(ResizableCompartmentFigure.MIN_CLIENT_DP, ResizableCompartmentFigure.MIN_CLIENT_DP); + if (node.getChildren().isEmpty()) { + if (nodeQuery.isListCompartment()) { + // Add one margin border (even if empty) + defaultSize.expand(0, 1); + } + } } else if (element instanceof AbstractDNode) { defaultSize = getDefaultSize((AbstractDNode) element); } @@ -887,23 +963,41 @@ private static Rectangle getChildrenBounds(Node container, boolean considerBorde if (container.getChildren().isEmpty()) { result = new PrecisionRectangle(); } - for (Iterator children = Iterators.filter(container.getChildren().iterator(), Node.class); children.hasNext(); /* */) { - Node child = children.next(); - // The border nodes are ignored, except if it is expected to consider it (auto-size of a container with - // children having border nodes) - if (considerBorderNodes || !(new NodeQuery(child).isBorderedNode())) { - Rectangle childAbsoluteBounds = getAbsoluteBounds(child, true, false, true); - if (result == null) { - result = childAbsoluteBounds.getCopy(); - } else { - // Make union of the child bounds and its parent bounds - result.union(childAbsoluteBounds); + ViewQuery containerViewQuery = new ViewQuery(container); + if (containerViewQuery.isListContainer() || containerViewQuery.isListCompartment()) { + if (!container.getChildren().isEmpty()) { + Node lastChild = getLastChild(container, considerBorderNodes); + result = getAbsoluteBounds(lastChild, true, false, true); + } + } else { + for (Iterator children = Iterators.filter(container.getChildren().iterator(), Node.class); children.hasNext(); /* */) { + Node child = children.next(); + // The border nodes are ignored, except if it is expected to consider it (auto-size of a container with + // children having border nodes) + if (considerBorderNodes || !(new NodeQuery(child).isBorderedNode())) { + Rectangle childAbsoluteBounds = getAbsoluteBounds(child, true, false, true); + if (result == null) { + result = childAbsoluteBounds.getCopy(); + } else { + // Make union of the child bounds and its parent bounds + result.union(childAbsoluteBounds); + } } } } return result; } + private static Node getLastChild(Node container, boolean considerBorderNodes) { + for (int i = container.getChildren().size() - 1; i >= 0; i--) { + Node currentNode = (Node) container.getChildren().get(i); + if (considerBorderNodes || !new NodeQuery(currentNode).isBorderedNode()) { + return currentNode; + } + } + return null; + } + private static Dimension getDefaultSize(AbstractDNode abstractDNode) { Dimension defaultSize = new Dimension(-1, -1); if (abstractDNode instanceof DNode) { diff --git a/plugins/org.eclipse.sirius.ui.debug/src/org/eclipse/sirius/ui/debug/SiriusDebugView.java b/plugins/org.eclipse.sirius.ui.debug/src/org/eclipse/sirius/ui/debug/SiriusDebugView.java index 1f6357d38e..1a12fe7875 100644 --- a/plugins/org.eclipse.sirius.ui.debug/src/org/eclipse/sirius/ui/debug/SiriusDebugView.java +++ b/plugins/org.eclipse.sirius.ui.debug/src/org/eclipse/sirius/ui/debug/SiriusDebugView.java @@ -122,6 +122,7 @@ import org.eclipse.sirius.diagram.ui.business.internal.query.EdgeTargetQuery; import org.eclipse.sirius.diagram.ui.edit.api.part.AbstractDiagramContainerEditPart; import org.eclipse.sirius.diagram.ui.edit.api.part.AbstractDiagramElementContainerEditPart; +import org.eclipse.sirius.diagram.ui.edit.api.part.AbstractDiagramListEditPart; import org.eclipse.sirius.diagram.ui.edit.api.part.AbstractDiagramNameEditPart; import org.eclipse.sirius.diagram.ui.edit.api.part.IAbstractDiagramNodeEditPart; import org.eclipse.sirius.diagram.ui.edit.api.part.IDiagramEdgeEditPart; @@ -255,7 +256,12 @@ private String getTextFor(IDiagramElementEditPart part) { appendSequenceEventInfo(part, sb); appendBoundsDetails(part, sb); - if (part instanceof AbstractDiagramContainerEditPart && ((AbstractDiagramContainerEditPart) part).isRegionContainer()) { + if (part instanceof AbstractDiagramListEditPart) { + for (IGraphicalEditPart child : Iterables.filter(part.getChildren(), IGraphicalEditPart.class)) { + sb.append("Children bounds (" + child.getClass().getSimpleName() + "):\n"); + appendBoundsDetails(child, sb); + } + } else if (part instanceof AbstractDiagramContainerEditPart && ((AbstractDiagramContainerEditPart) part).isRegionContainer()) { IGraphicalEditPart compartment = null; for (IGraphicalEditPart child : Iterables.filter(part.getChildren(), IGraphicalEditPart.class)) { sb.append("Children bounds:\n");