Skip to content

Commit 772cb4b

Browse files
committed
[2198] Handle Drag&Drop with multi selection
Bug: #2198 Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
1 parent 0b24d72 commit 772cb4b

18 files changed

Lines changed: 730 additions & 131 deletions

File tree

CHANGELOG.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
=== Improvements
1616

17+
- https://github.com/eclipse-syson/syson/issues/2198[#2198] [diagrams] Improve diagram-to-diagram drag and drop to support dropping multiple graphical nodes at once, leveraging Sirius Web's `droppedNodes` and `droppedElements` variables.
18+
1719
=== New features
1820

1921

backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDropFromDiagramTests.java

Lines changed: 352 additions & 2 deletions
Large diffs are not rendered by default.

backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationDndService.java

Lines changed: 258 additions & 59 deletions
Large diffs are not rendered by default.

backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/aql/DiagramMutationAQLService.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -164,37 +164,40 @@ public Element directEditNode(Element element, String newLabel) {
164164
}
165165

166166
/**
167-
* {@link DiagramMutationDndService#dropElementFromDiagram(Element, Node, Element, Node, IEditingContext, DiagramContext, Map)}.
167+
* {@link DiagramMutationDndService#dropElementFromDiagram(List, List, Element, Node, IEditingContext, DiagramContext, Map)}.
168168
*/
169-
public Element dropElementFromDiagram(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext, DiagramContext diagramContext,
169+
public Element dropElementFromDiagram(List<Element> droppedElements, List<Node> droppedNodes, Element targetElement, Node targetNode, IEditingContext editingContext, DiagramContext diagramContext,
170170
Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
171-
return this.diagramMutationDndService.dropElementFromDiagram(droppedElement, droppedNode, targetElement, targetNode, editingContext, diagramContext, convertedNodes);
171+
return this.diagramMutationDndService.dropElementFromDiagram(droppedElements, droppedNodes, targetElement, targetNode, editingContext, diagramContext, convertedNodes);
172172
}
173173

174174
/**
175-
* {@link DiagramMutationDndService#dropElementFromDiagramInConstraintCompartment(Element, Node, Element, Node, IEditingContext, DiagramContext, Map)}.
175+
* {@link DiagramMutationDndService#dropElementFromDiagramInConstraintCompartment(List, List, Element, Node, IEditingContext, DiagramContext, Map)}.
176176
*/
177-
public Element dropElementFromDiagramInConstraintCompartment(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext,
177+
public Element dropElementFromDiagramInConstraintCompartment(List<Element> droppedElements, List<Node> droppedNodes, Element targetElement, Node targetNode, IEditingContext editingContext,
178178
DiagramContext diagramContext, Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
179-
return this.diagramMutationDndService.dropElementFromDiagramInConstraintCompartment(droppedElement, droppedNode, targetElement, targetNode, editingContext, diagramContext,
179+
return this.diagramMutationDndService.dropElementFromDiagramInConstraintCompartment(droppedElements, droppedNodes, targetElement, targetNode, editingContext, diagramContext,
180180
convertedNodes);
181181
}
182182

183183
/**
184-
* {@link DiagramMutationDndService#dropElementFromDiagramInRequirementAssumeConstraintCompartment(Element, Node, Element, Node, IEditingContext, DiagramContext, Map)}.
184+
* {@link DiagramMutationDndService#dropElementFromDiagramInRequirementAssumeConstraintCompartment(List, List, Element, Node, IEditingContext, DiagramContext, Map)}.
185185
*/
186-
public Element dropElementFromDiagramInRequirementAssumeConstraintCompartment(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext,
186+
public Element dropElementFromDiagramInRequirementAssumeConstraintCompartment(List<Element> droppedElements, List<Node> droppedNodes, Element targetElement, Node targetNode, IEditingContext editingContext,
187187
DiagramContext diagramContext, Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
188-
return this.diagramMutationDndService.dropElementFromDiagramInRequirementAssumeConstraintCompartment(droppedElement, droppedNode, targetElement, targetNode, editingContext, diagramContext,
188+
return this.diagramMutationDndService.dropElementFromDiagramInRequirementAssumeConstraintCompartment(droppedElements, droppedNodes, targetElement, targetNode, editingContext,
189+
diagramContext,
189190
convertedNodes);
190191
}
191192

192193
/**
193-
* {@link DiagramMutationDndService#dropElementFromDiagramInRequirementRequireConstraintCompartment(Element, Node, Element, Node, IEditingContext, DiagramContext, Map)}.
194+
* {@link DiagramMutationDndService#dropElementFromDiagramInRequirementRequireConstraintCompartment(List, List, Element, Node, IEditingContext, DiagramContext, Map)}.
194195
*/
195-
public Element dropElementFromDiagramInRequirementRequireConstraintCompartment(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext,
196+
public Element dropElementFromDiagramInRequirementRequireConstraintCompartment(List<Element> droppedElements, List<Node> droppedNodes, Element targetElement, Node targetNode,
197+
IEditingContext editingContext,
196198
DiagramContext diagramContext, Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
197-
return this.diagramMutationDndService.dropElementFromDiagramInRequirementRequireConstraintCompartment(droppedElement, droppedNode, targetElement, targetNode, editingContext, diagramContext,
199+
return this.diagramMutationDndService.dropElementFromDiagramInRequirementRequireConstraintCompartment(droppedElements, droppedNodes, targetElement, targetNode, editingContext,
200+
diagramContext,
198201
convertedNodes);
199202
}
200203

@@ -207,19 +210,21 @@ public Element dropElementFromExplorer(Element element, IEditingContext editingC
207210
}
208211

209212
/**
210-
* {@link DiagramMutationDndService#dropObjectiveRequirementFromDiagram(Element, Node, Element, Node, IEditingContext, DiagramContext, Map)}.
213+
* {@link DiagramMutationDndService#dropObjectiveRequirementFromDiagram(List, List, Element, Node, IEditingContext, DiagramContext, Map)}.
211214
*/
212-
public Element dropObjectiveRequirementFromDiagram(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext, DiagramContext diagramContext,
215+
public Element dropObjectiveRequirementFromDiagram(List<Element> droppedElements, List<Node> droppedNodes, Element targetElement, Node targetNode, IEditingContext editingContext,
216+
DiagramContext diagramContext,
213217
Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
214-
return this.diagramMutationDndService.dropObjectiveRequirementFromDiagram(droppedElement, droppedNode, targetElement, targetNode, editingContext, diagramContext, convertedNodes);
218+
return this.diagramMutationDndService.dropObjectiveRequirementFromDiagram(droppedElements, droppedNodes, targetElement, targetNode, editingContext, diagramContext, convertedNodes);
215219
}
216220

217221
/**
218-
* {@link DiagramMutationDndService#dropSubjectFromDiagram(Element, Node, Element, Node, IEditingContext, DiagramContext, Map)}.
222+
* {@link DiagramMutationDndService#dropSubjectFromDiagram(List, List, Element, Node, IEditingContext, DiagramContext, Map)}.
219223
*/
220-
public Element dropSubjectFromDiagram(Element droppedElement, Node droppedNode, Element targetElement, Node targetNode, IEditingContext editingContext, DiagramContext diagramContext,
224+
public Element dropSubjectFromDiagram(List<Element> droppedElements, List<Node> droppedNodes, Element targetElement, Node targetNode, IEditingContext editingContext,
225+
DiagramContext diagramContext,
221226
Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
222-
return this.diagramMutationDndService.dropSubjectFromDiagram(droppedElement, droppedNode, targetElement, targetNode, editingContext, diagramContext, convertedNodes);
227+
return this.diagramMutationDndService.dropSubjectFromDiagram(droppedElements, droppedNodes, targetElement, targetNode, editingContext, diagramContext, convertedNodes);
223228
}
224229

225230
/**

backend/services/syson-services/src/main/java/org/eclipse/syson/util/ServiceMethod.java

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
* the number of AQL parameters after {@code self}.</li>
4040
* <li>The helper extracts the Java method name through the standard lambda serialization hook (reading a
4141
* {@link SerializedLambda}). No service is invoked.</li>
42-
* <li>You then call {@link #aqlSelf(String...)} or {@link #aql(String, String...)} to build the final AQL string,
43-
* delegating to {@code AQLUtils}.</li>
42+
* <li>You then call {@link #aqlSelf(String...)}, {@link #aqlSelfArrow(String...)}, {@link #aql(String, String...)}
43+
* or {@link #aqlArrow(String, String...)} to build the final AQL string.</li>
4444
* </ol>
4545
* <p>
4646
* Important: the arguments passed to {@code aqlSelf(...)} and {@code aql(var, ...)} are AQL snippets, not Java values.
@@ -51,8 +51,12 @@
5151
* <ul>
5252
* <li>{@link #aqlSelf(String...)} when the receiver is {@code self}, for expressions like
5353
* {@code aql:self.myService(...)}.</li>
54+
* <li>{@link #aqlSelfArrow(String...)} when the receiver is {@code self} and you need AQL's collection call syntax,
55+
* for expressions like {@code aql:self->myService(...)}.</li>
5456
* <li>{@link #aql(String, String...)} when you target another variable in the AQL context, for example
5557
* {@code aql:elt.myService(...)}.</li>
58+
* <li>{@link #aqlArrow(String, String...)} when you target another variable in the AQL context and you need AQL's
59+
* collection call syntax, for example {@code aql:elts->myService(...)}.</li>
5660
* </ul>
5761
* <p>
5862
* Type inference: if the compiler says something like <i>The type X does not define methodName(Object, Object,
@@ -127,10 +131,22 @@ public Method declaration() {
127131
*/
128132
public String aqlSelf(String... params) {
129133
this.checkArity(params);
130-
if (params == null || params.length == 0) {
131-
return AQLUtils.getSelfServiceCallExpression(this.name);
132-
}
133-
return AQLUtils.getSelfServiceCallExpression(this.name, Arrays.asList(params));
134+
return this.aqlCall(AQLConstants.SELF, ".", params);
135+
}
136+
137+
/**
138+
* Build {@code aql:self->method(...)} for the captured service name.
139+
* <p>
140+
* Use when the receiver is {@code self} and you want AQL to keep the receiver as a collection instead of applying
141+
* the call item by item.
142+
*
143+
* @param params
144+
* AQL parameter snippets, for example {@code "'declaredName'"} or {@code someVar}
145+
* @return A full AQL expression string
146+
*/
147+
public String aqlSelfArrow(String... params) {
148+
this.checkArity(params);
149+
return this.aqlCall(AQLConstants.SELF, "->", params);
134150
}
135151

136152
/**
@@ -146,17 +162,36 @@ public String aqlSelf(String... params) {
146162
* @return A full AQL expression string
147163
*/
148164
public String aql(String var, String... params) {
149-
String aqlString = null;
165+
this.checkArity(params);
166+
return this.aqlCall(var, ".", params);
167+
}
168+
169+
/**
170+
* Build {@code aql:var->method(...)} for the captured service name.
171+
* <p>
172+
* Use when you target a collection variable in the AQL scope and want to pass it as the receiver, for example
173+
* {@code aql:elts->myService(...)}.
174+
*
175+
* @param var
176+
* the AQL variable name to call the service on, for example {@code "elts"}
177+
* @param params
178+
* AQL parameter snippets
179+
* @return A full AQL expression string
180+
*/
181+
public String aqlArrow(String var, String... params) {
182+
this.checkArity(params);
183+
return this.aqlCall(var, "->", params);
184+
}
185+
186+
private String aqlCall(String var, String operator, String... params) {
150187
if (var == null || var.isEmpty()) {
151188
throw new IllegalArgumentException("var must be a non empty AQL variable name");
152-
} else {
153-
this.checkArity(params);
154-
if (params == null || params.length == 0) {
155-
aqlString = AQLUtils.getServiceCallExpression(var, this.name);
156-
}
157-
aqlString = AQLUtils.getServiceCallExpression(var, this.name, Arrays.asList(params));
158189
}
159-
return aqlString;
190+
String joinedParams = "";
191+
if (params != null && params.length > 0) {
192+
joinedParams = String.join(", ", Arrays.asList(params));
193+
}
194+
return MessageFormat.format("aql:{0}{1}{2}({3})", var, operator, this.name, joinedParams);
160195
}
161196

162197
// ---------------------- Factories for unbound instance methods ----------------------

backend/services/syson-services/src/test/java/org/eclipse/syson/util/ServiceMethodTest.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ public void givenInstanceServiceFactoriesWhenEachArityIsUsedThenTheExpectedExpre
5353
assertThat(ServiceMethod.of0(ArityFixture::instance0).declaration())
5454
.isEqualTo(ArityFixture.class.getDeclaredMethod("instance0", Object.class));
5555
assertThat(ServiceMethod.of1(ArityFixture::instance1).aql("ctx", AQL_VALUE)).isEqualTo("aql:ctx.instance1('value')");
56-
assertThat(ServiceMethod.of2(ArityFixture::instance2).aqlSelf(AQL_VALUE, AQL_INT)).isEqualTo("aql:self.instance2('value',2)");
57-
assertThat(ServiceMethod.of3(ArityFixture::instance3).aqlSelf(AQL_VALUE, AQL_INT, "true")).isEqualTo("aql:self.instance3('value',2,true)");
56+
assertThat(ServiceMethod.of2(ArityFixture::instance2).aqlSelf(AQL_VALUE, AQL_INT)).isEqualTo("aql:self.instance2('value', 2)");
57+
assertThat(ServiceMethod.of3(ArityFixture::instance3).aqlSelf(AQL_VALUE, AQL_INT, "true")).isEqualTo("aql:self.instance3('value', 2, true)");
5858
assertThat(ServiceMethod.of4(ArityFixture::instance4).aqlSelf(AQL_VALUE, AQL_INT, "true", "4.0"))
59-
.isEqualTo("aql:self.instance4('value',2,true,4.0)");
59+
.isEqualTo("aql:self.instance4('value', 2, true, 4.0)");
6060
assertThat(ServiceMethod.of5(ArityFixture::instance5).aqlSelf(AQL_VALUE, AQL_INT, "true", "4.0", "5L"))
61-
.isEqualTo("aql:self.instance5('value',2,true,4.0,5L)");
61+
.isEqualTo("aql:self.instance5('value', 2, true, 4.0, 5L)");
6262
assertThat(ServiceMethod.of6(ArityFixture::instance6).aqlSelf(AQL_VALUE, AQL_INT, "true", "4.0", "5L", "6.0f"))
63-
.isEqualTo("aql:self.instance6('value',2,true,4.0,5L,6.0f)");
63+
.isEqualTo("aql:self.instance6('value', 2, true, 4.0, 5L, 6.0f)");
6464
}
6565

6666
@Test
@@ -110,14 +110,23 @@ public void givenStaticServiceFactoriesWhenEachArityIsUsedThenTheExpectedExpress
110110
assertThat(ServiceMethod.ofStatic1(StaticFixture::static1).aqlSelf(AQL_VALUE)).isEqualTo("aql:self.static1('value')");
111111
assertThat(ServiceMethod.ofStatic1(Object.class, String.class, StaticFixture::static1).declaration())
112112
.isEqualTo(StaticFixture.class.getDeclaredMethod("static1", Object.class, String.class));
113-
assertThat(ServiceMethod.ofStatic2(StaticFixture::static2).aql("ctx", AQL_VALUE, AQL_INT)).isEqualTo("aql:ctx.static2('value',2)");
113+
assertThat(ServiceMethod.ofStatic2(StaticFixture::static2).aql("ctx", AQL_VALUE, AQL_INT)).isEqualTo("aql:ctx.static2('value', 2)");
114114
assertThat(ServiceMethod.ofStatic2(Object.class, String.class, Integer.class, StaticFixture::static2).declaration())
115115
.isEqualTo(StaticFixture.class.getDeclaredMethod("static2", Object.class, String.class, Integer.class));
116-
assertThat(ServiceMethod.ofStatic3(StaticFixture::static3).aqlSelf(AQL_VALUE, AQL_INT, "true")).isEqualTo("aql:self.static3('value',2,true)");
116+
assertThat(ServiceMethod.ofStatic3(StaticFixture::static3).aqlSelf(AQL_VALUE, AQL_INT, "true")).isEqualTo("aql:self.static3('value', 2, true)");
117117
assertThat(ServiceMethod.ofStatic3(Object.class, String.class, Integer.class, Boolean.class, StaticFixture::static3).declaration())
118118
.isEqualTo(StaticFixture.class.getDeclaredMethod("static3", Object.class, String.class, Integer.class, Boolean.class));
119119
}
120120

121+
@Test
122+
@DisplayName("GIVEN a service method, WHEN arrow AQL expressions are constructed, THEN the expected expressions are returned")
123+
public void givenServiceMethodWhenArrowAqlExpressionsAreConstructedThenTheExpectedExpressionsAreReturned() {
124+
assertThat(ServiceMethod.of0(ArityFixture::instance0).aqlSelfArrow()).isEqualTo("aql:self->instance0()");
125+
assertThat(ServiceMethod.of2(ArityFixture::instance2).aqlSelfArrow(AQL_VALUE, AQL_INT)).isEqualTo("aql:self->instance2('value', 2)");
126+
assertThat(ServiceMethod.of2(ArityFixture::instance2).aqlArrow("droppedElements", "droppedNodes", "targetElement"))
127+
.isEqualTo("aql:droppedElements->instance2(droppedNodes, targetElement)");
128+
}
129+
121130
@Test
122131
@DisplayName("GIVEN a service method with another arity, WHEN the wrong number of parameters is provided, THEN an exception is thrown")
123132
public void givenServiceMethodWithAnotherArityWhenWrongNumberOfParametersIsProvidedThenExceptionIsThrown() {

backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractCompartmentNodeDescriptionProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ protected String isHiddenByDefaultExpression() {
156156
* @return
157157
*/
158158
protected String getDropElementFromDiagramExpression() {
159-
return ServiceMethod.of6(DiagramMutationAQLService::dropElementFromDiagram).aql("droppedElement", "droppedNode", "targetElement", "targetNode", IEditingContext.EDITING_CONTEXT,
160-
DiagramContext.DIAGRAM_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE);
159+
return ServiceMethod.of6(DiagramMutationAQLService::dropElementFromDiagram).aqlArrow("droppedElements", "droppedNodes", "targetElement", "targetNode",
160+
IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE);
161161
}
162162

163163
/**

backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractDefinitionNodeDescriptionProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ protected DropNodeTool createDropFromDiagramTool(IViewDiagramElementFinder cache
284284
.name("Drop from Diagram")
285285
.acceptedNodeTypes(this.getDroppableNodes(cache).toArray(NodeDescription[]::new))
286286
.body(this.viewBuilderHelper.newChangeContext()
287-
.expression(ServiceMethod.of6(DiagramMutationAQLService::dropElementFromDiagram).aql("droppedElement", "droppedNode", "targetElement", "targetNode", IEditingContext.EDITING_CONTEXT,
288-
DiagramContext.DIAGRAM_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))
287+
.expression(ServiceMethod.of6(DiagramMutationAQLService::dropElementFromDiagram).aqlArrow("droppedElements", "droppedNodes", "targetElement", "targetNode",
288+
IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))
289289
.build())
290290
.build();
291291
}

backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractPackageNodeDescriptionProvider.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,8 @@ private DropNodeTool createDropFromDiagramTool(IViewDiagramElementFinder cache)
237237
.name("Drop from Diagram")
238238
.acceptedNodeTypes(this.getDroppableNodes(cache).toArray(NodeDescription[]::new))
239239
.body(this.viewBuilderHelper.newChangeContext()
240-
.expression(ServiceMethod.of6(DiagramMutationAQLService::dropElementFromDiagram).aql("droppedElement", "droppedNode", "targetElement", "targetNode",
241-
IEditingContext.EDITING_CONTEXT,
242-
DiagramContext.DIAGRAM_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))
240+
.expression(ServiceMethod.of6(DiagramMutationAQLService::dropElementFromDiagram).aqlArrow("droppedElements", "droppedNodes", "targetElement", "targetNode",
241+
IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))
243242
.build())
244243
.build();
245244
}

0 commit comments

Comments
 (0)