diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 6323b41f7..0f7eebc61 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -21,6 +21,7 @@ - https://github.com/eclipse-syson/syson/issues/2174[#2174] [diagrams] Add a new edge tool creating a _frame_ graphical edge between a `RequirementUsage` or a `RequirementDefinition` graphical node, and a `ConcernUsage` graphical node. - https://github.com/eclipse-syson/syson/issues/2116[#2116] [explorer] In the _Explorer_ view, the items corresponding to the internals of `Expression` elements (syntax tree) are now hidden by default. Disabling the _Hide expression internals_ filter in the _Explorer_ view allows to display them if needed. +- https://github.com/eclipse-syson/syson/issues/2112[#2112] [diagrams] Add tools to create _Start_ and _Done_ `StateUsages`, available on `StateUsage` and `StateDefinition` graphical nodes. == v2026.5.0 diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVNewStartDoneStatesTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVNewStartDoneStatesTests.java new file mode 100644 index 000000000..2d728c57a --- /dev/null +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVNewStartDoneStatesTests.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2026 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.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat; + +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput; +import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload; +import org.eclipse.sirius.components.core.api.IObjectSearchService; +import org.eclipse.sirius.components.diagrams.Diagram; +import org.eclipse.sirius.components.graphql.tests.api.IExecuteEditingContextFunctionRunner; +import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider; +import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.syson.AbstractIntegrationTests; +import org.eclipse.syson.GivenSysONServer; +import org.eclipse.syson.application.controllers.diagrams.checkers.CheckDiagramElementCount; +import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester; +import org.eclipse.syson.application.controllers.utils.TestNameGenerator; +import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData; +import org.eclipse.syson.services.diagrams.DiagramComparator; +import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider; +import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription; +import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription; +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.SysONRepresentationDescriptionIdentifiers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +/** + * Tests the New Start State and New Done State tools. + * + * @author Jerome Gout + */ +@Transactional +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class GVNewStartDoneStatesTests extends AbstractIntegrationTests { + + private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator(); + + @Autowired + private IGivenInitialServerState givenInitialServerState; + + @Autowired + private IGivenDiagramDescription givenDiagramDescription; + + @Autowired + private IGivenDiagramSubscription givenDiagramSubscription; + + @Autowired + private IDiagramIdProvider diagramIdProvider; + + @Autowired + private ToolTester nodeCreationTester; + + @Autowired + private DiagramComparator diagramComparator; + + @Autowired + private IExecuteEditingContextFunctionRunner executeEditingContextFunctionRunner; + + @Autowired + private IObjectSearchService objectSearchService; + + private Flux givenSubscriptionToDiagram() { + var diagramEventInput = new DiagramEventInput(UUID.randomUUID(), + GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID); + return this.givenDiagramSubscription.subscribe(diagramEventInput); + } + + @BeforeEach + public void setUp() { + this.givenInitialServerState.initialize(); + } + + private static Stream toolParameters() { + return Stream.of(Arguments.of("New Start State"), Arguments.of("New Done State")).map(TestNameGenerator::namedArguments); + } + + @DisplayName("GIVEN a SysML Project with a StateUsage inside a package named States, WHEN invoking $toolName, THEN the state is added in the state transition compartment") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @ParameterizedTest + @MethodSource("toolParameters") + public void checkNewStartDoneStateInStateUsage(String toolName) { + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + String creationToolId = diagramDescriptionIdProvider.getNodeToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage()), toolName); + AtomicReference diagram = new AtomicReference<>(); + + var flux = this.givenSubscriptionToDiagram(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable invokeCreationTool = () -> this.nodeCreationTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagram, GeneralViewWithTopNodesTestProjectData.SemanticIds.STATE_USAGE_ID, creationToolId); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + new CheckDiagramElementCount(this.diagramComparator) + .hasNewNodeCount(1) + .check(diagram.get(), newDiagram); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(invokeCreationTool) + .consumeNextWith(diagramCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + @DisplayName("GIVEN a SysML Project with a StateDefinition inside a package named States, WHEN invoking $toolName, THEN the state is added in the state transition compartment") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @ParameterizedTest + @MethodSource("toolParameters") + public void checkNewStartDoneStateInStateDefinition(String toolName) { + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + String creationToolId = diagramDescriptionIdProvider.getNodeToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateDefinition()), toolName); + AtomicReference diagram = new AtomicReference<>(); + + var flux = this.givenSubscriptionToDiagram(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable invokeCreationTool = () -> this.nodeCreationTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagram, GeneralViewWithTopNodesTestProjectData.SemanticIds.STATE_DEFINITION_ID , creationToolId); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + new CheckDiagramElementCount(this.diagramComparator) + .hasNewNodeCount(1) + .check(diagram.get(), newDiagram); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(invokeCreationTool) + .consumeNextWith(diagramCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + +} diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/services/UtilService.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/services/UtilService.java index ce289d355..2cba4507f 100644 --- a/backend/services/syson-services/src/main/java/org/eclipse/syson/services/UtilService.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/services/UtilService.java @@ -238,7 +238,7 @@ public List getAllNonExhibitStates(Element element) { * * @param eObject * the {@link EObject} stored in a {@link ResourceSet} - * @param type + * @param eClass * the searched type, represented by its qualified name * @return a list of reachable object */ @@ -246,6 +246,20 @@ public List getAllReachable(EObject eObject, EClass eClass) { return this.getAllReachableType(eObject, eClass); } + /** + * Get all reachable elements of the type given by the {@link EClass} in the {@link ResourceSet} of the given type + * (represented by its qualified name) without considering elements inside standard libs. + * + * @param eObject + * the {@link EObject} stored in a {@link ResourceSet} + * @param eClass + * the searched type, represented by its qualified name + * @return a list of reachable object + */ + public List getAllReachableWithoutStandardLibs(EObject eObject, EClass eClass) { + return this.getAllReachableType(eObject, eClass, false); + } + /** * Get all reachable elements of the type given by the {@link EClass} in the {@link ResourceSet} of the given * {@link EObject}. @@ -605,6 +619,18 @@ public ActionUsage retrieveStandardStartAction(Element eObject) { return this.findByNameAndTypeInStandardLibraries(eObject, ActionUsage.class, "Actions::Action::start"); } + /** + * Retrieve the start state defined inside the standard library States. + * + * @param eObject + * an object to access to the library resources. + * + * @return the standard start StateUsage defined in the States library. + */ + public StateUsage retrieveStandardStartState(Element eObject) { + return this.findByNameAndTypeInStandardLibraries(eObject, StateUsage.class, "States::StateAction::start"); + } + /** * Retrieve the done action defined inside the standard library Actions. * @@ -617,6 +643,18 @@ public ActionUsage retrieveStandardDoneAction(Element eObject) { return this.findByNameAndTypeInStandardLibraries(eObject, ActionUsage.class, "Actions::Action::done"); } + /** + * Retrieve the done state defined inside the standard library States. + * + * @param eObject + * an object to access to the library resources. + * + * @return the standard done StateUsage defined in the States library. + */ + public ActionUsage retrieveStandardDoneState(Element eObject) { + return this.findByNameAndTypeInStandardLibraries(eObject, ActionUsage.class, "States::StateAction::done"); + } + private T findByNameAndTypeInStandardLibraries(Element context, Class klass, String qualifiedName) { return context.eResource().getResourceSet().getResources().stream() .flatMap(resource -> this.getLibraries(resource, true).stream()) @@ -1022,6 +1060,10 @@ public boolean isUnsynchronized(Element element) { isUnsynchronized = true; } else if (Objects.equals(element, this.retrieveStandardDoneAction(element))) { isUnsynchronized = true; + } else if (Objects.equals(element, this.retrieveStandardStartState(element))) { + isUnsynchronized = true; + } else if (Objects.equals(element, this.retrieveStandardDoneState(element))) { + isUnsynchronized = true; } else if (element instanceof NamespaceImport) { isUnsynchronized = true; } else if (element instanceof AnnotatingElement) { diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/edges/AbstractTransitionEdgeDescriptionProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/edges/AbstractTransitionEdgeDescriptionProvider.java index d4b8ad14c..5f7b79301 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/edges/AbstractTransitionEdgeDescriptionProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/edges/AbstractTransitionEdgeDescriptionProvider.java @@ -33,7 +33,9 @@ import org.eclipse.sirius.components.view.diagram.NodeTool; import org.eclipse.sirius.components.view.diagram.SynchronizationPolicy; import org.eclipse.syson.diagram.common.view.nodes.DoneActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.DoneStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StartActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.StartStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.services.ViewEdgeService; import org.eclipse.syson.diagram.services.aql.DiagramQueryAQLService; import org.eclipse.syson.services.UtilService; @@ -73,7 +75,7 @@ public EdgeDescription create() { .name(this.getEdgeDescriptionName()) .preconditionExpression(ServiceMethod.of2(ViewEdgeService::isInSameGraphicalContainer).aql(org.eclipse.sirius.components.diagrams.description.EdgeDescription.GRAPHICAL_EDGE_SOURCE, org.eclipse.sirius.components.diagrams.description.EdgeDescription.GRAPHICAL_EDGE_TARGET, org.eclipse.sirius.components.diagrams.description.DiagramDescription.CACHE)) - .semanticCandidatesExpression(ServiceMethod.of1(UtilService::getAllReachable).aqlSelf(domainType)) + .semanticCandidatesExpression(ServiceMethod.of1(UtilService::getAllReachableWithoutStandardLibs).aqlSelf(domainType)) .sourceExpression(AQLConstants.AQL_SELF + "." + SysmlPackage.eINSTANCE.getTransitionUsage_Source().getName()) .style(this.createDefaultEdgeStyle()) .conditionalStyles(this.createStateConditionalStyle()) @@ -181,11 +183,13 @@ protected List getAllActionOrStateUsage(IViewDiagramElementFind } protected boolean isStartNode(NodeDescription n) { - return Objects.equals(this.getDescriptionNameGenerator().getNodeName(StartActionNodeDescriptionProvider.START_ACTION_NAME), n.getName()); + return Objects.equals(this.getDescriptionNameGenerator().getNodeName(StartActionNodeDescriptionProvider.START_ACTION_NAME), n.getName()) || + Objects.equals(this.getDescriptionNameGenerator().getNodeName(StartStateNodeDescriptionProvider.START_STATE_NAME), n.getName()); } protected boolean isDoneNode(NodeDescription n) { - return Objects.equals(this.getDescriptionNameGenerator().getNodeName(DoneActionNodeDescriptionProvider.DONE_ACTION_NAME), n.getName()); + return Objects.equals(this.getDescriptionNameGenerator().getNodeName(DoneActionNodeDescriptionProvider.DONE_ACTION_NAME), n.getName()) || + Objects.equals(this.getDescriptionNameGenerator().getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME), n.getName()); } protected boolean isActionOrStateUsage(NodeDescription nodeDescription) { diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractFakeNodeDescriptionProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractFakeNodeDescriptionProvider.java index aa45b3078..5e378b53f 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractFakeNodeDescriptionProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractFakeNodeDescriptionProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023, 2025 Obeo. + * Copyright (c) 2023, 2026 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 @@ -124,5 +124,9 @@ protected void addReusableCustomNodes(IViewDiagramElementFinder cache, List { + nodeDescription.setPalette(this.createNodePalette()); + }); + } + + private NodePalette createNodePalette() { + return this.diagramBuilderHelper.newNodePalette() + .quickAccessTools(this.getDeleteFromDiagramTool()) + .toolSections(this.defaultToolsFactory.createDefaultHideRevealNodeToolSection()) + .build(); + } +} diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/StartStateNodeDescriptionProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/StartStateNodeDescriptionProvider.java new file mode 100644 index 000000000..4930c5463 --- /dev/null +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/StartStateNodeDescriptionProvider.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2026 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.diagram.common.view.nodes; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.sirius.components.view.builder.IViewDiagramElementFinder; +import org.eclipse.sirius.components.view.builder.providers.IColorProvider; +import org.eclipse.sirius.components.view.diagram.DiagramDescription; +import org.eclipse.sirius.components.view.diagram.EdgeTool; +import org.eclipse.sirius.components.view.diagram.NodeDescription; +import org.eclipse.sirius.components.view.diagram.NodePalette; +import org.eclipse.sirius.components.view.diagram.SynchronizationPolicy; +import org.eclipse.sirius.components.view.diagram.UserResizableDirection; +import org.eclipse.syson.diagram.common.view.DescriptionFinder; +import org.eclipse.syson.diagram.common.view.services.ViewEdgeToolService; +import org.eclipse.syson.services.UtilService; +import org.eclipse.syson.sysml.SysmlPackage; +import org.eclipse.syson.util.IDescriptionNameGenerator; +import org.eclipse.syson.util.ServiceMethod; +import org.eclipse.syson.util.SysMLMetamodelHelper; + +/** + * Used to create the starting node description of a state. + * + * @author Jerome Gout + */ +public class StartStateNodeDescriptionProvider extends AbstractNodeDescriptionProvider { + + public static final String START_STATE_NAME = "StartState"; + + private final IDescriptionNameGenerator descriptionNameGenerator; + + public StartStateNodeDescriptionProvider(IColorProvider colorProvider, IDescriptionNameGenerator descriptionNameGenerator) { + super(colorProvider); + this.descriptionNameGenerator = Objects.requireNonNull(descriptionNameGenerator); + } + + @Override + public NodeDescription create() { + String domainType = SysMLMetamodelHelper.buildQualifiedName(SysmlPackage.eINSTANCE.getStateUsage()); + return this.diagramBuilderHelper.newNodeDescription() + .collapsible(false) + .domainType(domainType) + .defaultWidthExpression("28") + .defaultHeightExpression("28") + .name(this.descriptionNameGenerator.getNodeName(START_STATE_NAME)) + .semanticCandidatesExpression(ServiceMethod.of0(UtilService::retrieveStandardStartState).aqlSelf()) + .style(this.createImageNodeStyleDescription("images/start_action.svg")) + .userResizable(UserResizableDirection.NONE) + .synchronizationPolicy(SynchronizationPolicy.UNSYNCHRONIZED) + .build(); + } + + @Override + public void link(DiagramDescription diagramDescription, IViewDiagramElementFinder cache) { + // this nodeDescription has not been added to the diagramDescription children but to the fakeNodeDescription + // children instead + cache.getNodeDescription(this.descriptionNameGenerator.getNodeName(START_STATE_NAME)).ifPresent(nodeDescription -> { + nodeDescription.setPalette(this.createNodePalette(cache)); + }); + } + + private NodePalette createNodePalette(IViewDiagramElementFinder cache) { + var edgeTools = new ArrayList(this.getEdgeTools(cache)); + + return this.diagramBuilderHelper.newNodePalette() + .edgeTools(edgeTools.toArray(EdgeTool[]::new)) + .quickAccessTools(this.getDeleteFromDiagramTool()) + .toolSections(this.defaultToolsFactory.createDefaultHideRevealNodeToolSection()) + .build(); + } + + private List getEdgeTools(IViewDiagramElementFinder cache) { + var targetElementDescriptions = this.getStartTargetDescriptions(cache); + var edgeToolService = new ViewEdgeToolService(this.viewBuilderHelper, this.diagramBuilderHelper, cache.getNodeDescriptions(), this.descriptionNameGenerator); + var connectionTool = edgeToolService.createConnectionUsageEdgeTool(new DescriptionFinder(this.descriptionNameGenerator).getConnectableNodeDescriptions(cache.getNodeDescriptions(), SysmlPackage.eINSTANCE.getStateUsage())); + var transitionTool = edgeToolService.createTransitionUsageEdgeTool(SysmlPackage.eINSTANCE.getTransitionUsage(), targetElementDescriptions); + return List.of(transitionTool, connectionTool); + } + + private List getStartTargetDescriptions(IViewDiagramElementFinder cache) { + var targets = new ArrayList(); + cache.getNodeDescription(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage())).ifPresent(targets::add); + return targets; + } +} diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/StateTransitionCompartmentNodeDescriptionProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/StateTransitionCompartmentNodeDescriptionProvider.java index dbaa97ec8..cd073f7c2 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/StateTransitionCompartmentNodeDescriptionProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/StateTransitionCompartmentNodeDescriptionProvider.java @@ -33,6 +33,8 @@ import org.eclipse.sirius.components.view.diagram.UserResizableDirection; import org.eclipse.syson.diagram.common.view.services.description.ToolConstants; import org.eclipse.syson.diagram.common.view.tools.ExhibitStateNodeToolProvider; +import org.eclipse.syson.diagram.common.view.tools.DoneStateNodeToolProvider; +import org.eclipse.syson.diagram.common.view.tools.StartStateNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.StateTransitionCompartmentNodeToolProvider; import org.eclipse.syson.sysml.SysmlPackage; import org.eclipse.syson.util.AQLConstants; @@ -77,6 +79,8 @@ public void link(DiagramDescription diagramDescription, IViewDiagramElementFinde cache.getNodeDescription(this.name).ifPresent(nodeDescription -> { cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(SysmlPackage.eINSTANCE.getStateUsage())).ifPresent(nodeDescription.getReusedChildNodeDescriptions()::add); cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(SysmlPackage.eINSTANCE.getStateDefinition())).ifPresent(nodeDescription.getReusedChildNodeDescriptions()::add); + cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(StartStateNodeDescriptionProvider.START_STATE_NAME)).ifPresent(nodeDescription.getReusedChildNodeDescriptions()::add); + cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME)).ifPresent(nodeDescription.getReusedChildNodeDescriptions()::add); nodeDescription.setPalette(this.createCompartmentPalette(cache)); }); } @@ -118,6 +122,8 @@ protected NodePalette createCompartmentPalette(IViewDiagramElementFinder cache) var toolSections = this.toolDescriptionService.createDefaultNodeToolSections(); // Do not use getItemCreationToolProvider because the compartment contains multiple creation tools. + this.toolDescriptionService.addNodeTool(toolSections, ToolConstants.BEHAVIOR, new StartStateNodeToolProvider(this.eClass, this.getDescriptionNameGenerator()).create(cache)); + this.toolDescriptionService.addNodeTool(toolSections, ToolConstants.BEHAVIOR, new DoneStateNodeToolProvider(this.eClass, this.getDescriptionNameGenerator()).create(cache)); this.toolDescriptionService.addNodeTool(toolSections, ToolConstants.BEHAVIOR, new StateTransitionCompartmentNodeToolProvider(false).create(cache)); this.toolDescriptionService.addNodeTool(toolSections, ToolConstants.BEHAVIOR, new StateTransitionCompartmentNodeToolProvider(true).create(cache)); this.toolDescriptionService.addNodeTool(toolSections, ToolConstants.BEHAVIOR, new ExhibitStateNodeToolProvider(false).create(cache)); 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 6ba03ad18..415a43bcb 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 @@ -784,6 +784,17 @@ public ActionUsage addStartAction(Element ownerElement) { return this.utilService.retrieveStandardStartAction(ownerElement); } + /** + * Add the standard start state as the child of the given element. + * + * @param ownerElement + * an element that will own the standard start state. + * @return the {@link Membership} element containing the start state in its memberElement feature. + */ + public ActionUsage addStartState(Element ownerElement) { + return this.utilService.retrieveStandardStartState(ownerElement); + } + /** * Add the standard done action as the child of the given element. * @@ -795,6 +806,17 @@ public ActionUsage addDoneAction(Element ownerElement) { return this.utilService.retrieveStandardDoneAction(ownerElement); } + /** + * Add the standard done state as the child of the given element. + * + * @param ownerElement + * an element that will own the standard done state. + * @return the {@link Membership} element containing the done state in its memberElement feature. + */ + public ActionUsage addDoneState(Element ownerElement) { + return this.utilService.retrieveStandardDoneState(ownerElement); + } + /** * Create a new action {@link ActionUsage} inside the given element which should be an {@link ActionUsage} or an {@link ActionDefinition}. * @@ -1131,48 +1153,46 @@ public PerformActionUsage createStateSubaction(Element self, ActionUsage perform * @return the given source {@link Feature}. */ public Feature createTransitionUsage(Feature sourceUsage, Feature targetUsage, Node source, Node target, IDiagramService diagramService, IEditingContext editingContext) { - if (this.isInSameGraphicalContainer(source, target, diagramService)) { - // Check source and target have the same parent - Element semanticContainer = this.getEdgeSemanticContainer(source, target, diagramService.getDiagramContext().diagram(), editingContext); - if (semanticContainer != null) { - Element sourceParentElement = sourceUsage.getOwner(); - if (this.utilService.isParallelState(sourceParentElement)) { - // Should probably not be here as the transition creation should not be allowed. - return sourceUsage; - } - // Create transition usage and add it to the parent element - // sourceParentElement <>-> FeatureMembership -> RelatedElement = TransitionUsage - TransitionUsage newTransitionUsage = SysmlFactory.eINSTANCE.createTransitionUsage(); - var featureMembership = SysmlFactory.eINSTANCE.createFeatureMembership(); - featureMembership.getOwnedRelatedElement().add(newTransitionUsage); - sourceParentElement.getOwnedRelationship().add(featureMembership); - - // Create EndFeature - // TransitionUsage <>-> Membership -> MemberElement = sourceAction - var sourceMembership = SysmlFactory.eINSTANCE.createMembership(); - newTransitionUsage.getOwnedRelationship().add(sourceMembership); - sourceMembership.setMemberElement(sourceUsage); - - // Create Succession - // TransitionUsage <>-> FeatureMembership -> RelatedElement = succession - Succession succession = SysmlFactory.eINSTANCE.createSuccession(); - this.elementInitializerSwitch.doSwitch(succession); - var successionFeatureMembership = SysmlFactory.eINSTANCE.createFeatureMembership(); - successionFeatureMembership.getOwnedRelatedElement().add(succession); - newTransitionUsage.getOwnedRelationship().add(successionFeatureMembership); - - // Set Succession Source and Target Features - succession.getOwnedRelationship().add(this.createConnectorEndFeatureMembership(sourceUsage)); - succession.getOwnedRelationship().add(this.createConnectorEndFeatureMembership(targetUsage)); - this.elementInitializerSwitch.doSwitch(newTransitionUsage); - } else { - this.feedbackMessageService.addFeedbackMessage(new Message("Unable to find a suitable semantic owner for the new transition", MessageLevel.WARNING)); - } - - } else { + if (!this.isInSameGraphicalContainer(source, target, diagramService)) { + // The current implementation only rely on the semantic features "sourceFeature" and "targetFeature" to find source and target + // In order to avoid duplicated edges in case the source/target is displayed more than once we forbid the display of cross container edge this.feedbackMessageService.addFeedbackMessage(new Message("Can't create cross container TransitionUsage", MessageLevel.WARNING)); + return sourceUsage; } + EObject transitionOwner = this.getSourceOwner(source, editingContext, diagramService); + return this.createTransitionUsage(sourceUsage, targetUsage, transitionOwner); + } + private Feature createTransitionUsage(Feature sourceUsage, Feature targetUsage , EObject transitionOwner) { + if (transitionOwner instanceof Element ownerElement) { + // Create transition usage and add it to the parent element + // sourceParentElement <>-> FeatureMembership -> RelatedElement = TransitionUsage + TransitionUsage newTransitionUsage = SysmlFactory.eINSTANCE.createTransitionUsage(); + var featureMembership = SysmlFactory.eINSTANCE.createFeatureMembership(); + featureMembership.getOwnedRelatedElement().add(newTransitionUsage); + ownerElement.getOwnedRelationship().add(featureMembership); + + // Create EndFeature + // TransitionUsage <>-> Membership -> MemberElement = sourceAction + var sourceMembership = SysmlFactory.eINSTANCE.createMembership(); + newTransitionUsage.getOwnedRelationship().add(sourceMembership); + sourceMembership.setMemberElement(sourceUsage); + + // Create Succession + // TransitionUsage <>-> FeatureMembership -> RelatedElement = succession + Succession succession = SysmlFactory.eINSTANCE.createSuccession(); + this.elementInitializerSwitch.doSwitch(succession); + var successionFeatureMembership = SysmlFactory.eINSTANCE.createFeatureMembership(); + successionFeatureMembership.getOwnedRelatedElement().add(succession); + newTransitionUsage.getOwnedRelationship().add(successionFeatureMembership); + + // Set Succession Source and Target Features + succession.getOwnedRelationship().add(this.createConnectorEndFeatureMembership(sourceUsage)); + succession.getOwnedRelationship().add(this.createConnectorEndFeatureMembership(targetUsage)); + this.elementInitializerSwitch.doSwitch(newTransitionUsage); + } else { + this.feedbackMessageService.addFeedbackMessage(new Message("Unable to find a suitable semantic owner for the new transition", MessageLevel.WARNING)); + } return sourceUsage; } diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewEdgeToolSwitch.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewEdgeToolSwitch.java index edbca1802..2cd07e9cf 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewEdgeToolSwitch.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewEdgeToolSwitch.java @@ -25,10 +25,12 @@ import org.eclipse.syson.diagram.common.view.DescriptionFinder; import org.eclipse.syson.diagram.common.view.nodes.DecisionActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.DoneActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.DoneStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.ForkActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.JoinActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.MergeActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StartActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.StartStateNodeDescriptionProvider; import org.eclipse.syson.sysml.AcceptActionUsage; import org.eclipse.syson.sysml.ActionUsage; import org.eclipse.syson.sysml.AllocationUsage; @@ -289,12 +291,20 @@ public List caseStateDefinition(StateDefinition object) { @Override public List caseStateUsage(StateUsage object) { var edgeTools = new ArrayList(); - var targetNodes = this.allNodeDescriptions.stream().filter(nodeDesc -> this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getStateUsage()).equals(nodeDesc.getName())).collect(Collectors.toList()); + var targetNodes = this.allNodeDescriptions.stream() + .filter(this::isTransitionEdgeTargetNodeDescription) + .collect(Collectors.toList()); edgeTools.add(this.edgeToolService.createTransitionUsageEdgeTool(SysmlPackage.eINSTANCE.getTransitionUsage(), targetNodes)); edgeTools.addAll(this.caseUsage(object)); return edgeTools; } + private boolean isTransitionEdgeTargetNodeDescription(NodeDescription nodeDesc) { + boolean result = this.edgeToolService.isTheNodeDescriptionFor(nodeDesc, SysmlPackage.eINSTANCE.getStateUsage()); + result = result || this.descriptionNameGenerator.getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME).equals(nodeDesc.getName()); + return result; + } + @Override public List caseUsage(Usage object) { var edgeTools = new ArrayList(); @@ -338,6 +348,8 @@ private boolean isRegularNodeDescription(NodeDescription nodeDesc) { isSpecial = isSpecial || this.descriptionNameGenerator.getNodeName(ForkActionNodeDescriptionProvider.FORK_ACTION_NAME).equals(nodeDesc.getName()); isSpecial = isSpecial || this.descriptionNameGenerator.getNodeName(MergeActionNodeDescriptionProvider.MERGE_ACTION_NAME).equals(nodeDesc.getName()); isSpecial = isSpecial || this.descriptionNameGenerator.getNodeName(DecisionActionNodeDescriptionProvider.DECISION_ACTION_NAME).equals(nodeDesc.getName()); + isSpecial = isSpecial || this.descriptionNameGenerator.getNodeName(StartStateNodeDescriptionProvider.START_STATE_NAME).equals(nodeDesc.getName()); + isSpecial = isSpecial || this.descriptionNameGenerator.getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME).equals(nodeDesc.getName()); return !isSpecial; } diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/DoneStateNodeToolProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/DoneStateNodeToolProvider.java new file mode 100644 index 000000000..e18f75690 --- /dev/null +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/DoneStateNodeToolProvider.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2026 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.diagram.common.view.tools; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.syson.diagram.common.view.nodes.DoneStateNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.StateTransitionCompartmentNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.services.ViewCreateService; +import org.eclipse.syson.util.IDescriptionNameGenerator; +import org.eclipse.syson.util.ServiceMethod; + +/** + * Used to add the standard done state in states body for all diagrams. + * + * @author Jerome Gout + */ +public class DoneStateNodeToolProvider extends AbstractFreeFormCompartmentNodeToolProvider { + + public DoneStateNodeToolProvider(EClass ownerEClass, IDescriptionNameGenerator descriptionNameGenerator) { + super(ownerEClass, StateTransitionCompartmentNodeDescriptionProvider.STATE_COMPARTMENT_NAME, descriptionNameGenerator); + } + + @Override + protected String getNodeDescriptionName() { + return this.getDescriptionNameGenerator().getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME); + } + + @Override + protected String getCreationServiceCallExpression() { + return ServiceMethod.of0(ViewCreateService::addDoneState).aqlSelf(); + } + + @Override + protected String getLabel() { + return "New Done State"; + } + + @Override + protected String getIconPath() { + return "/icons/done_action.svg"; + } +} diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/StartStateNodeToolProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/StartStateNodeToolProvider.java new file mode 100644 index 000000000..905672351 --- /dev/null +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/StartStateNodeToolProvider.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2026 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.diagram.common.view.tools; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.syson.diagram.common.view.nodes.StartStateNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.StateTransitionCompartmentNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.services.ViewCreateService; +import org.eclipse.syson.util.IDescriptionNameGenerator; +import org.eclipse.syson.util.ServiceMethod; + +/** + * Used to add the standard start state in states body for all diagrams. + * + * @author Jerome Gout + */ +public class StartStateNodeToolProvider extends AbstractFreeFormCompartmentNodeToolProvider { + + public StartStateNodeToolProvider(EClass ownerEClass, IDescriptionNameGenerator descriptionNameGenerator) { + super(ownerEClass, StateTransitionCompartmentNodeDescriptionProvider.STATE_COMPARTMENT_NAME, descriptionNameGenerator); + } + + @Override + protected String getNodeDescriptionName() { + return this.getDescriptionNameGenerator().getNodeName(StartStateNodeDescriptionProvider.START_STATE_NAME); + } + + @Override + protected String getCreationServiceCallExpression() { + return ServiceMethod.of0(ViewCreateService::addStartState).aqlSelf(); + } + + @Override + protected String getLabel() { + return "New Start State"; + } + + @Override + protected String getIconPath() { + return "/icons/start_action.svg"; + } +} diff --git a/backend/views/syson-diagram-tests/src/main/java/org/eclipse/syson/diagram/tests/predicates/DiagramPredicates.java b/backend/views/syson-diagram-tests/src/main/java/org/eclipse/syson/diagram/tests/predicates/DiagramPredicates.java index 3f93fb293..96165f6c7 100644 --- a/backend/views/syson-diagram-tests/src/main/java/org/eclipse/syson/diagram/tests/predicates/DiagramPredicates.java +++ b/backend/views/syson-diagram-tests/src/main/java/org/eclipse/syson/diagram/tests/predicates/DiagramPredicates.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2025 Obeo. + * Copyright (c) 2024, 2026 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 @@ -20,7 +20,9 @@ import org.eclipse.sirius.components.view.diagram.DiagramElementDescription; import org.eclipse.sirius.components.view.diagram.NodeDescription; import org.eclipse.syson.diagram.common.view.nodes.DoneActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.DoneStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StartActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.StartStateNodeDescriptionProvider; import org.eclipse.syson.sysml.SysmlPackage; import org.eclipse.syson.util.IDescriptionNameGenerator; import org.eclipse.syson.util.SysMLMetamodelHelper; @@ -57,7 +59,9 @@ public DiagramPredicates(String diagramPrefix, IDescriptionNameGenerator descrip this.isInheritedBorderNode = n -> n.getName().contains(inheritedBorderNodeNameFragment); this.isNamespaceImportNode = n -> n.getName().equals(diagramPrefix + " Node NamespaceImport"); this.isStartOrDoneNode = n -> n.getName().equals(descriptionNameGenerator.getNodeName(StartActionNodeDescriptionProvider.START_ACTION_NAME)) - || n.getName().equals(descriptionNameGenerator.getNodeName(DoneActionNodeDescriptionProvider.DONE_ACTION_NAME)); + || n.getName().equals(descriptionNameGenerator.getNodeName(DoneActionNodeDescriptionProvider.DONE_ACTION_NAME)) + || n.getName().equals(descriptionNameGenerator.getNodeName(StartStateNodeDescriptionProvider.START_STATE_NAME)) + || n.getName().equals(descriptionNameGenerator.getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME)); } public Predicate isFakeNode() { diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java index 56f30f8a5..5e45f85de 100644 --- a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java @@ -50,6 +50,7 @@ import org.eclipse.syson.diagram.common.view.nodes.CompartmentItemNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.DecisionActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.DoneActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.DoneStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.ForkActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.ImportedPackageNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.InheritedCompartmentItemNodeDescriptionProvider; @@ -59,6 +60,7 @@ import org.eclipse.syson.diagram.common.view.nodes.SatisfyRequirementCompartmentItemNodeDescription; import org.eclipse.syson.diagram.common.view.nodes.SatisfyRequirementCompartmentNodeDescription; import org.eclipse.syson.diagram.common.view.nodes.StartActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.StartStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StateTransitionCompartmentNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StatesCompartmentItemNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StatesCompartmentNodeDescriptionProvider; @@ -861,6 +863,8 @@ private List> createAllCustomNodeDescripti customNodeDescriptionProviders.add(new StakeholderNodeDescriptionProvider(colorProvider)); customNodeDescriptionProviders.add(new SubjectNodeDescriptionProvider(colorProvider)); customNodeDescriptionProviders.add(new ImportedPackageNodeDescriptionProvider(colorProvider, this.getDescriptionNameGenerator())); + customNodeDescriptionProviders.add(new StartStateNodeDescriptionProvider(colorProvider, this.getDescriptionNameGenerator())); + customNodeDescriptionProviders.add(new DoneStateNodeDescriptionProvider(colorProvider, this.getDescriptionNameGenerator())); return customNodeDescriptionProviders; } diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/DefinitionNodeDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/DefinitionNodeDescriptionProvider.java index eade01314..3cceba6bb 100644 --- a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/DefinitionNodeDescriptionProvider.java +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/DefinitionNodeDescriptionProvider.java @@ -30,11 +30,13 @@ import org.eclipse.syson.diagram.common.view.nodes.AbstractDefinitionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.DecisionActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.DoneActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.DoneStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.ForkActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.JoinActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.MergeActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StartActionNodeDescriptionProvider; import org.eclipse.syson.diagram.services.aql.DiagramQueryAQLService; +import org.eclipse.syson.diagram.common.view.nodes.StartStateNodeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator; import org.eclipse.syson.standard.diagrams.view.SDVDiagramDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.services.SDVNodeToolSectionSwitch; @@ -123,6 +125,8 @@ protected List getAllNodeDescriptions(IViewDiagramElementFinder cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(ForkActionNodeDescriptionProvider.FORK_ACTION_NAME)).ifPresent(allNodes::add); cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(MergeActionNodeDescriptionProvider.MERGE_ACTION_NAME)).ifPresent(allNodes::add); cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(DecisionActionNodeDescriptionProvider.DECISION_ACTION_NAME)).ifPresent(allNodes::add); + cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(StartStateNodeDescriptionProvider.START_STATE_NAME)).ifPresent(allNodes::add); + cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME)).ifPresent(allNodes::add); return allNodes; } diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java index 2e60eff45..12bee5bff 100644 --- a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java @@ -30,11 +30,13 @@ import org.eclipse.syson.diagram.common.view.nodes.AbstractUsageNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.DecisionActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.DoneActionNodeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.nodes.DoneStateNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.ForkActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.JoinActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.MergeActionNodeDescriptionProvider; import org.eclipse.syson.diagram.common.view.nodes.StartActionNodeDescriptionProvider; import org.eclipse.syson.diagram.services.aql.DiagramQueryAQLService; +import org.eclipse.syson.diagram.common.view.nodes.StartStateNodeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator; import org.eclipse.syson.standard.diagrams.view.SDVDiagramDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.services.SDVNodeToolSectionSwitch; @@ -135,6 +137,8 @@ protected List getAllNodeDescriptions(IViewDiagramElementFinder cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(ForkActionNodeDescriptionProvider.FORK_ACTION_NAME)).ifPresent(allNodes::add); cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(MergeActionNodeDescriptionProvider.MERGE_ACTION_NAME)).ifPresent(allNodes::add); cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(DecisionActionNodeDescriptionProvider.DECISION_ACTION_NAME)).ifPresent(allNodes::add); + cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(StartStateNodeDescriptionProvider.START_STATE_NAME)).ifPresent(allNodes::add); + cache.getNodeDescription(this.getDescriptionNameGenerator().getNodeName(DoneStateNodeDescriptionProvider.DONE_STATE_NAME)).ifPresent(allNodes::add); return allNodes; } diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/services/SDVNodeToolSectionSwitch.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/services/SDVNodeToolSectionSwitch.java index 1b4e286da..4d13515ec 100644 --- a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/services/SDVNodeToolSectionSwitch.java +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/services/SDVNodeToolSectionSwitch.java @@ -34,6 +34,7 @@ import org.eclipse.syson.diagram.common.view.tools.ConnectionDefinitionEndCompartmentNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.DecisionActionNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.DoneActionNodeToolProvider; +import org.eclipse.syson.diagram.common.view.tools.DoneStateNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.ExhibitStateNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.ForkActionNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.InterfaceDefinitionEndCompartmentNodeToolProvider; @@ -56,6 +57,7 @@ import org.eclipse.syson.diagram.common.view.tools.SetAsViewToolProvider; import org.eclipse.syson.diagram.common.view.tools.StakeholdersCompartmentNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.StartActionNodeToolProvider; +import org.eclipse.syson.diagram.common.view.tools.StartStateNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.StateSubactionNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.StateTransitionCompartmentNodeToolProvider; import org.eclipse.syson.diagram.common.view.tools.SubjectCompartmentNodeToolProvider; @@ -734,6 +736,8 @@ public List caseRequirementDefinition(RequirementDefinition obj @Override public List caseStateDefinition(StateDefinition object) { var sections = this.toolDescriptionService.createDefaultNodeToolSections(); + this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new StartStateNodeToolProvider(object.eClass(), this.descriptionNameGenerator).create(this.cache)); + this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new DoneStateNodeToolProvider(object.eClass(), this.descriptionNameGenerator).create(this.cache)); this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new StateTransitionCompartmentNodeToolProvider(false).create(this.cache)); this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new StateTransitionCompartmentNodeToolProvider(true).create(this.cache)); this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new ExhibitStateNodeToolProvider(false).create(this.cache)); @@ -757,6 +761,8 @@ public List caseStateDefinition(StateDefinition object) { @Override public List caseStateUsage(StateUsage object) { var sections = this.toolDescriptionService.createDefaultNodeToolSections(); + this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new StartStateNodeToolProvider(object.eClass(), this.descriptionNameGenerator).create(this.cache)); + this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new DoneStateNodeToolProvider(object.eClass(), this.descriptionNameGenerator).create(this.cache)); this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new StateTransitionCompartmentNodeToolProvider(false).create(this.cache)); this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new StateTransitionCompartmentNodeToolProvider(true).create(this.cache)); this.toolDescriptionService.addNodeTool(sections, ToolConstants.BEHAVIOR, new ExhibitStateNodeToolProvider(false).create(this.cache)); diff --git a/doc/content/modules/user-manual/assets/images/release-notes-start-done-states.png b/doc/content/modules/user-manual/assets/images/release-notes-start-done-states.png new file mode 100644 index 000000000..77a378c5f Binary files /dev/null and b/doc/content/modules/user-manual/assets/images/release-notes-start-done-states.png differ diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc index 7ce8230b9..a4a1c1398 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc @@ -15,6 +15,10 @@ image::release-notes-frame-edge-tool.png[Edge tool to frame a ConcernUsage, widt + image::release-notes-frame-edge.png[Frame edge between a RequirementUsage and a ConcernUsage, width=60%,height=60%] +** Add tools to create _Start_ and _Done_ on `StateUsage` and `StateDefinition` graphical nodes. ++ +image::release-notes-start-done-states.png[Tool to create a start and done, width=60%,height=60%] + * In the _Explorer_ view: ** The tree items corresponding to the internals of `Expression` elements (syntax tree) are now hidden by default.