diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 40acbdbff..5ff11b686 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -59,6 +59,9 @@ As a consequence, the `syson-table-requirements-view` and `syson-common-view` mo `GetIntermediateContainerCreationSwitch.java` has been moved from `syson-application-configuration` to `syson-services`. - https://github.com/eclipse-syson/syson/issues/1245[#1245] [syson] Standardize read-only computation. The class `SysMLReadOnlyService` and the interface `ISysMLReadOnlyService` have been removed, use `IReadOnlyObjectPredicate` instead. +- https://github.com/eclipse-syson/syson/issues/1546[#1546] [diagrams] Fix drag and drop of a graphical node from the diagram background into a _Package_ graphical node. +As a consequence, the `ViewToolService#expose` method has been moved into `ToolService.java` and `NodeFinder.java` has been moved from `syson-diagram-common-view` to `syson-services`. + === Dependency update @@ -79,6 +82,7 @@ This issue was not visible in SysON but could appear in downstream applications Now it has been fixed in Sirius Web 2025.8.4, `SysONUpdateLibraryExecutor` has been deleted and `UpdateLibraryExecutor` is used again. - https://github.com/eclipse-syson/syson/issues/1522[#1522] [diagrams] To avoid edge blinking when refreshing the diagram, we need to set the properties `measured.height` and `measured.width` with the value of the node layout data when converting a custom node accordingly to the xyflow https://reactflow.dev/learn/advanced-use/ssr-ssg-configuration#node-dimensions[documentation]. - https://github.com/eclipse-syson/syson/issues/1545[#1545] [diagrams] Add _interconnection_ compartment to `PartDefinition` nodes in the standard diagrams. +- https://github.com/eclipse-syson/syson/issues/1546[#1546] [diagrams] Fix drag and drop of a graphical node from the diagram background into a _Package_ graphical node. === Improvements diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/compartments/ActionTransitionUsagesTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/compartments/ActionTransitionUsagesTests.java index cc8263613..f466a391d 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/compartments/ActionTransitionUsagesTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/compartments/ActionTransitionUsagesTests.java @@ -33,7 +33,6 @@ import org.eclipse.syson.AbstractIntegrationTests; import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester; import org.eclipse.syson.application.data.ActionTransitionUsagesProjectData; -import org.eclipse.syson.diagram.common.view.services.NodeFinder; import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider; import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription; import org.eclipse.syson.services.diagrams.api.IGivenDiagramReference; @@ -41,6 +40,7 @@ import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator; import org.eclipse.syson.sysml.SysmlPackage; import org.eclipse.syson.util.IDescriptionNameGenerator; +import org.eclipse.syson.util.NodeFinder; import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDropFromDiagramTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDropFromDiagramTests.java new file mode 100644 index 000000000..bb8b3c201 --- /dev/null +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDropFromDiagramTests.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2025 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.syson.application.controllers.diagrams.general.view; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat; + +import com.jayway.jsonpath.JsonPath; + +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput; +import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload; +import org.eclipse.sirius.components.collaborative.diagrams.dto.DropNodeInput; +import org.eclipse.sirius.components.core.api.SuccessPayload; +import org.eclipse.sirius.components.diagrams.tests.graphql.DropNodeMutationRunner; +import org.eclipse.sirius.components.diagrams.tests.navigation.DiagramNavigator; +import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.syson.AbstractIntegrationTests; +import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData; +import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.transaction.annotation.Transactional; + +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +/** + * Tests the drag and drop of nodes inside the General View diagram. + * + * @author arichard + */ +@Transactional +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class GVDropFromDiagramTests extends AbstractIntegrationTests { + + @Autowired + private IGivenInitialServerState givenInitialServerState; + + @Autowired + private IGivenDiagramSubscription givenDiagramSubscription; + + @Autowired + private DropNodeMutationRunner dropNodeMutationRunner; + + private Flux givenSubscriptionToDiagram() { + var diagramEventInput = new DiagramEventInput(UUID.randomUUID(), + GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID); + var flux = this.givenDiagramSubscription.subscribe(diagramEventInput); + return flux; + } + + @BeforeEach + public void beforeEach() { + this.givenInitialServerState.initialize(); + } + + @DisplayName("Given a diagram with some nodes, when a node is dropped in another one, then the diagram is updated") + @Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, + config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @Test + public void dropPartFromDiagramToPackageThenFromPackageToPart() { + var flux = this.givenSubscriptionToDiagram(); + + var diagramId = new AtomicReference(); + var packageNodeId = new AtomicReference(); + var partNodeId = new AtomicReference(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> { + diagramId.set(diagram.getId()); + + var packageNode = new DiagramNavigator(diagram).nodeWithLabel("Package").getNode(); + packageNodeId.set(packageNode.getId()); + + var partNode = new DiagramNavigator(diagram).nodeWithLabel("\u00ABref part\u00BB\npart").getNode(); + partNodeId.set(partNode.getId()); + + assertThat(packageNode.getChildNodes()).hasSize(0); + }); + + Runnable dropPartNodeFromDiagramToPackage = () -> { + var input = new DropNodeInput( + UUID.randomUUID(), + GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID.toString(), + diagramId.get(), + partNodeId.get(), + packageNodeId.get(), + 0, + 0); + var result = this.dropNodeMutationRunner.run(input); + String typename = JsonPath.read(result, "$.data.dropNode.__typename"); + assertThat(typename).isEqualTo(SuccessPayload.class.getSimpleName()); + }; + + Consumer updatedDiagramContentConsumerAfterFirstDrop = assertRefreshedDiagramThat(diagram -> { + var packageNode = new DiagramNavigator(diagram).nodeWithLabel("Package").getNode(); + + var partNode = new DiagramNavigator(diagram).nodeWithLabel("\u00ABref part\u00BB\npart").getNode(); + partNodeId.set(partNode.getId()); + + assertThat(packageNode.getChildNodes()).hasSize(1); + assertThat(packageNode.getChildNodes().get(0)).isEqualTo(partNode); + }); + + Runnable dropPartNodeFromPackageToDiagram = () -> { + var input = new DropNodeInput( + UUID.randomUUID(), + GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID.toString(), + diagramId.get(), + partNodeId.get(), + diagramId.get(), + 0, + 0); + var result = this.dropNodeMutationRunner.run(input); + String typename = JsonPath.read(result, "$.data.dropNode.__typename"); + assertThat(typename).isEqualTo(SuccessPayload.class.getSimpleName()); + }; + + Consumer updatedDiagramContentConsumerAfterSecondDrop = assertRefreshedDiagramThat(diagram -> { + var packageNode = new DiagramNavigator(diagram).nodeWithLabel("Package").getNode(); + + var partNode = new DiagramNavigator(diagram).nodeWithLabel("\u00ABref part\u00BB\npart").getNode(); + + assertThat(packageNode.getChildNodes()).hasSize(0); + assertThat(partNode).isNotNull(); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(dropPartNodeFromDiagramToPackage) + .consumeNextWith(updatedDiagramContentConsumerAfterFirstDrop) + .then(dropPartNodeFromPackageToDiagram) + .consumeNextWith(updatedDiagramContentConsumerAfterSecondDrop) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } +} diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/DiagramComparator.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/DiagramComparator.java index a5b4fe5e3..57c2595d5 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/DiagramComparator.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/DiagramComparator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 2025 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -17,7 +17,7 @@ import org.eclipse.sirius.components.diagrams.Diagram; import org.eclipse.sirius.components.diagrams.Edge; import org.eclipse.sirius.components.diagrams.Node; -import org.eclipse.syson.diagram.common.view.services.NodeFinder; +import org.eclipse.syson.util.NodeFinder; import org.springframework.stereotype.Service; /** diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/services/ToolService.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/services/ToolService.java index ac9bc138c..5bbff3f01 100644 --- a/backend/services/syson-services/src/main/java/org/eclipse/syson/services/ToolService.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/services/ToolService.java @@ -13,13 +13,14 @@ package org.eclipse.syson.services; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import org.eclipse.sirius.components.collaborative.diagrams.api.IDiagramContext; +import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext; import org.eclipse.sirius.components.collaborative.diagrams.api.IDiagramDescriptionService; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IFeedbackMessageService; @@ -32,7 +33,6 @@ import org.eclipse.sirius.components.diagrams.ListLayoutStrategy; import org.eclipse.sirius.components.diagrams.Node; import org.eclipse.sirius.components.diagrams.ViewCreationRequest; -import org.eclipse.sirius.components.diagrams.ViewDeletionRequest; import org.eclipse.sirius.components.diagrams.ViewModifier; import org.eclipse.sirius.components.diagrams.components.NodeContainmentKind; import org.eclipse.sirius.components.diagrams.components.NodeIdProvider; @@ -42,11 +42,17 @@ import org.eclipse.sirius.components.view.diagram.DiagramDescription; import org.eclipse.syson.services.api.ISysMLMoveElementService; import org.eclipse.syson.sysml.AnnotatingElement; +import org.eclipse.syson.sysml.Comment; +import org.eclipse.syson.sysml.Documentation; import org.eclipse.syson.sysml.Element; import org.eclipse.syson.sysml.Expose; import org.eclipse.syson.sysml.NamespaceImport; +import org.eclipse.syson.sysml.Package; +import org.eclipse.syson.sysml.SysmlFactory; +import org.eclipse.syson.sysml.TextualRepresentation; import org.eclipse.syson.sysml.ViewUsage; import org.eclipse.syson.sysml.helper.EMFUtils; +import org.eclipse.syson.util.NodeFinder; /** * Tool-related Java services used by SysON representations. @@ -86,6 +92,56 @@ public ToolService(IIdentityService identityService, IObjectSearchService object this.nodeDescriptionService = new NodeDescriptionService(objectSearchService); } + /** + * For the given element, search its ViewUsage (if this service has been called from the diagram background it will + * be the ViewUsage itself), and add the given element to the exposed elements of this ViewUsage. + * + * @param element + * the given {@link Element}. + * @param editingContext + * the {@link IEditingContext} of the tool. It corresponds to a variable accessible from the variable + * manager. + * @param diagramContext + * the {@link DiagramContext} of the tool. It corresponds to a variable accessible from the variable + * manager. + * @param selectedNode + * the selected node on which the service has been called (may be null if the tool has been called from + * the diagram). It corresponds to a variable accessible from the variable manager. + * @param convertedNodes + * the map of all existing node descriptions in the DiagramDescription of this Diagram. It corresponds to + * a variable accessible from the variable manager. + * @return the given {@link Element}. + */ + public Element expose(Element element, IEditingContext editingContext, DiagramContext diagramContext, Node selectedNode, + Map convertedNodes) { + if (this.isUnsynchronized(element)) { + final Element parentElement; + if (selectedNode == null) { + parentElement = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) + .filter(Element.class::isInstance) + .map(Element.class::cast) + .orElse(null); + } else { + parentElement = this.objectSearchService.getObject(editingContext, selectedNode.getTargetObjectId()) + .filter(Element.class::isInstance) + .map(Element.class::cast) + .orElse(null); + } + this.handleUnsynchronizedElement(element, parentElement, editingContext, diagramContext, selectedNode, convertedNodes); + } else { + var viewUsage = this.getViewUsage(editingContext, diagramContext, selectedNode); + if (viewUsage != null && !this.isExposed(element, viewUsage)) { + var membershipExpose = SysmlFactory.eINSTANCE.createMembershipExpose(); + membershipExpose.setImportedMembership(element.getOwningMembership()); + viewUsage.getOwnedRelationship().add(membershipExpose); + if (element instanceof Package) { + membershipExpose.setIsRecursive(true); + } + } + } + return element; + } + /** * Get the parent node of the given {@link Element}. This could be a {@link Node} or a {@link Diagram}. * @@ -99,7 +155,7 @@ public ToolService(IIdentityService identityService, IObjectSearchService object * the diagram context in which to find the parent node. * @return a {@link Node} or a {@link Diagram}. */ - public Object getParentNode(Element element, Node selectedNode, Edge selectedEdge, IDiagramContext diagramContext) { + public Object getParentNode(Element element, Node selectedNode, Edge selectedEdge, DiagramContext diagramContext) { Object parentNode = null; if (selectedEdge != null) { Node sourceNode = this.getSourceNode(selectedEdge, diagramContext.getDiagram()); @@ -125,7 +181,7 @@ public Object getParentNode(Element element, Node selectedNode, Edge selectedEdg * the diagram context in which to find the parent node. * @return a {@link Node} or a {@link Diagram}. */ - public Object getParentNode(Element element, IDiagramElement diagramElement, IDiagramContext diagramContext) { + public Object getParentNode(Element element, IDiagramElement diagramElement, DiagramContext diagramContext) { Object parentNode = null; var diagram = diagramContext.getDiagram(); var nodes = diagram.getNodes(); @@ -144,9 +200,68 @@ public Object getParentNode(Element element, IDiagramElement diagramElement, IDi return parentNode; } + /** + * Handle unsynchronized nodes. + * + * @param element + * the given {@link Element}. + * @param parentElement + * the parent element of the given {@link Element}. + * @param editingContext + * the {@link IEditingContext} of the tool. It corresponds to a variable accessible from the variable + * manager. + * @param diagramContext + * the {@link DiagramContext} of the tool. It corresponds to a variable accessible from the variable + * manager. + * @param selectedNode + * the selected node on which the service has been called (may be null if the tool has been called from + * the diagram). It corresponds to a variable accessible from the variable manager. + * @param convertedNodes + * the map of all existing node descriptions in the DiagramDescription of this Diagram. It corresponds to + * a variable accessible from the variable manager. + */ + protected void handleUnsynchronizedElement(Element element, Element parentElement, IEditingContext editingContext, DiagramContext diagramContext, Node selectedNode, + Map convertedNodes) { + if (selectedNode == null) { + this.createView(element, editingContext, diagramContext, selectedNode, convertedNodes); + } else if (element instanceof Documentation && (parentElement instanceof Package || parentElement instanceof NamespaceImport || parentElement instanceof ViewUsage)) { + var parentNode = new NodeFinder(diagramContext.diagram()).getParent(selectedNode); + this.createView(element, editingContext, diagramContext, parentNode, convertedNodes); + } else if (element instanceof Comment && !(element instanceof Documentation)) { + var parentNode = new NodeFinder(diagramContext.diagram()).getParent(selectedNode); + this.createView(element, editingContext, diagramContext, parentNode, convertedNodes); + } else if (element instanceof TextualRepresentation) { + var parentNode = new NodeFinder(diagramContext.diagram()).getParent(selectedNode); + this.createView(element, editingContext, diagramContext, parentNode, convertedNodes); + } else { + if (selectedNode.getStyle().getChildrenLayoutStrategy() instanceof ListLayoutStrategy) { + for (Node compartmentNode : selectedNode.getChildNodes()) { + var compartmentNodeDescription = convertedNodes.values().stream() + .filter(nd -> Objects.equals(nd.getId(), compartmentNode.getDescriptionId())) + .findFirst() + .orElse(null); + var candidates = this.nodeDescriptionService.getChildNodeDescriptionsForRendering(element, parentElement, List.of(compartmentNodeDescription), convertedNodes, editingContext, + diagramContext); + for (NodeDescription candidate : candidates) { + if (candidate.getSynchronizationPolicy().equals(SynchronizationPolicy.UNSYNCHRONIZED)) { + this.createView(element, compartmentNode.getId(), candidate.getId(), editingContext, diagramContext, NodeContainmentKind.CHILD_NODE); + } + } + } + } else { + // The parent doesn't have compartments, we want to add elements directly inside it if possible. + // This is for example the case with Package elements. + this.getChildNodeDescriptionIdForRendering(element, editingContext, diagramContext, selectedNode, convertedNodes) + .ifPresent(descriptionId -> { + this.createView(element, editingContext, diagramContext, selectedNode, convertedNodes); + }); + } + } + } + /** * Remove the given Element from the exposedElements reference of the {@link ViewUsage} that is the target of the - * given {@link IDiagramContext}. Also removes potential children that are sub-nodes of the given selectedNode + * given {@link DiagramContext}. Also removes potential children that are sub-nodes of the given selectedNode * corresponding to the given Element. * * @param element @@ -156,10 +271,10 @@ public Object getParentNode(Element element, IDiagramElement diagramElement, IDi * @param editingContext * the given {@link IEditingContext} in which this service has been called. * @param diagramContext - * the given {@link IDiagramContext}. + * the given {@link DiagramContext}. * @return always true. */ - public boolean removeFromExposedElements(Element element, Node selectedNode, IEditingContext editingContext, IDiagramContext diagramContext) { + public boolean removeFromExposedElements(Element element, Node selectedNode, IEditingContext editingContext, DiagramContext diagramContext) { var optDiagramTargetObject = this.objectSearchService.getObject(editingContext, diagramContext.getDiagram().getTargetObjectId()); if (optDiagramTargetObject.isPresent()) { var diagramTargetObject = optDiagramTargetObject.get(); @@ -188,7 +303,7 @@ protected Node getParentNode(IDiagramElement diagramElement, Node nodeContainer) .orElse(null); } - protected List getChildNodes(IDiagramContext diagramContext, Object selectedNode) { + protected List getChildNodes(DiagramContext diagramContext, Object selectedNode) { var childNodes = new ArrayList(); if (selectedNode instanceof Node node) { childNodes.addAll(node.getChildNodes()); @@ -208,12 +323,12 @@ protected boolean isPresent(Element element, List nodes) { } - protected ViewCreationRequest createView(Element element, IEditingContext editingContext, IDiagramContext diagramContext, Object selectedNode, + protected ViewCreationRequest createView(Element element, IEditingContext editingContext, DiagramContext diagramContext, Object selectedNode, Map convertedNodes) { return this.createView(element, editingContext, diagramContext, selectedNode, convertedNodes, NodeContainmentKind.CHILD_NODE); } - protected ViewCreationRequest createView(Element element, IEditingContext editingContext, IDiagramContext diagramContext, Object selectedNode, + protected ViewCreationRequest createView(Element element, IEditingContext editingContext, DiagramContext diagramContext, Object selectedNode, Map convertedNodes, NodeContainmentKind nodeKind) { var optDescriptionId = this.getChildNodeDescriptionIdForRendering(element, editingContext, diagramContext, selectedNode, convertedNodes); if (optDescriptionId.isPresent()) { @@ -224,7 +339,7 @@ protected ViewCreationRequest createView(Element element, IEditingContext editin } } - protected ViewCreationRequest createView(Element element, String parentElementId, String descriptionId, IEditingContext editingContext, IDiagramContext diagramContext, + protected ViewCreationRequest createView(Element element, String parentElementId, String descriptionId, IEditingContext editingContext, DiagramContext diagramContext, NodeContainmentKind nodeKind) { ViewCreationRequest request = null; var diagramDescription = this.representationDescriptionSearchService.findById(editingContext, diagramContext.getDiagram().getDescriptionId()) @@ -258,7 +373,7 @@ protected ViewCreationRequest createView(Element element, String parentElementId * the diagram context * @return the identifier of {@code graphicalElement}'s parent */ - protected String getParentElementId(Object graphicalElement, IDiagramContext diagramContext) { + protected String getParentElementId(Object graphicalElement, DiagramContext diagramContext) { final String parentElementId; if (graphicalElement instanceof Node node) { parentElementId = node.getId(); @@ -293,7 +408,7 @@ protected String getParentElementId(Object graphicalElement, IDiagramContext dia * the converted nodes * @return the description id */ - protected Optional getChildNodeDescriptionIdForRendering(Element element, IEditingContext editingContext, IDiagramContext diagramContext, Object parent, + protected Optional getChildNodeDescriptionIdForRendering(Element element, IEditingContext editingContext, DiagramContext diagramContext, Object parent, Map convertedNodes) { List candidates = new ArrayList<>(); final Object parentObject; @@ -330,12 +445,88 @@ protected Optional getChildNodeDescriptionIdForRendering(Element element .findFirst(); } - protected void moveElement(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext, IDiagramContext diagramContext, + /** + * Search and retrieve the ViewUsage corresponding to the parent Node/diagram of the given Node. + * + * @param editingContext + * the {@link IEditingContext} of the tool. It corresponds to a variable accessible from the variable + * manager. + * @param diagramContext + * the {@link DiagramContext} of the tool. It corresponds to a variable accessible from the variable + * manager. + * @param node + * the selected node on which the element has been dropped (may be null if the tool has been called from + * the diagram). It corresponds to a variable accessible from the variable manager. + * @return an Optional ViewUsage if found, an empty Optional otherwise. + */ + protected ViewUsage getViewUsage(IEditingContext editingContext, DiagramContext diagramContext, Node node) { + Optional optViewUsage = Optional.empty(); + if (node == null) { + optViewUsage = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) + .filter(ViewUsage.class::isInstance) + .map(ViewUsage.class::cast); + } else { + optViewUsage = this.objectSearchService.getObject(editingContext, node.getTargetObjectId()) + .filter(ViewUsage.class::isInstance) + .map(ViewUsage.class::cast); + } + if (optViewUsage.isEmpty()) { + List rootNodes = diagramContext.diagram().getNodes(); + for (Node rootNode : rootNodes) { + if (Objects.equals(rootNode, node)) { + optViewUsage = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) + .filter(ViewUsage.class::isInstance) + .map(ViewUsage.class::cast); + break; + } + } + } + if (optViewUsage.isEmpty()) { + List rootNodes = diagramContext.diagram().getNodes(); + List allSubNodes = this.getAllSubNodes(rootNodes); + for (Node subNode : allSubNodes) { + if (subNode.getChildNodes().contains(node)) { + var vu = this.getViewUsage(editingContext, diagramContext, subNode); + if (vu != null) { + optViewUsage = Optional.of(vu); + break; + } + } + } + } + if (optViewUsage.isEmpty()) { + optViewUsage = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) + .filter(ViewUsage.class::isInstance) + .map(ViewUsage.class::cast); + } + return optViewUsage.orElse(null); + } + + /** + * Get all sub nodes (nod border nodes, only child nodes) of the given list of nodes. + * + * @param nodes + * the given list of nodes. + * @return all sub nodes of the given list of nodes. + */ + protected List getAllSubNodes(List nodes) { + var allSubNodes = new LinkedList(); + for (Node node : nodes) { + var children = new LinkedList(); + children.addAll(node.getChildNodes()); + allSubNodes.addAll(this.getAllSubNodes(children)); + } + return allSubNodes; + } + + protected void moveElement(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext, DiagramContext diagramContext, Map convertedNodes) { this.moveService.moveSemanticElement(droppedElement, targetElement); - ViewCreationRequest droppedElementViewCreationRequest = this.createView(droppedElement, editingContext, diagramContext, targetNode, convertedNodes); - this.moveSubNodes(droppedElementViewCreationRequest, droppedNode, diagramContext); - diagramContext.getViewDeletionRequests().add(ViewDeletionRequest.newViewDeletionRequest().elementId(droppedNode.getId()).build()); + ViewUsage viewUsage = this.getViewUsage(editingContext, diagramContext, droppedNode); + if (viewUsage != null) { + this.removeFromExposedElements(droppedElement, droppedNode, editingContext, diagramContext); + } + this.expose(droppedElement, editingContext, diagramContext, targetNode, convertedNodes); } @@ -371,7 +562,7 @@ protected Optional g * @param diagramContext * the diagram context */ - protected void moveSubNodes(ViewCreationRequest parentViewCreationRequest, Node parentNode, IDiagramContext diagramContext) { + protected void moveSubNodes(ViewCreationRequest parentViewCreationRequest, Node parentNode, DiagramContext diagramContext) { for (Node childNode : parentNode.getChildNodes()) { ViewCreationRequest childViewCreationRequest = ViewCreationRequest.newViewCreationRequest() .containmentKind(NodeContainmentKind.CHILD_NODE) diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/NodeFinder.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/NodeFinder.java similarity index 98% rename from backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/NodeFinder.java rename to backend/services/syson-services/src/main/java/org/eclipse/syson/util/NodeFinder.java index f11be9be7..c1102ddda 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/NodeFinder.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/NodeFinder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 2025 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -10,7 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -package org.eclipse.syson.diagram.common.view.services; +package org.eclipse.syson.util; import java.util.ArrayList; import java.util.List; diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractPackageNodeDescriptionProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractPackageNodeDescriptionProvider.java index 9db672934..308a40ac6 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractPackageNodeDescriptionProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractPackageNodeDescriptionProvider.java @@ -17,7 +17,7 @@ import java.util.Objects; import org.eclipse.emf.ecore.EClass; -import org.eclipse.sirius.components.collaborative.diagrams.api.IDiagramContext; +import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.diagrams.Node; import org.eclipse.sirius.components.view.builder.IViewDiagramElementFinder; @@ -132,7 +132,7 @@ public NodeDescription create() { .insideLabel(this.createInsideLabelDescription()) .name(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getPackage())) .semanticCandidatesExpression(AQLUtils.getSelfServiceCallExpression("getExposedElements", - List.of(domainType, org.eclipse.sirius.components.diagrams.description.NodeDescription.ANCESTORS, IEditingContext.EDITING_CONTEXT, IDiagramContext.DIAGRAM_CONTEXT))) + List.of(domainType, org.eclipse.sirius.components.diagrams.description.NodeDescription.ANCESTORS, IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT))) .style(this.createPackageNodeStyle()) .userResizable(UserResizableDirection.BOTH) .synchronizationPolicy(SynchronizationPolicy.SYNCHRONIZED) @@ -221,7 +221,7 @@ private List getEdgeTools(NodeDescription nodeDescription, IViewDiagra private DropNodeTool createDropFromDiagramTool(IViewDiagramElementFinder cache) { var dropElementFromDiagram = this.viewBuilderHelper.newChangeContext() .expression(AQLUtils.getServiceCallExpression("droppedElement", "dropElementFromDiagram", - List.of("droppedNode", "targetElement", "targetNode", IEditingContext.EDITING_CONTEXT, IDiagramContext.DIAGRAM_CONTEXT, + List.of("droppedNode", "targetElement", "targetNode", IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))); return this.diagramBuilderHelper.newDropNodeTool() @@ -270,7 +270,7 @@ private NodeTool createNodeTool(NodeDescription nodeDescription, EClass eClass) var updateExposedElements = this.viewBuilderHelper.newChangeContext() .expression(AQLUtils.getSelfServiceCallExpression("expose", - List.of(IEditingContext.EDITING_CONTEXT, IDiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))); + List.of(IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))); var changeContextNewInstance = this.viewBuilderHelper.newChangeContext() .expression(AQLUtils.getServiceCallExpression("newInstance", "elementInitializer")) diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewCreateService.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewCreateService.java index 027729ec8..b857db64a 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewCreateService.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewCreateService.java @@ -83,6 +83,7 @@ import org.eclipse.syson.sysml.UseCaseDefinition; import org.eclipse.syson.sysml.UseCaseUsage; import org.eclipse.syson.sysml.util.ElementUtil; +import org.eclipse.syson.util.NodeFinder; /** * Creation-related Java shared services used by several diagrams. diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java index 47de90afd..520b8dc70 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java @@ -57,6 +57,7 @@ import org.eclipse.syson.sysml.ViewDefinition; import org.eclipse.syson.sysml.ViewUsage; import org.eclipse.syson.sysml.util.ElementUtil; +import org.eclipse.syson.util.NodeFinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java index 9350bafda..07e13ab65 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java @@ -18,7 +18,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -43,14 +42,12 @@ import org.eclipse.sirius.components.core.api.IObjectSearchService; import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; import org.eclipse.sirius.components.diagrams.Diagram; -import org.eclipse.sirius.components.diagrams.ListLayoutStrategy; import org.eclipse.sirius.components.diagrams.Node; import org.eclipse.sirius.components.diagrams.ViewCreationRequest; import org.eclipse.sirius.components.diagrams.ViewDeletionRequest; import org.eclipse.sirius.components.diagrams.ViewModifier; import org.eclipse.sirius.components.diagrams.components.NodeContainmentKind; import org.eclipse.sirius.components.diagrams.description.NodeDescription; -import org.eclipse.sirius.components.diagrams.description.SynchronizationPolicy; import org.eclipse.sirius.components.diagrams.events.HideDiagramElementEvent; import org.eclipse.sirius.components.emf.ResourceMetadataAdapter; import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext; @@ -76,7 +73,6 @@ import org.eclipse.syson.sysml.FeatureMembership; import org.eclipse.syson.sysml.Membership; import org.eclipse.syson.sysml.Namespace; -import org.eclipse.syson.sysml.NamespaceImport; import org.eclipse.syson.sysml.ObjectiveMembership; import org.eclipse.syson.sysml.OwningMembership; import org.eclipse.syson.sysml.Package; @@ -90,13 +86,13 @@ import org.eclipse.syson.sysml.SubjectMembership; import org.eclipse.syson.sysml.SysmlFactory; import org.eclipse.syson.sysml.SysmlPackage; -import org.eclipse.syson.sysml.TextualRepresentation; import org.eclipse.syson.sysml.Type; import org.eclipse.syson.sysml.Usage; import org.eclipse.syson.sysml.UseCaseDefinition; import org.eclipse.syson.sysml.UseCaseUsage; import org.eclipse.syson.sysml.ViewUsage; import org.eclipse.syson.sysml.helper.EMFUtils; +import org.eclipse.syson.util.NodeFinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -167,56 +163,6 @@ public Element addToExposedElements(Element element, boolean recursive, IEditing return element; } - /** - * For the given element, search its ViewUsage (if this service has been called from the diagram background it will - * be the ViewUsage itself), and add the given element to the exposed elements of this ViewUsage. - * - * @param element - * the given {@link Element}. - * @param editingContext - * the {@link IEditingContext} of the tool. It corresponds to a variable accessible from the variable - * manager. - * @param diagramContext - * the {@link DiagramContext} of the tool. It corresponds to a variable accessible from the variable - * manager. - * @param selectedNode - * the selected node on which the service has been called (may be null if the tool has been called from - * the diagram). It corresponds to a variable accessible from the variable manager. - * @param convertedNodes - * the map of all existing node descriptions in the DiagramDescription of this Diagram. It corresponds to - * a variable accessible from the variable manager. - * @return the given {@link Element}. - */ - public Element expose(Element element, IEditingContext editingContext, DiagramContext diagramContext, Node selectedNode, - Map convertedNodes) { - if (this.isUnsynchronized(element)) { - final Element parentElement; - if (selectedNode == null) { - parentElement = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) - .filter(Element.class::isInstance) - .map(Element.class::cast) - .orElse(null); - } else { - parentElement = this.objectSearchService.getObject(editingContext, selectedNode.getTargetObjectId()) - .filter(Element.class::isInstance) - .map(Element.class::cast) - .orElse(null); - } - this.handleUnsynchronizedElement(element, parentElement, editingContext, diagramContext, selectedNode, convertedNodes); - } else { - var viewUsage = this.getViewUsage(editingContext, diagramContext, selectedNode); - if (viewUsage != null && !this.isExposed(element, viewUsage)) { - var membershipExpose = SysmlFactory.eINSTANCE.createMembershipExpose(); - membershipExpose.setImportedMembership(element.getOwningMembership()); - viewUsage.getOwnedRelationship().add(membershipExpose); - if (element instanceof Package) { - membershipExpose.setIsRecursive(true); - } - } - } - return element; - } - /** * Called by "Drop tool" from General View diagram. * @@ -800,139 +746,6 @@ public List getActionReferenceSelectionDialogChildren(Object s return this.getChildrenWithInstancesOf(selectionDialogTreeElement, SysmlPackage.eINSTANCE.getActionUsage()); } - /** - * Handle unsynchronized nodes. - * - * @param element - * the given {@link Element}. - * @param parentElement - * the parent element of the given {@link Element}. - * @param editingContext - * the {@link IEditingContext} of the tool. It corresponds to a variable accessible from the variable - * manager. - * @param diagramContext - * the {@link DiagramContext} of the tool. It corresponds to a variable accessible from the variable - * manager. - * @param selectedNode - * the selected node on which the service has been called (may be null if the tool has been called from - * the diagram). It corresponds to a variable accessible from the variable manager. - * @param convertedNodes - * the map of all existing node descriptions in the DiagramDescription of this Diagram. It corresponds to - * a variable accessible from the variable manager. - */ - protected void handleUnsynchronizedElement(Element element, Element parentElement, IEditingContext editingContext, DiagramContext diagramContext, Node selectedNode, - Map convertedNodes) { - if (selectedNode == null) { - this.createView(element, editingContext, diagramContext, selectedNode, convertedNodes); - } else if (element instanceof Documentation && (parentElement instanceof Package || parentElement instanceof NamespaceImport || parentElement instanceof ViewUsage)) { - var parentNode = new NodeFinder(diagramContext.diagram()).getParent(selectedNode); - this.createView(element, editingContext, diagramContext, parentNode, convertedNodes); - } else if (element instanceof Comment && !(element instanceof Documentation)) { - var parentNode = new NodeFinder(diagramContext.diagram()).getParent(selectedNode); - this.createView(element, editingContext, diagramContext, parentNode, convertedNodes); - } else if (element instanceof TextualRepresentation) { - var parentNode = new NodeFinder(diagramContext.diagram()).getParent(selectedNode); - this.createView(element, editingContext, diagramContext, parentNode, convertedNodes); - } else { - if (selectedNode.getStyle().getChildrenLayoutStrategy() instanceof ListLayoutStrategy) { - for (Node compartmentNode : selectedNode.getChildNodes()) { - var compartmentNodeDescription = convertedNodes.values().stream() - .filter(nd -> Objects.equals(nd.getId(), compartmentNode.getDescriptionId())) - .findFirst() - .orElse(null); - var candidates = this.nodeDescriptionService.getChildNodeDescriptionsForRendering(element, parentElement, List.of(compartmentNodeDescription), convertedNodes, editingContext, - diagramContext); - for (NodeDescription candidate : candidates) { - if (candidate.getSynchronizationPolicy().equals(SynchronizationPolicy.UNSYNCHRONIZED)) { - this.createView(element, compartmentNode.getId(), candidate.getId(), editingContext, diagramContext, NodeContainmentKind.CHILD_NODE); - } - } - } - } else { - // The parent doesn't have compartments, we want to add elements directly inside it if possible. - // This is for example the case with Package elements. - this.getChildNodeDescriptionIdForRendering(element, editingContext, diagramContext, selectedNode, convertedNodes) - .ifPresent(descriptionId -> { - this.createView(element, editingContext, diagramContext, selectedNode, convertedNodes); - }); - } - } - } - - /** - * Search and retrieve the ViewUsage corresponding to the parent Node/diagram of the given Node. - * - * @param editingContext - * the {@link IEditingContext} of the tool. It corresponds to a variable accessible from the variable - * manager. - * @param diagramContext - * the {@link DiagramContext} of the tool. It corresponds to a variable accessible from the variable - * manager. - * @param node - * the selected node on which the element has been dropped (may be null if the tool has been called from - * the diagram). It corresponds to a variable accessible from the variable manager. - * @return an Optional ViewUsage if found, an empty Optional otherwise. - */ - protected ViewUsage getViewUsage(IEditingContext editingContext, DiagramContext diagramContext, Node node) { - Optional optViewUsage = Optional.empty(); - if (node == null) { - optViewUsage = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) - .filter(ViewUsage.class::isInstance) - .map(ViewUsage.class::cast); - } else { - optViewUsage = this.objectSearchService.getObject(editingContext, node.getTargetObjectId()) - .filter(ViewUsage.class::isInstance) - .map(ViewUsage.class::cast); - } - if (optViewUsage.isEmpty()) { - List rootNodes = diagramContext.diagram().getNodes(); - for (Node rootNode : rootNodes) { - if (Objects.equals(rootNode, node)) { - optViewUsage = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) - .filter(ViewUsage.class::isInstance) - .map(ViewUsage.class::cast); - break; - } - } - } - if (optViewUsage.isEmpty()) { - List rootNodes = diagramContext.diagram().getNodes(); - List allSubNodes = this.getAllSubNodes(rootNodes); - for (Node subNode : allSubNodes) { - if (subNode.getChildNodes().contains(node)) { - var vu = this.getViewUsage(editingContext, diagramContext, subNode); - if (vu != null) { - optViewUsage = Optional.of(vu); - break; - } - } - } - } - if (optViewUsage.isEmpty()) { - optViewUsage = this.objectSearchService.getObject(editingContext, diagramContext.diagram().getTargetObjectId()) - .filter(ViewUsage.class::isInstance) - .map(ViewUsage.class::cast); - } - return optViewUsage.orElse(null); - } - - /** - * Get all sub nodes (nod border nodes, only child nodes) of the given list of nodes. - * - * @param nodes - * the given list of nodes. - * @return all sub nodes of the given list of nodes. - */ - protected List getAllSubNodes(List nodes) { - var allSubNodes = new LinkedList(); - for (Node node : nodes) { - var children = new LinkedList(); - children.addAll(node.getChildNodes()); - allSubNodes.addAll(this.getAllSubNodes(children)); - } - return allSubNodes; - } - /** * Drops the provided {@code sourceElement} from the explorer on the given {@code selectedNode} on the diagram. *

diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/PackageNodeDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/PackageNodeDescriptionProvider.java index f56c7f699..933b990e1 100644 --- a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/PackageNodeDescriptionProvider.java +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/PackageNodeDescriptionProvider.java @@ -28,7 +28,7 @@ import org.eclipse.syson.sysml.SysmlPackage; /** - * Used to create the package node description in the General View diagram. + * Used to create the package node description in the Standard Diagram Views. * * @author arichard */ diff --git a/doc/content/modules/user-manual/pages/release-notes/2025.10.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2025.10.0.adoc index e17379895..c8c9bf361 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2025.10.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2025.10.0.adoc @@ -73,7 +73,7 @@ This issue was not visible in SysON but could appear in downstream applications Now it is only available on SysML model elements. - Fix library update performance and ensure it does not impact standard libraries. - Fix an issue where diagrams' edges were blinking when refreshing a diagram. -- Add _interconnection_ compartment to `PartDefinition` nodes in the standard diagrams. +- Fix drag and drop of a graphical node from the diagram background into a _Package_ graphical node. == Improvements