diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/MANIFEST.MF b/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/MANIFEST.MF
index c61328d68..e2fc80c19 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/MANIFEST.MF
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/MANIFEST.MF
@@ -9,6 +9,7 @@ Export-Package: de.cau.cs.kieler.klighd.lsp,
de.cau.cs.kieler.klighd.lsp.gson_utils,
de.cau.cs.kieler.klighd.lsp.interactive,
de.cau.cs.kieler.klighd.lsp.interactive.layered,
+ de.cau.cs.kieler.klighd.lsp.interactive.mrtree,
de.cau.cs.kieler.klighd.lsp.interactive.rectpacking,
de.cau.cs.kieler.klighd.lsp.launch,
de.cau.cs.kieler.klighd.lsp.model,
@@ -25,6 +26,7 @@ Require-Bundle: de.cau.cs.kieler.kgraph.text,
com.google.inject;bundle-version="3.0.0",
org.apache.log4j;bundle-version="1.2.15",
org.eclipse.elk.alg.layered,
+ org.eclipse.elk.alg.mrtree,
org.eclipse.elk.alg.rectpacking,
org.eclipse.elk.core,
org.eclipse.elk.graph,
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/services/de.cau.cs.kieler.klighd.lsp.ISprottyActionHandler b/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/services/de.cau.cs.kieler.klighd.lsp.ISprottyActionHandler
new file mode 100644
index 000000000..6e03b9500
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/services/de.cau.cs.kieler.klighd.lsp.ISprottyActionHandler
@@ -0,0 +1,3 @@
+de.cau.cs.kieler.klighd.lsp.interactive.layered.LayeredInteractiveActionHandler
+de.cau.cs.kieler.klighd.lsp.interactive.rectpacking.RectpackingInteractiveActionHandler
+de.cau.cs.kieler.klighd.lsp.interactive.mrtree.MrTreeActionHandler
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/services/de.cau.cs.kieler.klighd.lsp.interactive.IConstraintSerializer b/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/services/de.cau.cs.kieler.klighd.lsp.interactive.IConstraintSerializer
new file mode 100644
index 000000000..67775785d
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/META-INF/services/de.cau.cs.kieler.klighd.lsp.interactive.IConstraintSerializer
@@ -0,0 +1 @@
+de.cau.cs.kieler.klighd.lsp.interactive.ElkGraphConstraintSerializer
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/pom.xml b/plugins/de.cau.cs.kieler.klighd.lsp/pom.xml
index 2c75eb316..442f4c4de 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/pom.xml
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/pom.xml
@@ -78,6 +78,11 @@
org.eclipse.elk.alg.layered${elk-version}
+
+ org.eclipse.elk
+ org.eclipse.elk.alg.mrtree
+ ${elk-version}
+ org.eclipse.elkorg.eclipse.elk.alg.rectpacking
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/AbstractActionHandler.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/AbstractActionHandler.xtend
index 0c3d93bb4..eb5a81bce 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/AbstractActionHandler.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/AbstractActionHandler.xtend
@@ -16,21 +16,25 @@
*/
package de.cau.cs.kieler.klighd.lsp
-import java.util.HashMap
-import org.eclipse.xtend.lib.annotations.Accessors
+import java.util.Map
+import org.eclipse.sprotty.Action
/**
* Abstract class to handle Sprotty actions.
- *
- * @author sdo
*/
-abstract class AbstractActionHandler implements IActionHandler {
+abstract class AbstractActionHandler implements ISprottyActionHandler {
- @Accessors(PUBLIC_GETTER, PROTECTED_SETTER)
- HashMap> supportedMessages = newHashMap
+ Map> supportedMessages
+
+ override Map> getSupportedMessages() {
+ return supportedMessages
+ }
+
+ def setSupportedMessages(Map> messages) {
+ this.supportedMessages = messages
+ }
override canHandleAction(String kind) {
- return supportedMessages.containsKey(kind)
-
- }
+ return supportedMessages.containsKey(kind)
+ }
}
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/IActionHandler.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/ISprottyActionHandler.xtend
similarity index 84%
rename from plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/IActionHandler.xtend
rename to plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/ISprottyActionHandler.xtend
index 56aa23794..875198c82 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/IActionHandler.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/ISprottyActionHandler.xtend
@@ -17,13 +17,16 @@
package de.cau.cs.kieler.klighd.lsp
import org.eclipse.sprotty.Action
+import java.util.Map
/**
- * Service Interface for ActionHandler.
- *
- * @author sdo
+ * Service interface for Sprotty action handlers.
*/
-interface IActionHandler {
+interface ISprottyActionHandler {
+
+
+ def Map> getSupportedMessages();
+
/**
* Checks and returns true if this ActionHandler can handle this action.
* @param kind String identifier of action
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend
index 819ad0f07..aac307a32 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramGenerator.xtend
@@ -49,7 +49,6 @@ import java.util.HashSet
import java.util.List
import org.apache.log4j.Logger
import org.eclipse.elk.alg.layered.options.LayeredOptions
-import org.eclipse.elk.alg.rectpacking.options.RectPackingOptions
import org.eclipse.elk.core.options.CoreOptions
import org.eclipse.emf.ecore.EObject
import org.eclipse.sprotty.Dimension
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend
index d8347dd08..512d7e1f3 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/KGraphDiagramServer.xtend
@@ -19,14 +19,13 @@ package de.cau.cs.kieler.klighd.lsp
import com.google.common.base.Throwables
import com.google.common.io.ByteStreams
import com.google.inject.Inject
+import com.google.inject.Injector
import de.cau.cs.kieler.klighd.IAction
import de.cau.cs.kieler.klighd.IAction.ActionContext
import de.cau.cs.kieler.klighd.Klighd
import de.cau.cs.kieler.klighd.KlighdDataManager
import de.cau.cs.kieler.klighd.ViewContext
import de.cau.cs.kieler.klighd.kgraph.KNode
-import de.cau.cs.kieler.klighd.lsp.interactive.layered.LayeredInteractiveActionHandler
-import de.cau.cs.kieler.klighd.lsp.interactive.rectpacking.RectpackingInteractiveActionHandler
import de.cau.cs.kieler.klighd.lsp.model.CheckImagesAction
import de.cau.cs.kieler.klighd.lsp.model.CheckedImagesAction
import de.cau.cs.kieler.klighd.lsp.model.DisplayedActionUIData
@@ -47,8 +46,13 @@ import java.io.InputStream
import java.util.ArrayList
import java.util.Base64
import java.util.Collection
+import java.util.HashMap
import java.util.List
import java.util.Map
+import java.util.ServiceLoader
+import java.util.Set
+import java.util.concurrent.CompletableFuture
+import org.apache.log4j.Logger
import org.eclipse.core.runtime.Platform
import org.eclipse.elk.core.data.LayoutMetaDataService
import org.eclipse.elk.core.data.LayoutOptionData
@@ -74,11 +78,9 @@ import org.eclipse.xtend.lib.annotations.Accessors
*/
class KGraphDiagramServer extends LanguageAwareDiagramServer {
- @Inject
- protected LayeredInteractiveActionHandler constraintActionHandler
+ @Inject protected Injector injector
- @Inject
- protected RectpackingInteractiveActionHandler rectpackingActionHandler
+ Map handlers = new HashMap
@Inject
protected KGraphDiagramState diagramState
@@ -105,6 +107,22 @@ class KGraphDiagramServer extends LanguageAwareDiagramServer {
@Accessors(PUBLIC_GETTER)
protected Object modelLock = new Object
+ /**
+ * Needed for KeithUpdateModelAction
+ *
+ * FIXME Remove this if UpdateModelAction has a cause.
+ */
+ new() {
+ super()
+ // Create map of registered action kinds and handlers.
+ ServiceLoader.load(ISprottyActionHandler, KlighdDataManager.getClassLoader()).forEach[handler |
+ val Set kindsSupported = handler.supportedMessages.keySet
+ for (kind : kindsSupported) {
+ handlers.put(kind, handler);
+ }
+ ]
+ }
+
/**
* Prepares the client side update of the model by processing the potentially needed images on the client. Checks
* for client-side cached images with the {@link CheckImagesAction}. If the corresponding response to the
@@ -215,12 +233,16 @@ class KGraphDiagramServer extends LanguageAwareDiagramServer {
handle(action as RefreshLayoutAction)
} else if (action.getKind === RequestDiagramPieceAction.KIND) {
handle(action as RequestDiagramPieceAction)
- } else if (constraintActionHandler.canHandleAction(action.getKind)) {
- constraintActionHandler.handle(action, clientId, this)
- } else if (rectpackingActionHandler.canHandleAction(action.getKind)) {
- rectpackingActionHandler.handle(action, clientId, this)
} else {
- super.accept(message)
+ val handlerInstance = handlers.get(action.kind)
+ if (handlerInstance !== null) {
+ // Even though we have an instance, it is not yet populated with all injected things.
+ val handler = injector.getInstance(handlerInstance.class)
+ handler.handle(action, clientId, this)
+ } else {
+ // If no handler is registered for this message. Let the default super class handle it.
+ super.accept(message)
+ }
}
}
} catch (Exception e) {
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend
index d866d90ee..b0bdcc778 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/gson_utils/KGraphTypeAdapterUtil.xtend
@@ -3,7 +3,7 @@
*
* http://rtsys.informatik.uni-kiel.de/kieler
*
- * Copyright 2018-2021 by
+ * Copyright 2018-2022 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
@@ -17,16 +17,10 @@
package de.cau.cs.kieler.klighd.lsp.gson_utils
import com.google.gson.GsonBuilder
+import com.google.inject.Injector
+import de.cau.cs.kieler.klighd.KlighdDataManager
import de.cau.cs.kieler.klighd.SynthesisOption
-import de.cau.cs.kieler.klighd.lsp.interactive.layered.DeleteLayerConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.layered.DeletePositionConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.layered.DeleteStaticConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.layered.SetLayerConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.layered.SetPositionConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.layered.SetStaticConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.rectpacking.RectpackingDeletePositionConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.rectpacking.RectpackingSetPositionConstraintAction
-import de.cau.cs.kieler.klighd.lsp.interactive.rectpacking.SetAspectRatioAction
+import de.cau.cs.kieler.klighd.lsp.ISprottyActionHandler
import de.cau.cs.kieler.klighd.lsp.model.CheckedImagesAction
import de.cau.cs.kieler.klighd.lsp.model.PerformActionAction
import de.cau.cs.kieler.klighd.lsp.model.RefreshDiagramAction
@@ -34,16 +28,17 @@ import de.cau.cs.kieler.klighd.lsp.model.RefreshLayoutAction
import de.cau.cs.kieler.klighd.lsp.model.RequestDiagramPieceAction
import de.cau.cs.kieler.klighd.lsp.model.SetSynthesisAction
import java.awt.geom.Point2D
+import java.util.ServiceLoader
import org.eclipse.emf.ecore.EObject
import org.eclipse.sprotty.server.json.ActionTypeAdapter
/**
* Static util class to configure needed gson type adapters for KGraph serialization.
*
- * @author nre
+ * @author nre, sdo
*/
class KGraphTypeAdapterUtil {
- def static GsonBuilder configureGson(GsonBuilder gsonBuilder) {
+ def static GsonBuilder configureGson(GsonBuilder gsonBuilder, Injector injector) {
gsonBuilder
.registerTypeAdapterFactory(
new ActionTypeAdapter.Factory => [
@@ -54,21 +49,18 @@ class KGraphTypeAdapterUtil {
addActionKind(RefreshDiagramAction.KIND, RefreshDiagramAction)
addActionKind(RefreshLayoutAction.KIND, RefreshLayoutAction)
- // Interactive layered actions
- addActionKind(SetStaticConstraintAction.KIND, SetStaticConstraintAction)
- addActionKind(SetPositionConstraintAction.KIND, SetPositionConstraintAction)
- addActionKind(SetLayerConstraintAction.KIND, SetLayerConstraintAction)
- addActionKind(DeleteStaticConstraintAction.KIND, DeleteStaticConstraintAction)
- addActionKind(DeletePositionConstraintAction.KIND, DeletePositionConstraintAction)
- addActionKind(DeleteLayerConstraintAction.KIND, DeleteLayerConstraintAction)
-
- // Interactive rectpacking actions
- addActionKind(RectpackingSetPositionConstraintAction.KIND, RectpackingSetPositionConstraintAction)
- addActionKind(RectpackingDeletePositionConstraintAction.KIND, RectpackingDeletePositionConstraintAction)
- addActionKind(SetAspectRatioAction.KIND, SetAspectRatioAction)
+ // Load all registered action handlers and add their actions.
+ ServiceLoader.load(ISprottyActionHandler, KlighdDataManager.getClassLoader()).forEach[handler |
+ val handlerInstance = injector.getInstance(handler.class)
+ handlerInstance.supportedMessages.keySet.forEach[kind |
+ addActionKind(kind, handlerInstance.supportedMessages.get(kind))
+ ]
+ ]
// Incremental topdown actions
addActionKind(RequestDiagramPieceAction.KIND, RequestDiagramPieceAction)
+
+
]
)
.registerTypeAdapter(Point2D, new Point2DTypeAdapter)
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/ConstraintProperty.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/ConstraintProperty.xtend
index 68f6cc5e9..a7de3a3c0 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/ConstraintProperty.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/ConstraintProperty.xtend
@@ -26,7 +26,8 @@ import de.cau.cs.kieler.klighd.kgraph.KNode
* @author sdo
*/
@Data
-class ConstraintProperty {
+class ConstraintProperty {
KNode kNode
- IProperty property
+ IProperty property
+ T value
}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/ElkGraphConstraintSerializer.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/ElkGraphConstraintSerializer.xtend
new file mode 100644
index 000000000..7c22d0c31
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/ElkGraphConstraintSerializer.xtend
@@ -0,0 +1,52 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive
+
+import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties
+import java.util.List
+import org.eclipse.elk.graph.ElkNode
+import org.eclipse.emf.ecore.resource.Resource
+
+/**
+ * Serializes constraint for an ELK graph by just adding the corresponding properties.
+ *
+ * @author sdo
+ *
+ */
+class ElkGraphConstraintSerializer implements IConstraintSerializer {
+
+ override canHandle(Object graph) {
+ return graph instanceof ElkNode
+ }
+
+ override serializeConstraints(
+ List> changedNodes,
+ Object graph,
+ Resource resource
+ ) {
+ val codeBefore = InteractiveUtil.serializeResource(resource)
+ changedNodes.forEach [ c |
+ val ElkNode elkNode = c.KNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as ElkNode
+ elkNode.setProperty(c.property, c.value)
+ ]
+
+ val codeAfter = InteractiveUtil.serializeResource(resource)
+
+ return InteractiveUtil.calculateTextEdit(codeBefore, codeAfter)
+ }
+
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/IConstraintSerializer.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/IConstraintSerializer.xtend
new file mode 100644
index 000000000..db5002235
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/IConstraintSerializer.xtend
@@ -0,0 +1,47 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive
+
+import java.util.List
+import org.eclipse.emf.ecore.resource.Resource
+import org.eclipse.lsp4j.TextEdit
+
+/**
+ * Service interface for implementations that serialize a set constraint in the model.
+ * E.g. for ElkGraphs a property is added and the graph serialized,
+ * for SCCharts an Annotation with a layout constraint is added and the graph serialized.
+ *
+ * @author sdo
+ *
+ */
+interface IConstraintSerializer {
+ /**
+ * Checks whether this serializer can handle a graph type
+ *
+ * @param graph The graph to serialize
+ * @return true if the graph can be serialized
+ */
+ def boolean canHandle(Object graph);
+
+ /**
+ * @param changedNodes The added constraints.
+ * @param graph The model, e.g. SCChart or ElkGraph.
+ * @param resource The resource to change
+ * @return The TextEdit to send to the client consisting of the new text and a range.
+ */
+ def TextEdit serializeConstraints(List> changedNodes, Object graph, Resource resource);
+}
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/InteractiveUtil.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/InteractiveUtil.xtend
index 8cbb6526d..add8e671d 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/InteractiveUtil.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/InteractiveUtil.xtend
@@ -3,7 +3,7 @@
*
* http://rtsys.informatik.uni-kiel.de/kieler
*
- * Copyright 2020 by
+ * Copyright 2022 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
@@ -16,14 +16,23 @@
*/
package de.cau.cs.kieler.klighd.lsp.interactive
+import de.cau.cs.kieler.klighd.KlighdDataManager
+import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties
+import de.cau.cs.kieler.klighd.kgraph.KIdentifier
import de.cau.cs.kieler.klighd.kgraph.KNode
+import de.cau.cs.kieler.klighd.lsp.KGraphLanguageClient
+import de.cau.cs.kieler.klighd.lsp.KGraphLanguageServerExtension
+import java.io.ByteArrayOutputStream
import java.util.ArrayList
import java.util.List
+import java.util.ServiceLoader
import org.eclipse.elk.alg.layered.options.LayeredOptions
-import org.eclipse.elk.alg.rectpacking.options.RectPackingOptions
-import org.eclipse.elk.core.options.CoreOptions
import org.eclipse.elk.graph.ElkNode
import org.eclipse.elk.graph.properties.IProperty
+import org.eclipse.emf.ecore.resource.Resource
+import org.eclipse.lsp4j.Position
+import org.eclipse.lsp4j.Range
+import org.eclipse.lsp4j.TextEdit
/**
* Provides utility methods for interactive layout.
@@ -83,32 +92,6 @@ class InteractiveUtil {
}
}
- /**
- * Copies constraint properties depending on the algorithm from kNode to elkNode
- * by using {@code copyConstraintProp()}.
- *
- * @param elkNode The target ElkNode
- * @param kNode The source KNode of the property
- */
- static def copyAllConstraints(ElkNode elkNode, KNode kNode) {
- val algorithm = kNode.parent.getProperty(CoreOptions.ALGORITHM)
- var List> props = #[]
- if(algorithm === null || algorithm == 'layered') {
- props = #[
- LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT,
- LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT
- ]
- } else if ("rectpacking".equals(algorithm)) {
- props = #[
- RectPackingOptions.DESIRED_POSITION,
- RectPackingOptions.ASPECT_RATIO
- ]
- }
- for (prop : props) {
- copyConstraintProp(elkNode, kNode, prop)
- }
- }
-
/**
* Determines the root of the given node.
*
@@ -122,4 +105,147 @@ class InteractiveUtil {
}
return parent
}
+
+ /**
+ * Determines the nodes that are connected to {@code node} by relative constraints.
+ * The returned list of nodes is sorted based on the position of the nodes.
+ *
+ * @param node One node of the chain
+ * @param layerNodes Nodes that are in the same layer as {@code node}
+ * @return All nodes of the relative constraint chain of the given node present in the given layer nodes.
+ */
+ static def getChain(KNode node, List layerNodes) {
+ var pos = layerNodes.indexOf(node)
+ var chainNodes = new ArrayList();
+ chainNodes.add(node)
+
+ // from node to the start
+ for (var i = pos - 1; i >= 0; i--) {
+ if (layerNodes.get(i).getProperty(LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_PRED_OF) == getIdOfNode(layerNodes.get(i + 1))
+ || layerNodes.get(i + 1).getProperty(LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_SUCC_OF) == getIdOfNode(layerNodes.get(i))) {
+ chainNodes.add(0, layerNodes.get(i))
+ } else {
+ i = -1
+ }
+ }
+
+ // count from node to the end
+ for (var i = pos + 1; i < layerNodes.size; i++) {
+ if (layerNodes.get(i).getProperty(LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_SUCC_OF) == getIdOfNode(layerNodes.get(i - 1))
+ || layerNodes.get(i - 1).getProperty(LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_PRED_OF) == getIdOfNode(layerNodes.get(i))) {
+ chainNodes.add(layerNodes.get(i))
+ } else {
+ i = layerNodes.size
+ }
+ }
+
+ return chainNodes
+ }
+
+
+ /**
+ * Checks whether the nodes of {@code chain1} and the nodes {@code chain2} can be merged to one chain
+ *
+ * @param chain1 One of the two chains.
+ * @param chain2 Other one of the two chains.
+ * @return Returns true if the chains can be merged.
+ */
+ static def isMergeImpossible(List chain1, List chain2) {
+ var connectedNodes = new ArrayList()
+ for (n : chain1) {
+ var edges = n.outgoingEdges
+ for (e : edges) {
+ connectedNodes.add(e.target)
+ }
+ edges = n.incomingEdges
+ for (e : edges) {
+ connectedNodes.add(e.source)
+ }
+ }
+
+ for (n : connectedNodes) {
+ if (chain2.contains(n)) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ /**
+ * Calls serializer for corresponding model type if one was bound.
+ *
+ * @param changedNodes List of {@code ConstraintProperty} that represent the changed nodes.
+ * @param model The KNode that has to be changed.
+ * @param uri The uri of the model file that will be rewritten if a serializer exists.
+ */
+ static def serializeConstraints(List> changedNodes, KNode model, String uri,
+ KGraphLanguageServerExtension languageServer, KGraphLanguageClient client
+ ) {
+ var serializer = false
+
+ var sourceModel = model.getProperty(KlighdInternalProperties.MODEL_ELEMENT)
+ if (!model.hasProperty(KlighdInternalProperties.MODEL_ELEMENT)) {
+ sourceModel = model.children.get(0).getProperty(KlighdInternalProperties.MODEL_ELEMENT)
+ }
+ for (IConstraintSerializer cs : ServiceLoader.load(IConstraintSerializer,
+ KlighdDataManager.getClassLoader())) {
+ if (cs.canHandle(sourceModel)) {
+ val resource = languageServer.getResource(uri)
+ val textEdit = cs.serializeConstraints(changedNodes, model, resource)
+ // Send changes to the client.
+ client.replaceContentInFile(uri, textEdit.newText, textEdit.range)
+ // If a serializer is registered, we do not need to update the layout since the diagram will update
+ // since the model changes.
+ serializer = true
+ }
+ }
+ // If there is no serializer that changes the textual model (which will cause a refresh model action),
+ // we have to update the layout (not refresh the layout) to update based on the changed via model.
+ if (!serializer) {
+ languageServer.updateLayout(uri)
+ }
+ return
+ }
+
+ /**
+ * Creates a string of a model resource by saving the resource and returning the output string.
+ *
+ * @param resource Resource to get the string for.
+ */
+ static def String serializeResource(Resource resource) {
+ var outputStream = new ByteArrayOutputStream
+ resource.save(outputStream, emptyMap)
+ return outputStream.toString
+ }
+
+ /**
+ * Creates a TextEdit based on the before and after text.
+ *
+ * @param codeBefore The whole text before
+ * @param codeAfter The new text
+ * @return A TextEdit that assumes that the whole code before will ne replaced.
+ */
+ static def TextEdit calculateTextEdit(String codeBefore, String codeAfter) {
+ val lines = codeBefore.split("\r\n|\r|\n")
+ val lastLineLength = lines.get(lines.size - 1).length
+ val Range range = new Range(new Position(0, 0), new Position(lines.length, lastLineLength))
+ return new TextEdit(range, codeAfter)
+ }
+
+ /**
+ * Returns id of a node.
+ *
+ * @param node The node.
+ * @return The id string of the node.
+ */
+ static def String getIdOfNode(KNode node) {
+ var nameStringOfReferenceNode = node.toString
+ val id = node.getData(KIdentifier)
+ if (id !== null) {
+ nameStringOfReferenceNode = id.id
+ }
+ return nameStringOfReferenceNode
+ }
+
}
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/Actions.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/Actions.xtend
index 42c32dcba..b352af9e0 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/Actions.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/Actions.xtend
@@ -141,3 +141,104 @@ class DeleteLayerConstraintAction implements Action {
initializer.accept(this)
}
}
+
+/**
+ * Sets a 'in layer predecessor of'-constraint for a node.
+ *
+ * @author jep
+ */
+@Accessors
+@EqualsHashCode
+@ToString(skipNulls = true)
+class SetInLayerPredecessorOfConstraintAction implements Action {
+ public static val KIND = 'setILPredOfConstraint'
+ String kind = KIND
+
+ InLayerPredecessorOfConstraint constraint
+
+ new() {}
+ new(Consumer initializer) {
+ initializer.accept(this)
+ }
+}
+
+/**
+ * Sets a 'in layer successor of'-constraint for a node.
+ *
+ * @author jep
+ */
+@Accessors
+@EqualsHashCode
+@ToString(skipNulls = true)
+class SetInLayerSuccessorOfConstraintAction implements Action {
+ public static val KIND = 'setILSuccOfConstraint'
+ String kind = KIND
+
+ InLayerSuccessorOfConstraint constraint
+
+ new() {}
+ new(Consumer initializer) {
+ initializer.accept(this)
+ }
+}
+
+/**
+ * Deletes the relative constraints on the node that is identified by the given id.
+ *
+ * @author jep
+ */
+@Accessors
+@EqualsHashCode
+@ToString(skipNulls = true)
+class DeleteRelativeConstraintsAction implements Action {
+ public static val KIND = 'deleteRelativeConstraints'
+ String kind = KIND
+
+ DeleteConstraint constraint
+
+ new() {}
+ new(Consumer initializer) {
+ initializer.accept(this)
+ }
+}
+
+/**
+ * Deletes the iLPredOf constraint on the node that is identified by the given id.
+ *
+ * @author jep
+ */
+@Accessors
+@EqualsHashCode
+@ToString(skipNulls = true)
+class DeleteInLayerPredecessorOfConstraintAction implements Action {
+ public static val KIND = 'deleteILPredOfConstraint'
+ String kind = KIND
+
+ DeleteConstraint constraint
+
+ new() {}
+ new(Consumer initializer) {
+ initializer.accept(this)
+ }
+}
+
+/**
+ * Deletes the iLSuccOf constraint on the node that is identified by the given id.
+ *
+ * @author jep
+ */
+@Accessors
+@EqualsHashCode
+@ToString(skipNulls = true)
+class DeleteInLayerSuccessorOfConstraintAction implements Action {
+ public static val KIND = 'deleteILSuccOfConstraint'
+ String kind = KIND
+
+ DeleteConstraint constraint
+
+ new() {}
+ new(Consumer initializer) {
+ initializer.accept(this)
+ }
+}
+
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/ConstraintsUtils.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/ConstraintsUtils.xtend
index 430035bdd..8759b9021 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/ConstraintsUtils.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/ConstraintsUtils.xtend
@@ -22,7 +22,7 @@ import org.eclipse.elk.alg.layered.options.LayeredOptions
/**
* Provides a set of utility methods that is used in the constraints package.
*
- * @author jet, cos, sdo
+ * @author jep, cos, sdo
*/
class ConstraintsUtils {
@@ -33,17 +33,17 @@ class ConstraintsUtils {
/**
* Returns the value of the position constraint that is set on the node.
*
- * @param node the instance of KNode of which you want the constraint value
+ * @param node The instance of KNode of which you want the constraint value
* @return The position constraint of the given node.
*/
- def static getPosConstraint(KNode node) {
+ def static getPositionConstraint(KNode node) {
return node.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT)
}
/**
* Returns the value of the layer constraint that is set on the node.
*
- * @param node the instance of KNode of which you want the constraint value
+ * @param node The instance of KNode of which you want the constraint value
* @return The layer constraint of the given node.
*/
def static getLayerConstraint(KNode node) {
@@ -54,17 +54,17 @@ class ConstraintsUtils {
* Sets the value of the position constraint that is set on the node.
*
* @param node The instance of KNode of which you want the constraint value
- * @param pos The new desired position of the node.
+ * @param position The new desired position of the node.
*/
- def static setPosConstraint(KNode node, int pos) {
- node.setProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT, pos)
+ def static setPositionConstraint(KNode node, int position) {
+ node.setProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT, position)
}
/**
* Sets the value of the layer constraint that is set on the node.
*
- * @param node the instance of KNode that should get the constraint.
- * @param layer the value for the layer constraint
+ * @param node The instance of KNode that should get the constraint.
+ * @param layer The value for the layer constraint.
*/
def static setLayerConstraint(KNode node, int layer) {
node.setProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, layer)
@@ -74,7 +74,7 @@ class ConstraintsUtils {
* Sets the value of the layer constraint to null.
* This procedure effectively deletes the constraint from the node.
*
- * @param node the instance of KNode of which the layer constraint is set to null
+ * @param node The instance of KNode of which the layer constraint is set to null
*/
def static nullifyLayerConstraint(KNode node) {
node.setProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, null)
@@ -84,9 +84,9 @@ class ConstraintsUtils {
* Sets the value of the position constraint to null.
* This procedure effectively deletes the constraint from the node.
*
- * @param node the instance of KNode of which the position constraint is set to null.
+ * @param node The instance of KNode of which the position constraint is set to null.
*/
- def static nullifyPosConstraint(KNode node) {
+ def static nullifyPositionConstraint(KNode node) {
node.setProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT, null)
}
}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/DeleteConstraint.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/DeleteConstraint.xtend
index e899ea896..df4a2ee0e 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/DeleteConstraint.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/DeleteConstraint.xtend
@@ -21,7 +21,7 @@ import org.eclipse.xtend.lib.annotations.Data
/**
* Data class for a delete constraint.
*
- * @author jet, cos, sdo
+ * @author jep, cos, sdo
*/
@Data
class DeleteConstraint {
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/InLayerPredecessorOfConstraint.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/InLayerPredecessorOfConstraint.xtend
new file mode 100644
index 000000000..83c11118c
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/InLayerPredecessorOfConstraint.xtend
@@ -0,0 +1,30 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.layered
+
+import org.eclipse.xtend.lib.annotations.Data
+
+/**
+ * Data class for a 'in layer predecessor of'-constraint.
+ *
+ * @author jep
+ */
+@Data
+class InLayerPredecessorOfConstraint {
+ String id
+ String referencedNode
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/InLayerSuccessorOfConstraint.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/InLayerSuccessorOfConstraint.xtend
new file mode 100644
index 000000000..a1e3a7da7
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/InLayerSuccessorOfConstraint.xtend
@@ -0,0 +1,30 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.layered
+
+import org.eclipse.xtend.lib.annotations.Data
+
+/**
+ * Data class for a 'in layer successor of'-constraint.
+ *
+ * @author jep
+ */
+@Data
+class InLayerSuccessorOfConstraint {
+ String id
+ String referencedNode
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayerConstraint.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayerConstraint.xtend
index c4c5f07c5..2c0256207 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayerConstraint.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayerConstraint.xtend
@@ -21,11 +21,11 @@ import org.eclipse.xtend.lib.annotations.Data
/**
* Data class for a layer constraint.
*
- * @author jet, cos, sdo
+ * @author jep, cos, sdo
*/
@Data
class LayerConstraint {
String id
int layer
- int layerCons
+ int layerConstraint
}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredConstraintReevaluation.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredConstraintReevaluation.xtend
index d94b1ded8..32a7ad6e0 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredConstraintReevaluation.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredConstraintReevaluation.xtend
@@ -3,7 +3,7 @@
*
* http://rtsys.informatik.uni-kiel.de/kieler
*
- * Copyright 2019, 2020 by
+ * Copyright 2019-2022 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
@@ -20,7 +20,6 @@ import de.cau.cs.kieler.klighd.kgraph.KNode
import de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil
import de.cau.cs.kieler.klighd.lsp.interactive.ConstraintProperty
import de.cau.cs.kieler.klighd.lsp.interactive.InteractiveUtil
-import java.util.HashMap
import java.util.List
import org.eclipse.elk.alg.layered.options.LayeredOptions
import org.eclipse.xtend.lib.annotations.Accessors
@@ -29,12 +28,12 @@ import org.eclipse.xtend.lib.annotations.Accessors
* Class to reevaluate constraint set for the layered algorithm since they may become obsolete or have to be changed
* if some node is moved.
*
- * @author cos, sdo
+ * @author cos, sdo, jep
*/
class LayeredConstraintReevaluation {
@Accessors(PUBLIC_GETTER)
- HashMap changedNodes = newHashMap()
+ List> changedNodes = newLinkedList()
@Accessors(PUBLIC_GETTER)
KNode target
@@ -46,84 +45,93 @@ class LayeredConstraintReevaluation {
/**
* Adjusts position constraints in a layer after one node has been introduced to it.
*
- * @param newNodesOfLayer nodes of the new layer
- * @param oldNodesOfLayer the nodes of old layer
- * @param target the moved node
- * @param newPos the new position of the node
+ * @param newNodesOfLayer The nodes of the new layer.
+ * @param oldNodesOfLayer The nodes of old layer.
+ * @param target The moved node.
+ * @param newPosition The new position of the node.
*/
- def reevaluatePositionConstraintsAfterLayerSwap(List newNodesOfLayer, List oldNodesOfLayer, KNode target,
- int newPos) {
-
+ def reevaluatePositionConstraintsAfterLayerSwap(List newNodesOfLayer, List oldNodesOfLayer,
+ KNode target, int newPosition) {
+ val chainLength = InteractiveUtil.getChain(target, oldNodesOfLayer).size
// formerLayer != newLayer -- should always be true - it doesn't cause errors if it's not, though.
// The node is "deleted" from its old layer if it had a position constraint the old layer
- // needs to be reevaluated
- offsetPosConstraintsOfLayerFrom(oldNodesOfLayer, -1,
+ // needs to be reevaluated.
+ offsetPositionConstraintsOfLayerFrom(oldNodesOfLayer, -chainLength,
target.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID), target)
// The node is added at the new position in the new layer.
- offsetPosConstraintsOfLayerFrom(newNodesOfLayer, 1, newPos, target)
+ offsetPositionConstraintsOfLayerFrom(newNodesOfLayer, chainLength, newPosition, target)
}
/**
* Adjusts position constraints in a layer after one node has been introduced to it.
*
- * @param nodesOfLayer the nodes of the layer
- * @param target the moved node
- * @param newPos the new position of the node
+ * @param nodesOfLayer The nodes of the layer.
+ * @param target The moved node.
+ * @param newPosition The new position of the node.
*/
- def reevaluatePositionConstraintsAfterPosChangeInLayer(List nodesOfLayer, KNode target, int newPos) {
- val oldPos = target.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
+ def reevaluatePositionConstraintsAfterPositionChangeInLayer(List nodesOfLayer, KNode target,
+ int newPosition) {
+ val oldPosition = target.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
- if (newPos < oldPos) {
- // new position constraint is above the old position
- // increment all position constraints of nodes that weren't below the target beforehand
- offsetPosConstraintsOfLayerFromTo(nodesOfLayer, 1, newPos, oldPos, target)
+ val chainLength = InteractiveUtil.getChain(target, nodesOfLayer).size
+ if (newPosition < oldPosition) {
+ // New position constraint is above the old position
+ // increment all position constraints of nodes that weren't below the target beforehand.
+ offsetPositionConstraintsOfLayerFromTo(nodesOfLayer, chainLength, newPosition, oldPosition, target)
} else {
- // oldPos < newPos new position constraint is below the old position
- // Decrement all position constraints of nodes that weren't above the target beforehand
- offsetPosConstraintsOfLayerFromTo(nodesOfLayer, -1, oldPos, newPos, target)
+ // oldPos < newPos: New position constraint is below the old position.
+ // Decrement all position constraints of nodes that weren't above the target beforehand.
+ offsetPositionConstraintsOfLayerFromTo(nodesOfLayer, -chainLength, oldPosition, newPosition, target)
}
}
/**
* Reevaluates position constraints in a layer after a node is removed.
*
- * @param nodesOfLayer the nodes in the layer
- * @param removedNode the removed node
+ * @param nodesOfLayer The nodes in the layer.
+ * @param removedNode The removed node.
*/
def reevaluatePositionConstraintsAfterRemoval(List nodesOfLayer, KNode removedNode) {
- // Offset all positional constraint greater or equal to the new one in order to conserve the
- // established subsequence of nodes below the removed node
- val formerPosCons = ConstraintsUtils.getPosConstraint(removedNode)
- if (formerPosCons !== -1) {
- offsetPosConstraintsOfLayerFrom(nodesOfLayer, -1, formerPosCons, removedNode)
+ // Offset all position constraints greater or equal to the new one in order to conserve the
+ // established subsequence of nodes below the removed node.
+ val formerPositionConstraint = ConstraintsUtils.getPositionConstraint(removedNode)
+ if (formerPositionConstraint !== null) {
+ offsetPositionConstraintsOfLayerFrom(nodesOfLayer, -1, formerPositionConstraint, removedNode)
}
}
/**
- * Optional reevaluation when emptying a layer should lead to its disappearance.
+ * Optional: Reevaluation when emptying a layer should lead to its disappearance.
* Adjust layer constraints in the graph if a new layer constraint empties a layer and lets it disappear.
*
- * @param target the removed node
- * @param targetLayer the layer id
- * @param nodes the nodes in the graph
+ * @param target The removed node.
+ * @param targetLayer The layer id.
+ * @param nodes The nodes in the graph.
+ * @return True, if layers were deleted such that the targetLayer is out of bounds.
*/
def reevaluateAfterEmptyingALayer(KNode target, int targetLayer, List nodes) {
val layerConstraintTarget = ConstraintsUtils.getLayerConstraint(target)
val layerId = target.getProperty(LayeredOptions.LAYERING_LAYER_ID)
- val originalLayerIndex = if (layerConstraintTarget > layerId) layerConstraintTarget else layerId
+ var originalLayerIndex = 0
+ // Calculate previous desired layer of target.
+ if (layerConstraintTarget === null || layerConstraintTarget <= layerId) {
+ originalLayerIndex = layerId
+ } else {
+ originalLayerIndex = layerConstraintTarget
+ }
val originalLayer = InteractiveUtil.getNodesOfLayer(originalLayerIndex, nodes)
- if (originalLayer.length == 1) {
- /* If a layer is emptied and disappears from the drawing
- * then all LayerConstraints with a value higher or equal than
- * the disappeared layer need to be decremented*/
+ if (originalLayer.length == InteractiveUtil.getChain(target, originalLayer).size()) {
+ // If a layer is emptied and disappears from the drawing
+ // then all layer constraint with a value higher or equal than
+ // the disappeared layer need to be decremented.
for (node : nodes) {
- val layerCons = ConstraintsUtils.getLayerConstraint(node)
- if (layerCons >= originalLayerIndex) {
- changedNodes.put(new ConstraintProperty(node, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT), layerCons - 1)
+ val layerConstraint = ConstraintsUtils.getLayerConstraint(node)
+ if (layerConstraint !== null && layerConstraint >= originalLayerIndex) {
+ changedNodes.add(new ConstraintProperty(node, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, layerConstraint - 1))
}
}
@@ -135,18 +143,20 @@ class LayeredConstraintReevaluation {
}
/**
- * Shifting-Reevaluation
+ * Shifting-Reevaluation:
* Simulates the shifting of nodes in order to adjust layer and position constraints.
*
- * @param insertedNode the node that is the target of the constraints
- * @param posCons the value of the position constraint for insertedNode
- * @param layerCons the value of the layer constraint for insertedNode
- * @param oldLayerNodes The origin layer of insertedNode
- * @param newLayerNodes The target layer of insertedNode
- * @param nodes All nodes of the current graph
+ * @param insertedNode The node that is the target of the constraints.
+ * @param newLayerId The id of the new layer.
+ * @param layerConstraint The value of the layer constraint for insertedNode.
+ * @param newPositionId The new position in the layer.
+ * @param positionConstraint The value of the position constraint for insertedNode.
+ * @param oldLayerNodes The origin layer of insertedNode.
+ * @param newLayerNodes The target layer of insertedNode.
+ * @param nodes All nodes of the current graph.
*/
- def void shiftIfNecessary(KNode insertedNode, int newLayerId, int layerCons, int newPosId, int posCons,
- List oldLayerNodes, List newLayerNodes, List nodes) {
+ def void shiftIfNecessary(KNode insertedNode, int newLayerId, int layerConstraint, int newPositionId,
+ int positionConstraint, List oldLayerNodes, List newLayerNodes, List nodes) {
val adjacentNodes = KGraphUtil.getAdjacentNodes(insertedNode)
@@ -154,44 +164,48 @@ class LayeredConstraintReevaluation {
for (n : adjacentNodes) {
if (newLayerNodes.contains(n)) {
- // If the shifted node has a layer constraint. It needs to be incremented else the shift would have no effect.
+ // If the shifted node has a layer constraint.
+ // It needs to be incremented else the shift would have no effect.
shiftedNodes.add(n)
// Test whether the shift leads to more shifts in the next layer.
val nextNextLayerNodes = InteractiveUtil.getNodesOfLayer(newLayerId + 1, nodes)
- val nPosId = n.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
- val nPosCons = ConstraintsUtils.getPosConstraint(n)
+ val nPositionId = n.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
+ val nPositionConstraint = ConstraintsUtils.getPositionConstraint(n)
- shiftIfNecessary(n, newLayerId + 1, layerCons + 1, nPosId, nPosCons, newLayerNodes, nextNextLayerNodes, nodes)
+ shiftIfNecessary(n, newLayerId + 1, layerConstraint + 1, nPositionId, nPositionConstraint,
+ newLayerNodes, nextNextLayerNodes, nodes)
}
}
}
/**
- * Position Reevaluation for Blockshifting
+ * Position Reevaluation for Blockshifting.
* Adjusts positional constraints in the source and target layer after one node has been shifted.
*
- * @param shiftedNode the shifted node
- * @param targetNode the node that might be changed
- * @param posCons the layer the target node
- * @param originLayer the previous layer of the node
- * @param targetLayer the new layer of the node
+ * @param shiftedNode The shifted node.
+ * @param targetNode The node that might be changed.
+ * @param positionConstraint The layer the target node.
+ * @param oldLayer The previous layer of the node.
+ * @param targetLayer The new layer of the node.
*/
- def reevaluateAfterShift(KNode shiftedNode, KNode targetNode, int posCons, List originLayer,
+ def reevaluateAfterShift(KNode shiftedNode, KNode targetNode, int positionConstraint, List oldLayer,
List targetLayer) {
// Currently, we only shift from left to right.
- // Get the position of the shiftedNode - it's the same position on which it will end up in its new layer
- val posIndexOfShifted = shiftedNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
- val posConsShifted = ConstraintsUtils.getPosConstraint(shiftedNode)
- if (posConsShifted !== -1) {
- reevaluatePositionConstraintsAfterLayerSwap(originLayer, targetLayer, shiftedNode, posConsShifted)
+ // Get the position of the shiftedNode - it's the same position on which it will end up in its new layer.
+ val positionIndexOfShifted = shiftedNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
+ val positionConstraintOfShifted = ConstraintsUtils.getPositionConstraint(shiftedNode)
+ if (positionConstraintOfShifted !== null) {
+ reevaluatePositionConstraintsAfterLayerSwap(oldLayer, targetLayer, shiftedNode, positionConstraintOfShifted)
} else {
- reevaluatePositionConstraintsAfterLayerSwap(originLayer, targetLayer, shiftedNode, posIndexOfShifted)
+ reevaluatePositionConstraintsAfterLayerSwap(oldLayer, targetLayer, shiftedNode, positionIndexOfShifted)
}
- // Reevaluate the position constraints in the source and target layer accordingly
- // Also examine the position constraint of the target node
- if (posCons > 0 && posCons >= posIndexOfShifted) {
- changedNodes.put(new ConstraintProperty(targetNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT), posCons - 1)
+ // Reevaluate the position constraints in the source and target layer accordingly.
+ // Also examine the position constraint of the target node.
+ if (positionConstraint > 0 && positionConstraint >= positionIndexOfShifted) {
+ changedNodes.add(
+ new ConstraintProperty(targetNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT,
+ positionConstraint - 1))
}
}
@@ -200,21 +214,23 @@ class LayeredConstraintReevaluation {
* than {@code startPos} and smaller than {@code endPos}.
*
* @param layer The layer to examine as a list including its nodes ordered by position.
- * @param offset The offset that should be applied on the position constraints
- * @param startPos The smallest position of the layer
- * @param endPos The biggest position of the layer
- * @param target The target node
+ * @param offset The offset that should be applied on the position constraints.
+ * @param startPosition The smallest position of the layer.
+ * @param endPosition The biggest position of the layer.
+ * @param target The target node.
*/
- def private offsetPosConstraintsOfLayerFromTo(List layer, int offset, int startPos, int endPos,
- KNode target) {
+ def private offsetPositionConstraintsOfLayerFromTo(List layer, int offset, int startPosition,
+ int endPosition, KNode target) {
if (!layer.empty) {
- for (var i = startPos; i < endPos + 1; i++) {
- val node = layer.get(i)
- val posChoiceCons = ConstraintsUtils.getPosConstraint(node)
+ for (var currentPosition = startPosition; currentPosition < endPosition + 1; currentPosition++) {
+ val node = layer.get(currentPosition)
+ val positionConstraint = ConstraintsUtils.getPositionConstraint(node)
- if (node != target && posChoiceCons !== -1) {
- changedNodes.put(new ConstraintProperty(node, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT), posChoiceCons + offset)
+ if (node != target && positionConstraint !== null) {
+ changedNodes.add(
+ new ConstraintProperty(node, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT,
+ Math.max(0, positionConstraint + offset)))
}
}
}
@@ -222,14 +238,49 @@ class LayeredConstraintReevaluation {
/**
* Offsets all nodes in a layer by {@code offset} that own a positional constraint that is greater or equal
- * than {@code startPos}.
+ * than {@code startPosition}.
*
* @param layer The layer to examine as a list including its nodes ordered by position.
- * @param The offset that should be applied on the position constraints
- * @param startPos The smallest position of the layer
- * @param target The target node
+ * @param offset The offset that should be applied on the position constraints.
+ * @param startPosition The smallest position of the layer.
+ * @param target The target node.
*/
- def private offsetPosConstraintsOfLayerFrom(List layer, int offset, int startPos, KNode target) {
- offsetPosConstraintsOfLayerFromTo(layer, offset, startPos, layer.length - 1, target)
+ def private offsetPositionConstraintsOfLayerFrom(List layer, int offset, int startPosition, KNode target) {
+ offsetPositionConstraintsOfLayerFromTo(layer, offset, startPosition, layer.length - 1, target)
+ }
+
+ /**
+ * Updates position constraints of the moved node and the one in its chain.
+ *
+ * @param node The moved node.
+ * @param newPosition The new position of the moved node.
+ * @param chain The chain of {@code node}.
+ */
+ def reevaluatePositionConstraintInChain(KNode node, int newPosition, List chain) {
+ val offset = chain.indexOf(node)
+ for (var i = 0; i < chain.size; i++) {
+ val chainedNode = chain.get(i)
+ if (chainedNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT) !== null) {
+ val currentPosition = newPosition - (offset - i)
+ changedNodes.add(
+ new ConstraintProperty(chainedNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT,
+ currentPosition))
+ }
+ }
+ }
+
+ /**
+ * Updates layer constraint of the moved node and all other nodes in the chain to the layer of the target.
+ *
+ * @param layer New value of the layer constraints.
+ * @param chain Nodes of the chain the moved node is in.
+ */
+ def reevaluateLayerConstraintsInChain(int layer, List chain) {
+ for (chainedNode : chain) {
+ if (chainedNode.getProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT) !== null) {
+ changedNodes.add(new ConstraintProperty(chainedNode, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT,
+ layer))
+ }
+ }
}
}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveActionHandler.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveActionHandler.xtend
index 67e18d96a..6057c6301 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveActionHandler.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveActionHandler.xtend
@@ -3,7 +3,7 @@
*
* http://rtsys.informatik.uni-kiel.de/kieler
*
- * Copyright 2019, 2020 by
+ * Copyright 2019-2022 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
@@ -24,7 +24,7 @@ import org.eclipse.sprotty.Action
/**
* Handles all sprotty actions for the layered interactive algorithm.
*
- * @author sdo
+ * @author sdo, jep
*/
class LayeredInteractiveActionHandler extends AbstractActionHandler {
@@ -38,7 +38,12 @@ class LayeredInteractiveActionHandler extends AbstractActionHandler {
SetLayerConstraintAction.KIND -> SetLayerConstraintAction,
DeleteStaticConstraintAction.KIND -> DeleteStaticConstraintAction,
DeletePositionConstraintAction.KIND -> DeletePositionConstraintAction,
- DeleteLayerConstraintAction.KIND -> DeleteLayerConstraintAction
+ DeleteLayerConstraintAction.KIND -> DeleteLayerConstraintAction,
+ SetInLayerPredecessorOfConstraintAction.KIND -> SetInLayerPredecessorOfConstraintAction,
+ SetInLayerSuccessorOfConstraintAction.KIND -> SetInLayerSuccessorOfConstraintAction,
+ DeleteRelativeConstraintsAction.KIND -> DeleteRelativeConstraintsAction,
+ DeleteInLayerPredecessorOfConstraintAction.KIND -> DeleteInLayerPredecessorOfConstraintAction,
+ DeleteInLayerSuccessorOfConstraintAction.KIND -> DeleteInLayerSuccessorOfConstraintAction
)
}
@@ -67,6 +72,26 @@ class LayeredInteractiveActionHandler extends AbstractActionHandler {
synchronized (server.modelLock) {
constraintLS.deleteLayerConstraint(action.constraint, clientId)
}
+ } else if (action instanceof SetInLayerPredecessorOfConstraintAction) {
+ synchronized (server.modelLock) {
+ constraintLS.setInLayerPredecessorOfConstraint(action.constraint, clientId)
+ }
+ } else if (action instanceof SetInLayerSuccessorOfConstraintAction) {
+ synchronized (server.modelLock) {
+ constraintLS.setInLayerSuccessorOfConstraint(action.constraint, clientId)
+ }
+ } else if (action instanceof DeleteRelativeConstraintsAction) {
+ synchronized (server.modelLock) {
+ constraintLS.deleteRelativeConstraints(action.constraint, clientId)
+ }
+ } else if (action instanceof DeleteInLayerPredecessorOfConstraintAction) {
+ synchronized (server.modelLock) {
+ constraintLS.deleteInLayerPredecessorOfConstraint(action.constraint, clientId)
+ }
+ } else if (action instanceof DeleteInLayerSuccessorOfConstraintAction) {
+ synchronized (server.modelLock) {
+ constraintLS.deleteILSuccOfConstraint(action.constraint, clientId)
+ }
} else {
throw new IllegalArgumentException("Action " + action.kind + " not supported by handler " +
this.class.simpleName)
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveLanguageServerExtension.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveLanguageServerExtension.xtend
index 0b34f516b..de027a7e7 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveLanguageServerExtension.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/LayeredInteractiveLanguageServerExtension.xtend
@@ -3,7 +3,7 @@
*
* http://rtsys.informatik.uni-kiel.de/kieler
*
- * Copyright 2019, 2020 by
+ * Copyright 2019-2022 by
* + Kiel University
* + Department of Computer Science
* + Real-Time and Embedded Systems Group
@@ -17,25 +17,20 @@
package de.cau.cs.kieler.klighd.lsp.interactive.layered
import com.google.inject.Inject
+import de.cau.cs.kieler.klighd.kgraph.KIdentifier
import com.google.inject.Singleton
-import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties
import de.cau.cs.kieler.klighd.kgraph.KNode
+import de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil
import de.cau.cs.kieler.klighd.lsp.KGraphDiagramState
import de.cau.cs.kieler.klighd.lsp.KGraphLanguageClient
import de.cau.cs.kieler.klighd.lsp.KGraphLanguageServerExtension
import de.cau.cs.kieler.klighd.lsp.LSPUtil
import de.cau.cs.kieler.klighd.lsp.interactive.ConstraintProperty
import de.cau.cs.kieler.klighd.lsp.interactive.InteractiveUtil
-import java.io.ByteArrayOutputStream
-import java.util.HashMap
+import java.util.LinkedList
import java.util.List
-import java.util.Map
import org.eclipse.elk.alg.layered.options.LayeredOptions
-import org.eclipse.elk.graph.ElkNode
import org.eclipse.elk.graph.properties.IProperty
-import org.eclipse.lsp4j.Position
-import org.eclipse.lsp4j.Range
-import org.eclipse.lsp4j.TextEdit
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtext.ide.server.ILanguageServerAccess
import org.eclipse.xtext.ide.server.ILanguageServerExtension
@@ -43,7 +38,7 @@ import org.eclipse.xtext.ide.server.ILanguageServerExtension
/**
* Language server extension to change the layered algorithm in the interactive mode.
*
- * @author jet, cos, sdo
+ * @author jep, cos, sdo
*/
@Singleton
class LayeredInteractiveLanguageServerExtension implements ILanguageServerExtension {
@@ -60,117 +55,337 @@ class LayeredInteractiveLanguageServerExtension implements ILanguageServerExtens
// Not implemented, since it is not needed.
}
+ /**
+ * Sets a 'in layer predecessor'-constraint.
+ *
+ * @param constraint The constraint.
+ * @param clientId The client id.
+ */
+ def setInLayerPredecessorOfConstraint(InLayerPredecessorOfConstraint constraint, String clientId) {
+ val uri = diagramState.getURIString(clientId)
+ setRelativeConstraint(LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_PRED_OF, uri, constraint.id,
+ constraint.getReferencedNode, clientId)
+ }
+
+ /**
+ * Sets a 'in layer successor'-constraint.
+ *
+ * @param constraint The constraint.
+ * @param clientId The client id.
+ */
+ def setInLayerSuccessorOfConstraint(InLayerSuccessorOfConstraint constraint, String clientId) {
+ val uri = diagramState.getURIString(clientId)
+ setRelativeConstraint(LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_SUCC_OF, uri, constraint.id,
+ constraint.getReferencedNode, clientId)
+ }
+
+ /**
+ * Delete relative constraints.
+ *
+ * @param constraint The constraint to delete.
+ * @param clientId The client id.
+ */
+ def deleteRelativeConstraints(DeleteConstraint constraint, String clientId) {
+ val uri = diagramState.getURIString(clientId)
+ val kNode = getKNode(uri, constraint.id)
+ if (kNode !== null) {
+ val changedNodes = new LinkedList>
+ changedNodes.add(new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_PRED_OF, null))
+ changedNodes.add(new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_SUCC_OF, null))
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
+ }
+ }
+
+ /**
+ * Delete in-layer-predecessor-of constraints.
+ *
+ * @param constraint The constraint to delete.
+ * @param clientId The client id.
+ */
+ def deleteInLayerPredecessorOfConstraint(DeleteConstraint constraint, String clientId) {
+ val uri = diagramState.getURIString(clientId)
+ val kNode = getKNode(uri, constraint.id)
+ if (kNode !== null) {
+ val changedNodes = new LinkedList>
+ changedNodes.add(new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_PRED_OF, null))
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
+ }
+ }
+
+ /**
+ * Delete in-layer-successor-of constraints.
+ *
+ * @param constraint The constraint to delete.
+ * @param clientId The client id.
+ */
+ def deleteILSuccOfConstraint(DeleteConstraint constraint, String clientId) {
+ val uri = diagramState.getURIString(clientId)
+ val kNode = getKNode(uri, constraint.id)
+ if (kNode !== null) {
+ val changedNodes = new LinkedList>
+ changedNodes.add(new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_SUCC_OF, null))
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
+ }
+ }
+
+ /**
+ * Sets a relative constraint with a chosen {@code value} on the node that is specified by the {@code targetId}.
+ *
+ * @param property The IProperty class of constraint that should be set.
+ * @param uri The uri of the diagram/file.
+ * @param targetId The id of the node on which the constraint should be set.
+ * @param referencedId The id of the node to which the relation should be set.
+ * @param clientId The client id.
+ */
+ private def setRelativeConstraint(
+ IProperty property,
+ String uri,
+ String targetId,
+ String referencedId,
+ String clientId
+ ) {
+ val targetNode = LSPUtil.getKNode(diagramState, uri, targetId)
+ val parentOfNode = targetNode.parent
+
+ // Get the actual label of the node
+ val referencedNode = LSPUtil.getKNode(diagramState, uri, referencedId)
+
+ var nameStringOfReferenceNode = targetNode.toString
+ val id = referencedNode.getData(KIdentifier)
+ if (id !== null) {
+ nameStringOfReferenceNode = id.id
+ }
+
+ if (targetNode !== null && parentOfNode !== null) {
+ var referenceLayer = referencedNode.getProperty(LayeredOptions.LAYERING_LAYER_ID);
+ var targetPosition = referencedNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID);
+
+ // Update target position depending on the constraint that should be set.
+ if (referenceLayer === targetNode.getProperty(LayeredOptions.LAYERING_LAYER_ID) &&
+ targetPosition > targetNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)) {
+ targetPosition--;
+ }
+ if (property === LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_SUCC_OF) {
+ targetPosition++;
+ }
+
+ var layerNodes = InteractiveUtil.getNodesOfLayer(referenceLayer, parentOfNode.children)
+ var oldLayerNodes = InteractiveUtil.getNodesOfLayer(
+ targetNode.getProperty(LayeredOptions.LAYERING_LAYER_ID), parentOfNode.children)
+
+ // Update position constraints.
+ val layerSwap = referenceLayer !== targetNode.getProperty(LayeredOptions.LAYERING_LAYER_ID)
+ var relativeConstraintReevaluation = new RelativeConstraintReevaluation(targetNode)
+ var absoluteConstraintReevaluation = new LayeredConstraintReevaluation(targetNode)
+
+ targetNode.setProperty(property, null);
+ var List chain = InteractiveUtil.getChain(targetNode, oldLayerNodes)
+
+ if (layerSwap) {
+ absoluteConstraintReevaluation.
+ reevaluatePositionConstraintsAfterLayerSwap(layerNodes, oldLayerNodes, targetNode, targetPosition)
+ absoluteConstraintReevaluation.reevaluateLayerConstraintsInChain(referenceLayer, chain)
+ } else {
+ if (targetPosition !== targetNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID) &&
+ chain.contains(referencedNode) &&
+ targetPosition >= chain.get(0).getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID) &&
+ targetPosition <=
+ chain.get(chain.size - 1).getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)) {
+ // Node is moved within its chain.
+ relativeConstraintReevaluation.reevaluateRelativeConstraintAfterSwapInChain(targetNode, oldLayerNodes)
+ }
+ absoluteConstraintReevaluation.
+ reevaluatePositionConstraintsAfterPositionChangeInLayer(layerNodes, targetNode, targetPosition)
+ }
+
+ var posCons = targetPosition
+ if (layerSwap ||
+ targetPosition < targetNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)) {
+ // Position ids must be increased by the number of predecessors.
+ posCons = targetPosition + chain.indexOf(targetNode)
+ }
+
+ absoluteConstraintReevaluation.reevaluatePositionConstraintInChain(targetNode, posCons, chain)
+
+ // Update relative constraints.
+ if (layerSwap ||
+ targetPosition !== targetNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)) {
+ relativeConstraintReevaluation.checkRelativeConstraints(targetNode, targetPosition, layerNodes, oldLayerNodes,
+ property)
+ relativeConstraintReevaluation.reevaluateRelativeConstraints(targetNode, targetPosition, layerNodes, oldLayerNodes)
+ }
+ val changedNodes = absoluteConstraintReevaluation.changedNodes
+ changedNodes.addAll(relativeConstraintReevaluation.changedNodes)
+ changedNodes.add(new ConstraintProperty(targetNode, property, nameStringOfReferenceNode))
+ if (layerSwap) {
+ // Already apply changes to be able to correctly identify if a chain is split because of the new
+ // relative constraint.
+ changedNodes.forEach[constraint|
+ val KNode kNode = constraint.KNode
+ kNode.setProperty(constraint.property, constraint.value)
+ ]
+ absoluteConstraintReevaluation.reevaluateAfterEmptyingALayer(targetNode, referenceLayer, parentOfNode.children)
+ }
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(targetNode), uri)
+ }
+ }
+
/**
* Sets a layer constraint.
- * @param lc the layer constraint
- * @param clientId the client id
+ *
+ * @param constraint The layer constraint.
+ * @param clientId The client id.
*/
- def setLayerConstraint(LayerConstraint lc, String clientId) {
+ def setLayerConstraint(LayerConstraint constraint, String clientId) {
val uri = diagramState.getURIString(clientId)
- setConstraint(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, uri, lc.id, lc.layer, lc.layerCons)
+ setConstraint(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, uri, constraint.id, constraint.layer,
+ constraint.getLayerConstraint, clientId)
}
/**
* Sets a position constraint.
- * @param pc the position constraint
- * @param clientId the client id
+ *
+ * @param constraint The position constraint.
+ * @param clientId The client id.
*/
- def setPositionConstraint(PositionConstraint pc, String clientId) {
+ def setPositionConstraint(PositionConstraint constraint, String clientId) {
val uri = diagramState.getURIString(clientId)
- setConstraint(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT, uri, pc.id,
- pc.position, pc.posCons)
+ setConstraint(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT, uri, constraint.id,
+ constraint.position, constraint.getPositionConstraint, clientId)
}
/**
* Sets a layer constraint and a positional constraint that
* are encapsulated in an instance of StaticConstraint.
- * @param sc the constraint
- * @param clientId the client id
+ *
+ * @param constraint The constraint.
+ * @param clientId The client id.
*/
- def setStaticConstraint(StaticConstraint sc, String clientId) {
+ def setStaticConstraint(StaticConstraint constraint, String clientId) {
val uri = diagramState.getURIString(clientId)
- val kNode = LSPUtil.getKNode(diagramState, uri, sc.id)
+ val kNode = LSPUtil.getKNode(diagramState, uri, constraint.id)
val parentOfNode = kNode.parent
// In case that the interactive mode is active, the viewContext is not null
// and the element is actually a KNode. Carry on.
if (kNode !== null && parentOfNode !== null) {
- /*
- * As long as no increased pos constraint is present in the target layer
- * and no increased layer constraint is present left to the target layer
- * newLayerId === newLayer Cons && newPosCons = newPosId
- * In the other cases both values can differ.
- */
+ // As long as no increased position constraint is present in the target layer
+ // and no increased layer constraint is present left to the target layer
+ // newLayerId === newLayerConstraint && newPosCons = newPosId
+ // In the other cases both values can differ.
var allNodes = parentOfNode.children
- var newLayerId = sc.layer
- val newPosId = sc.position
- val newPosCons = sc.posCons
- var newLayerCons = sc.layerCons
-
+ var newLayerId = constraint.layer
+ var newLayerConstraint = constraint.getLayerConstraint
+ val List> changedNodes = newLinkedList;
+ // If layerId is -1 all other nodes need to have their layerId and layerChoiceId increased.
+ if (newLayerConstraint == -1) {
+ newLayerId++
+ newLayerConstraint++
+ allNodes.forEach[node |
+ if (node.hasProperty(LayeredOptions.LAYERING_LAYER_ID)) {
+ node.setProperty(LayeredOptions.LAYERING_LAYER_ID,
+ node.getProperty(LayeredOptions.LAYERING_LAYER_ID) + 1
+ )
+ }
+ if (node != kNode && node.hasProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT)) {
+ node.setProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT,
+ node.getProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT) + 1
+ )
+ changedNodes.add(new ConstraintProperty(node, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT,
+ node.getProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT)
+ ))
+ }
+ ]
+ }
+
val layerId = kNode.getProperty(LayeredOptions.LAYERING_LAYER_ID)
var targetLayerNodes = InteractiveUtil.getNodesOfLayer(newLayerId, allNodes)
var oldLayerNodes = InteractiveUtil.getNodesOfLayer(layerId, allNodes)
-
+
+ val chain = InteractiveUtil.getChain(kNode, oldLayerNodes)
+ // posID must be increased by the number of predecessors
+ val newPositionId = constraint.position + chain.indexOf(kNode)
+ val newPositionConstraint = constraint.getPositionConstraint + chain.indexOf(kNode)
+
// Reevaluate insertion of node to target layer
- var reval = new LayeredConstraintReevaluation(kNode)
+ var absoluteConstraintReevaluation = new LayeredConstraintReevaluation(kNode)
- if (reval.reevaluateAfterEmptyingALayer(kNode, newLayerCons, allNodes)) {
- newLayerCons--
+ if (absoluteConstraintReevaluation.reevaluateAfterEmptyingALayer(kNode, newLayerConstraint, allNodes)) {
+ newLayerConstraint--
}
- reval.reevaluatePositionConstraintsAfterLayerSwap(targetLayerNodes, oldLayerNodes, kNode, newPosId)
+ absoluteConstraintReevaluation.
+ reevaluatePositionConstraintsAfterLayerSwap(targetLayerNodes, oldLayerNodes, kNode, newPositionId)
+ absoluteConstraintReevaluation.reevaluateLayerConstraintsInChain(newLayerConstraint, chain)
+ absoluteConstraintReevaluation.reevaluatePositionConstraintInChain(kNode, newPositionConstraint, chain)
- var changedNodes = reval.changedNodes
- changedNodes.put(new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT), newPosCons)
- changedNodes.put(new ConstraintProperty(kNode, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT), newLayerCons)
- // Update source code of the model
- refreshModelInEditor(changedNodes, uri)
+ changedNodes.addAll(absoluteConstraintReevaluation.changedNodes)
+ changedNodes.add(
+ new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT,
+ newPositionConstraint))
+ changedNodes.add(
+ new ConstraintProperty(kNode, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, newLayerConstraint))
+
+ // Update relative constraints.
+ var relativeCOnstraintReevaluation = new RelativeConstraintReevaluation(kNode)
+ relativeCOnstraintReevaluation.reevaluateRelativeConstraints(kNode, newPositionId, targetLayerNodes, oldLayerNodes)
+ changedNodes.addAll(relativeCOnstraintReevaluation.changedNodes)
+ // Update source code of the model.
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
}
}
/**
* Delete a constraint.
- * @param dc the constraint o delete
- * @param clientId the client id
+ *
+ * @param constraint The constraint to delete.
+ * @param clientId The client id.
*/
- def deleteStaticConstraint(DeleteConstraint dc, String clientId) {
+ def deleteStaticConstraint(DeleteConstraint constraint, String clientId) {
val uri = diagramState.getURIString(clientId)
- val kNode = getKNode(uri, dc.id)
+ val kNode = getKNode(uri, constraint.id)
if (kNode !== null) {
- val changedNodes = newHashMap()
- changedNodes.put(new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT), null)
- changedNodes.put(new ConstraintProperty(kNode, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT), null)
- refreshModelInEditor(changedNodes, uri)
+ val changedNodes = new LinkedList>
+ changedNodes.add(
+ new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT, null))
+ changedNodes.add(new ConstraintProperty(kNode, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, null))
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
}
}
/**
* Delete a position constraint.
- * @param dc the position constraint o delete
- * @param clientId the client id
+ *
+ * @param constraint The position constraint to delete.
+ * @param clientId The client id.
*/
- def deletePositionConstraint(DeleteConstraint dc, String clientId) {
+ def deletePositionConstraint(DeleteConstraint constraint, String clientId) {
val uri = diagramState.getURIString(clientId)
- val kNode = getKNode(uri, dc.id)
+ val kNode = getKNode(uri, constraint.id)
if (kNode !== null) {
- val changedNodes = newHashMap()
- changedNodes.put(new ConstraintProperty(kNode, LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT), null)
- refreshModelInEditor(changedNodes, uri)
+ val changedNodes = new LinkedList>
+ changedNodes.add(new ConstraintProperty(
+ kNode,
+ LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT,
+ null
+ ))
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
}
}
/**
* Delete a layer constraint.
- * @param dc the layer constraint o delete
- * @param clientId the client id
+ *
+ * @param constraint The layer constraint to delete.
+ * @param clientId The client id.
*/
- def deleteLayerConstraint(DeleteConstraint dc, String clientId) {
+ def deleteLayerConstraint(DeleteConstraint constraint, String clientId) {
val uri = diagramState.getURIString(clientId)
- val kNode = getKNode(uri, dc.id)
+ val kNode = getKNode(uri, constraint.id)
if (kNode !== null) {
- val changedNodes = newHashMap()
- changedNodes.put(new ConstraintProperty(kNode, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT), null)
- refreshModelInEditor(changedNodes, uri)
+ val changedNodes = new LinkedList>
+ changedNodes.add(new ConstraintProperty(kNode, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT, null))
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
}
}
@@ -178,30 +393,97 @@ class LayeredInteractiveLanguageServerExtension implements ILanguageServerExtens
* Sets a layer or position constraint with a chosen {@code value} on the node
* that is specified by the {@code targetID}.
*
- * @param PropID the type of constraint that should be set (LayerConstraint or PositionConstraint)
- * The IProperty class is expected.
+ * @param property The IProperty of constraint that should be set (LayerConstraint or PositionConstraint).
* @param uri The uri of the diagram/file.
* @param targetId The id of the node on which the constraint should be set.
- * @param value Either the id of the position or the id of the layer.
+ * @param oldValue Either the id of the position or the id of the layer.
+ * @param newValue Either the value if the position constraint or the layer constraint.
+ * @param cliendId The client id.
*/
- private def setConstraint(IProperty property, String uri, String targetId, int valueId, int valueCons) {
+ private def setConstraint(IProperty property, String uri, String targetId, int oldValue, int newValue,
+ String clientId
+ ) {
val kNode = LSPUtil.getKNode(diagramState, uri, targetId)
val parentOfNode = kNode.parent
if (kNode !== null && parentOfNode !== null) {
- var layerID = kNode.getProperty(LayeredOptions.LAYERING_LAYER_ID)
- var List residingLayer
- residingLayer = InteractiveUtil.getNodesOfLayer(layerID, parentOfNode.children)
+ var layerId = kNode.getProperty(LayeredOptions.LAYERING_LAYER_ID)
+ var positionId = kNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT);
+ if (property === LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT) {
+ layerId = oldValue
+ }
+ var newValueConstraint = newValue
+ var newValueId = oldValue
+
+ val List> changedNodes = newLinkedList;
+ // If layerId is -1 all other nodes need to have their layerId and layerChoiceId increased.
+ if (oldValue == -1) {
+ layerId++
+ newValueId++
+ newValueConstraint++
+ parentOfNode.children.forEach[node |
+ if (node.hasProperty(LayeredOptions.LAYERING_LAYER_ID)) {
+ node.setProperty(LayeredOptions.LAYERING_LAYER_ID,
+ node.getProperty(LayeredOptions.LAYERING_LAYER_ID) + 1
+ )
+ }
+ if (node != kNode && node.hasProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT)) {
+ node.setProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT,
+ node.getProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT) + 1
+ )
+ changedNodes.add(new ConstraintProperty(node, LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT,
+ node.getProperty(LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT)
+ ))
+ }
+ ]
+ }
+
+ var List layerNodes = InteractiveUtil.getNodesOfLayer(layerId, parentOfNode.children)
+ val oldLayerNodes = InteractiveUtil.getNodesOfLayer(kNode.getProperty(LayeredOptions.LAYERING_LAYER_ID),
+ parentOfNode.children
+ )
- var reval = new LayeredConstraintReevaluation(kNode)
+ val oldPosition = kNode.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
+
+ var relativeConstraintReevaluation = new RelativeConstraintReevaluation(kNode)
+ val chain = InteractiveUtil.getChain(kNode, oldLayerNodes)
+ if (property === LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT) {
+ positionId = oldValue
+ if (positionId != -1 &&
+ positionId >= chain.get(0).getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID) &&
+ positionId <= chain.get(chain.size - 1).getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)) {
+ // node is moved within its chain
+ relativeConstraintReevaluation.reevaluateRelativeConstraintAfterSwapInChain(kNode, oldLayerNodes)
+ } else if (positionId < oldPosition) {
+ // posID must be increased by the number of predecessors
+ newValueConstraint += chain.indexOf(kNode)
+ newValueId += chain.indexOf(kNode)
+ }
+ }
+
+ var absoluteConstraintReevalution = new LayeredConstraintReevaluation(kNode)
switch (property) {
- case LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT:
- reval.reevaluatePositionConstraintsAfterPosChangeInLayer(residingLayer, kNode, valueId)
+ case LayeredOptions.CROSSING_MINIMIZATION_POSITION_CHOICE_CONSTRAINT: {
+ absoluteConstraintReevalution.reevaluatePositionConstraintsAfterPositionChangeInLayer(layerNodes, kNode, newValueId)
+ absoluteConstraintReevalution.reevaluatePositionConstraintInChain(kNode, newValueConstraint, chain)
+ }
+ case LayeredOptions.LAYERING_LAYER_CHOICE_CONSTRAINT: {
+ absoluteConstraintReevalution.reevaluateLayerConstraintsInChain(layerId, chain)
+ if (absoluteConstraintReevalution.reevaluateAfterEmptyingALayer(kNode, newValueConstraint, parentOfNode.children)) {
+ newValueConstraint--
+ }
+ }
}
- var changedNodes = reval.changedNodes
- changedNodes.put(new ConstraintProperty(kNode, property), valueCons)
- refreshModelInEditor(changedNodes, uri)
+ changedNodes.addAll(absoluteConstraintReevalution.changedNodes)
+ changedNodes.add(new ConstraintProperty(kNode, property, newValueConstraint))
+
+ // Update relative constraints.
+ if (positionId !== null) {
+ relativeConstraintReevaluation.reevaluateRelativeConstraints(kNode, positionId, layerNodes, oldLayerNodes)
+ changedNodes.addAll(relativeConstraintReevaluation.changedNodes)
+ };
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
}
}
@@ -213,9 +495,9 @@ class LayeredInteractiveLanguageServerExtension implements ILanguageServerExtens
* This version of getKNode retrieves the root itself. If you already have retrieved the root,
* then you should use the other variant.
*
- * @param uri The resource's uri
- * @param nodeId The Id of the requested KNode
- * @return The requested node
+ * @param uri The resource's uri.
+ * @param nodeId The Id of the requested KNode.
+ * @return The requested node.
*/
private def getKNode(String uri, String nodeId) {
return LSPUtil.getKNode(diagramState, uri, nodeId)
@@ -224,49 +506,16 @@ class LayeredInteractiveLanguageServerExtension implements ILanguageServerExtens
/**
* Sends request to the client to update the file according to the property changes.
*
- * @param changedNodes list of all changes to nodes
- * @param uri uri of resource
+ * @param changedNodes The list of all changes to nodes.
+ * @param model The main kNode.
+ * @param uri The uri of resource.
*/
- def refreshModelInEditor(HashMap changedNodes, String uri) {
- val resource = languageServer.getResource(uri)
-
- // Get previous file content as String
- var outputStream = new ByteArrayOutputStream
- resource.save(outputStream, emptyMap)
- val codeBefore = outputStream.toString
-
- var changed = false
- for (entry : changedNodes.keySet) {
- // set Property of corresponding elkNode
- val kNode = entry.KNode
- val elkNode = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT)
-
- if (elkNode instanceof ElkNode) {
- val value = changedNodes.get(entry)
- if (kNode.getProperty(entry.property) !== value) {
- kNode.setProperty(entry.property, value)
- InteractiveUtil.copyAllConstraints(elkNode, kNode)
- changed = true;
- }
- }
- }
-
- val elkNode = changedNodes.keySet().head.KNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT)
- if (elkNode instanceof ElkNode && changed) {
- val Map> changes = newHashMap
-
- // Get changed file as String
- outputStream = new ByteArrayOutputStream
- resource.save(outputStream, emptyMap)
- val String codeAfter = outputStream.toString().trim
- // The range is the length of the previous file.
- val Range range = new Range(new Position(0, 0), new Position(codeBefore.split("\r\n|\r|\n").length, 0))
- val TextEdit textEdit = new TextEdit(range, codeAfter)
- changes.put(uri, #[textEdit]);
- this.client.replaceContentInFile(uri, codeAfter, range)
- return
- } else {
- languageServer.updateDiagram(uri)
- }
+ def refreshModelInEditor(List> changedNodes, KNode model, String uri) {
+ changedNodes.forEach[constraint|
+ val KNode kNode = constraint.KNode
+ kNode.setProperty(constraint.property, constraint.value)
+ ]
+ InteractiveUtil.serializeConstraints(changedNodes, model, uri, this.languageServer, this.client)
+ return
}
}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/PositionConstraint.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/PositionConstraint.xtend
index 85229d444..c87a98868 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/PositionConstraint.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/PositionConstraint.xtend
@@ -21,11 +21,11 @@ import org.eclipse.xtend.lib.annotations.Data
/**
* Data class for a position constraint.
*
- * @author jet, cos, sdo
+ * @author jep, cos, sdo
*/
@Data
class PositionConstraint {
String id
int position
- int posCons
+ int positionConstraint
}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/RelativeConstraintReevaluation.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/RelativeConstraintReevaluation.xtend
new file mode 100644
index 000000000..d021737c5
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/RelativeConstraintReevaluation.xtend
@@ -0,0 +1,157 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.layered
+
+import de.cau.cs.kieler.klighd.kgraph.KNode
+import de.cau.cs.kieler.klighd.lsp.interactive.ConstraintProperty
+import de.cau.cs.kieler.klighd.lsp.interactive.InteractiveUtil
+import java.util.List
+import org.eclipse.elk.alg.layered.options.LayeredOptions
+import org.eclipse.elk.graph.properties.IProperty
+import org.eclipse.xtend.lib.annotations.Accessors
+
+/**
+ * Class to reevaluate relative constraint set for the layered algorithm since they may become obsolete or have to be
+ * changed if some node is moved.
+ *
+ * @author jep
+ */
+class RelativeConstraintReevaluation {
+
+ IProperty predecessorProperty = LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_PRED_OF
+ IProperty successorProperty = LayeredOptions.CROSSING_MINIMIZATION_IN_LAYER_SUCC_OF
+
+ @Accessors(PUBLIC_GETTER)
+ List> changedNodes = newLinkedList()
+
+ @Accessors(PUBLIC_GETTER)
+ KNode target
+
+ new(KNode target) {
+ this.target = target
+ }
+
+ /**
+ * When a node is moved between two other nodes, the relative constraints of them must be updated.
+ *
+ * @param target The moved node.
+ * @param newPosition The position {@code target} is moved to.
+ * @param newLayerNodes Nodes of the layer {@code target} is moved to.
+ * @param oldLayerNodes Nodes of the layer {@code target} was original in.
+ */
+ def reevaluateRelativeConstraints(KNode target, int newPosition, List newLayerNodes, List oldLayerNodes) {
+ val chainNodes = InteractiveUtil.getChain(target, oldLayerNodes)
+ var startOfChain = chainNodes.get(0)
+ var endOfChain = chainNodes.get(chainNodes.size - 1)
+ var position = newPosition
+ // Determine newPosition of new successor.
+ if (newLayerNodes.contains(target)) {
+ val oldPos = target.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
+ if (newPosition > oldPos) {
+ position++
+ }
+ }
+
+ if (position > 0 && position < newLayerNodes.size) {
+ // Update relative constraint of the new predecessor and successor.
+ val predecessor = newLayerNodes.get(position - 1)
+ val successor = newLayerNodes.get(position)
+ if (predecessor.getProperty(predecessorProperty) !== null) {
+ changedNodes.add(new ConstraintProperty(predecessor, predecessorProperty, startOfChain.labels.get(0).text))
+ }
+ if (successor.getProperty(successorProperty) !== null) {
+ changedNodes.add(new ConstraintProperty(successor, successorProperty, endOfChain.labels.get(0).text))
+ }
+ }
+ }
+
+ /**
+ * Updates relative constraints that have the moved node as target and of the target.
+ *
+ * @param target The moved node.
+ * @param newPosition The position {@code target} is moved to.
+ * @param newLayerNodes Nodes of the layer {@code target} is moved to.
+ * @param oldLayerNodes Nodes of the layer {@code target} was original in.
+ * @param property The property to check.
+ */
+ def checkRelativeConstraints(KNode target, int newPosition, List newLayerNodes, List oldLayerNodes,
+ IProperty property
+ ) {
+ val oldPosition = target.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
+ var forbidden = false
+ // Delete relative constraints that are overwritten by new relative constraint of the target.
+ switch(property) {
+ case predecessorProperty: {
+ if (oldPosition + 1 < oldLayerNodes.size) {
+ changedNodes.add(new ConstraintProperty(oldLayerNodes.get(oldPosition + 1), successorProperty, null))
+ }
+ // If the chains can not be merged, delete old relative constraint and only merge moved node with chain.
+ val chain = InteractiveUtil.getChain(newLayerNodes.get(newPosition), newLayerNodes)
+ forbidden = InteractiveUtil.isMergeImpossible(InteractiveUtil.getChain(target, oldLayerNodes), chain)
+ if (forbidden) {
+ if (oldPosition - 1 >= 0) {
+ changedNodes.add(new ConstraintProperty(oldLayerNodes.get(oldPosition - 1), predecessorProperty, null))
+ }
+ changedNodes.add(new ConstraintProperty(target, successorProperty, null))
+ }
+ }
+ case successorProperty: {
+ if (oldPosition - 1 >= 0) {
+ changedNodes.add(new ConstraintProperty(oldLayerNodes.get(oldPosition - 1), predecessorProperty, null))
+ }
+ // If the chains can not be merged, delete old relative constraint and only merge moved node with chain.
+ val chain = InteractiveUtil.getChain(newLayerNodes.get(newPosition - 1), newLayerNodes)
+ forbidden = InteractiveUtil.isMergeImpossible(InteractiveUtil.getChain(target, oldLayerNodes), chain)
+ if (forbidden) {
+ if (oldPosition + 1 < oldLayerNodes.size) {
+ changedNodes.add(new ConstraintProperty(oldLayerNodes.get(oldPosition + 1), successorProperty, null))
+ }
+ changedNodes.add(new ConstraintProperty(target, predecessorProperty, null))
+ }
+ }
+ }
+ }
+
+ /**
+ * Reevaluates relative constraints after a node is moved within its chain.
+ *
+ * @param target The moved node.
+ * @param layerNodes The nodes that are in the same layer as {@code target}.
+ */
+ def reevaluateRelativeConstraintAfterSwapInChain(KNode target, List layerNodes) {
+ // Remove relative constraint of target.
+ changedNodes.add(new ConstraintProperty(target, predecessorProperty, null))
+ changedNodes.add(new ConstraintProperty(target, successorProperty, null))
+ // Must be done in order for correct calculation of the chain in later reevaluation.
+ target.setProperty(predecessorProperty, null)
+ target.setProperty(successorProperty, null)
+
+ // Remove relative constraint of predecessor and successor that could have the moved node as target.
+ val oldPosition = target.getProperty(LayeredOptions.CROSSING_MINIMIZATION_POSITION_ID)
+ if (oldPosition - 1 >= 0) {
+ val oldPredecessor = layerNodes.get(oldPosition - 1)
+ oldPredecessor.setProperty(predecessorProperty, null)
+ changedNodes.add(new ConstraintProperty(oldPredecessor, predecessorProperty, null))
+ }
+ if (oldPosition + 1 < layerNodes.size) {
+ val oldSuccessor = layerNodes.get(oldPosition + 1)
+ changedNodes.add(new ConstraintProperty(oldSuccessor, successorProperty, null))
+ oldSuccessor.setProperty(successorProperty, null)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/StaticConstraint.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/StaticConstraint.xtend
index d25812b6c..f58a5007a 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/StaticConstraint.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/layered/StaticConstraint.xtend
@@ -21,7 +21,7 @@ import org.eclipse.xtend.lib.annotations.Data
/**
* Data class for a combined position and layer constraint.
*
- * @author jet, cos, sdo
+ * @author jep, cos, sdo
*/
@Data
class StaticConstraint {
@@ -44,11 +44,11 @@ class StaticConstraint {
/**
* Value for the layer constraint
*/
- int layerCons
+ int layerConstraint
/**
* Value for the position constraint
*/
- int posCons
+ int positionConstraint
}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeActionHandler.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeActionHandler.xtend
new file mode 100644
index 000000000..a6c39d756
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeActionHandler.xtend
@@ -0,0 +1,45 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This code is provided under the terms of the Eclipse Public License (EPL).
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.mrtree
+
+import com.google.inject.Inject
+import de.cau.cs.kieler.klighd.lsp.AbstractActionHandler
+import de.cau.cs.kieler.klighd.lsp.KGraphDiagramServer
+import org.eclipse.sprotty.Action
+
+/**
+ * This class handles all sprotty actions for the interactive mrtree algorithm.
+ *
+ * @author sdo
+ *
+ */
+class MrTreeActionHandler extends AbstractActionHandler {
+
+ @Inject
+ MrTreeInteractiveLanguageServerExtension lsExtension
+
+ new() {
+ this.supportedMessages = newHashMap(MrTreeSetPositionConstraintAction.KIND -> MrTreeSetPositionConstraintAction)
+ }
+
+ override handle(Action action, String clientId, KGraphDiagramServer server) {
+ if (action instanceof MrTreeSetPositionConstraintAction) {
+ synchronized((server as KGraphDiagramServer).modelLock) {
+ lsExtension.setPositionConstraint(action.constraint, clientId)
+ }
+ } else {
+ throw new IllegalArgumentException("Action " + action.kind + " not supported by handler " + this.class.simpleName)
+ }
+ }
+
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeInteractiveLanguageServerExtension.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeInteractiveLanguageServerExtension.xtend
new file mode 100644
index 000000000..6832940c1
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeInteractiveLanguageServerExtension.xtend
@@ -0,0 +1,108 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.mrtree
+
+import com.google.inject.Inject
+import com.google.inject.Singleton
+import de.cau.cs.kieler.klighd.kgraph.KNode
+import de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil
+import de.cau.cs.kieler.klighd.lsp.KGraphDiagramState
+import de.cau.cs.kieler.klighd.lsp.KGraphLanguageClient
+import de.cau.cs.kieler.klighd.lsp.KGraphLanguageServerExtension
+import de.cau.cs.kieler.klighd.lsp.LSPUtil
+import de.cau.cs.kieler.klighd.lsp.interactive.ConstraintProperty
+import de.cau.cs.kieler.klighd.lsp.interactive.InteractiveUtil
+import java.util.List
+import org.eclipse.elk.alg.mrtree.options.MrTreeOptions
+import org.eclipse.elk.graph.properties.IProperty
+import org.eclipse.xtend.lib.annotations.Accessors
+import org.eclipse.xtext.ide.server.ILanguageServerAccess
+import org.eclipse.xtext.ide.server.ILanguageServerExtension
+
+/**
+ * Language server extension to change the mrtree algorithm in the interactive mode.
+ * @author sdo
+ *
+ */
+@Singleton
+class MrTreeInteractiveLanguageServerExtension implements ILanguageServerExtension {
+
+ @Accessors KGraphLanguageClient client;
+
+ @Inject
+ KGraphDiagramState diagramState
+
+ @Inject
+ KGraphLanguageServerExtension languageServer
+
+ override initialize(ILanguageServerAccess access) {
+ }
+
+ /**
+ * Set order constraint of node specified by node id.
+ * This changes all order values of all constraints of a previous layout run.
+ *
+ * @param pc constraint to be set
+ * @param clientId identifier of diagram
+ */
+ def setPositionConstraint(MrTreeSetPositionConstraint pc, String clientId) {
+ val uri = diagramState.getURIString(clientId)
+ setConstraint(MrTreeOptions.POSITION_CONSTRAINT, uri, pc.id,
+ pc.position, pc.positionConstraint)
+ }
+
+ /**
+ * Sets a layer or position constraint with a chosen {@code value} on the node
+ * that is specified by the {@code targetID}.
+ *
+ * @param PropID the type of constraint that should be set (LayerConstraint or PositionConstraint)
+ * The IProperty class is expected.
+ * @param uri The uri of the diagram/file.
+ * @param targetID The id of the node on which the constraint should be set.
+ * @param value Either the id of the position or the id of the layer.
+ */
+ private def setConstraint(IProperty property, String uri, String targetID, int valueId, int valueCons) {
+ val kNode = LSPUtil.getKNode(diagramState, uri, targetID)
+ val parentOfNode = kNode.parent
+
+ if (kNode !== null && parentOfNode !== null) {
+ val reeval = new PositionConstraintReevaluation(kNode)
+ reeval.reevaluatePositionConstraintsAfterPosChangeInLayer(parentOfNode, kNode, valueId)
+
+ var changedNodes = reeval.changedNodes
+ refreshModelInEditor(changedNodes, KGraphUtil.getRootNodeOf(kNode), uri)
+ }
+ }
+
+
+
+ /**
+ * Sends request to the client to update the file according to the property changes.
+ *
+ * @param changedNodes The list of all changes to nodes.
+ * @param model The main kNode.
+ * @param uri The uri of resource.
+ */
+ def refreshModelInEditor(List> changedNodes, KNode model, String uri) {
+ changedNodes.forEach[constraint|
+ val KNode kNode = constraint.KNode
+ kNode.setProperty(constraint.property, constraint.value)
+ ]
+ InteractiveUtil.serializeConstraints(changedNodes, model, uri, this.languageServer, this.client)
+ return
+ }
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeInteractiveUtil.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeInteractiveUtil.xtend
new file mode 100644
index 000000000..4505b73f5
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeInteractiveUtil.xtend
@@ -0,0 +1,98 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.mrtree
+
+import de.cau.cs.kieler.klighd.kgraph.KEdge
+import de.cau.cs.kieler.klighd.kgraph.KNode
+import java.util.ArrayList
+import java.util.Comparator
+import java.util.List
+import java.util.stream.Collectors
+import org.eclipse.elk.alg.mrtree.options.MrTreeOptions
+import org.eclipse.elk.core.math.KVector
+import org.eclipse.elk.core.options.Direction
+
+/**
+ * Provides utility methods for the interactive mrtree algorithm.
+ *
+ * @author sdo
+ *
+ */
+class MrTreeInteractiveUtil {
+
+ static def List getSiblings(KNode n, KNode parent) {
+ val lowestParent = getLowestParent(n, parent);
+ if (lowestParent === null)
+ return new ArrayList();
+ val siblings = parent.children.stream.
+ filter[x | lowestParent == getLowestParent(x, parent)].collect(Collectors.toList)
+ return siblings
+ }
+
+ static def KNode getLowestParent(KNode n, KNode parent) {
+ val dirVec = getDirectionVector(parent);
+ if (n.incomingEdges.size == 0)
+ return null
+ val sources = n.incomingEdges.stream().map[x | x.source].collect(Collectors.toList)
+ val parents = parent.children.stream.filter[x | sources.contains(x)].collect(Collectors.toList)
+
+ if (parents.size < 1)
+ return null
+ else if (parents.size == 1)
+ return parents.get(0)
+ else {
+ val lowestParentPos = parents.stream.
+ map[x | new KVector(x.xpos + x.width / 2, x.ypos + x.height / 2).dotProduct(dirVec)].
+ max(Comparator.naturalOrder).get
+ val lowestParent = parents.stream.
+ filter[x | new KVector(x.xpos + x.width / 2, x.ypos + x.height / 2).dotProduct(dirVec) == lowestParentPos].
+ findFirst.get
+
+ return lowestParent;
+ }
+ }
+
+ /**
+ * Gets a KVector which faces in the direction of the layout direction of parent
+ * @param parent the parent node of the graph
+ * @return the KVector
+ */
+ static def KVector getDirectionVector(KNode parent) {
+ val direction = parent.getProperty(MrTreeOptions.DIRECTION)
+ if (direction == Direction.LEFT)
+ return new KVector(-1, 0)
+ else if (direction == Direction.RIGHT)
+ return new KVector(1, 0)
+ else if (direction == Direction.UP)
+ return new KVector(0, -1)
+ else
+ return new KVector(0, 1)
+ }
+
+ /**
+ * Get the child nodes of n within a tree.
+ * @param n the n
+ * @return the child nodes of n within a tree
+ */
+ static def List getChildren(KNode n) {
+ var re = new ArrayList();
+ for (KEdge out : n.getOutgoingEdges()) {
+ re.add(out.getTarget());
+ }
+ return re.stream().distinct().collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeSetPositionConstraint.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeSetPositionConstraint.xtend
new file mode 100644
index 000000000..5700b26f1
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeSetPositionConstraint.xtend
@@ -0,0 +1,31 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.mrtree
+
+import org.eclipse.xtend.lib.annotations.Data
+
+/**
+ * Data class for a position constraint sent from client to server for the mrtree algorithm.
+ *
+ * @author sdo
+ */
+@Data
+class MrTreeSetPositionConstraint {
+ String id
+ int position
+ int positionConstraint
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeSetPositionConstraintAction.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeSetPositionConstraintAction.xtend
new file mode 100644
index 000000000..f860449ab
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/MrTreeSetPositionConstraintAction.xtend
@@ -0,0 +1,41 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.mrtree
+
+import java.util.function.Consumer
+import org.eclipse.sprotty.Action
+import org.eclipse.xtend.lib.annotations.Accessors
+import org.eclipse.xtend.lib.annotations.EqualsHashCode
+import org.eclipse.xtend.lib.annotations.ToString
+
+/**
+ * Sets the order of a node for the MrTree algorithm.
+ */
+@Accessors
+@EqualsHashCode
+@ToString(skipNulls = true)
+public class MrTreeSetPositionConstraintAction implements Action {
+ public static val KIND = 'treeSetPositionConstraint'
+ String kind = KIND
+
+ MrTreeSetPositionConstraint constraint
+
+ new() {}
+ new(Consumer initializer) {
+ initializer.accept(this)
+ }
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/PositionConstraintReevaluation.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/PositionConstraintReevaluation.xtend
new file mode 100644
index 000000000..dced571fd
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/PositionConstraintReevaluation.xtend
@@ -0,0 +1,76 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.mrtree
+
+import de.cau.cs.kieler.klighd.kgraph.KNode
+import de.cau.cs.kieler.klighd.lsp.interactive.ConstraintProperty
+import java.util.LinkedList
+import org.eclipse.elk.alg.mrtree.options.MrTreeOptions
+import org.eclipse.xtend.lib.annotations.Accessors
+
+/**
+ * Class to reevalute position constraints for MrTree.
+ *
+ * @author jnc, sdo
+ */
+class PositionConstraintReevaluation {
+ @Accessors(PUBLIC_GETTER)
+ KNode target
+
+ @Accessors(PUBLIC_GETTER)
+ LinkedList> changedNodes = newLinkedList()
+
+ new(KNode target) {
+ this.target = target
+ }
+
+ /**
+ * Adjusts position constraints in a layer after one node has been introduced to it.
+ * @param parent the nodes of the layer
+ * @param target the moved node
+ * @param newPosition the new position of the node
+ */
+ def reevaluatePositionConstraintsAfterPosChangeInLayer(KNode parent, KNode target, int newPosition) {
+ var targetSiblings = MrTreeInteractiveUtil.getSiblings(target, parent)
+
+ // There is no point in sorting a list with less than 2 elements.
+ if (targetSiblings.size <= 1) {
+ return
+ }
+
+ // Sort list by node positions.
+ if (parent.getProperty(MrTreeOptions.DIRECTION).horizontal)
+ targetSiblings = targetSiblings.sortBy[ypos]
+ else
+ targetSiblings = targetSiblings.sortBy[xpos]
+
+ // Set target node to its target position.
+ val newPositionInBounds =
+ if (newPosition >= targetSiblings.length) targetSiblings.length - 1
+ else newPosition
+ if (newPositionInBounds == targetSiblings.indexOf(target)) {
+ return
+ }
+ targetSiblings.remove(target);
+ targetSiblings.add(newPositionInBounds, target);
+
+ // Set node position constraint to its list index.
+ for (var i = 0; i < targetSiblings.length; i++) {
+ changedNodes.add(new ConstraintProperty(targetSiblings.get(i), MrTreeOptions.POSITION_CONSTRAINT, i))
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/SetAspectRatio.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/SetAspectRatio.xtend
new file mode 100644
index 000000000..6c1e68719
--- /dev/null
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/mrtree/SetAspectRatio.xtend
@@ -0,0 +1,30 @@
+/*
+ * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
+ *
+ * http://rtsys.informatik.uni-kiel.de/kieler
+ *
+ * Copyright 2022 by
+ * + Kiel University
+ * + Department of Computer Science
+ * + Real-Time and Embedded Systems Group
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package de.cau.cs.kieler.klighd.lsp.interactive.mrtree
+
+import org.eclipse.xtend.lib.annotations.Data
+
+/**
+ * Data class to set a new aspect ratio which is sent from client to server for the mrtree algorithm.
+ *
+ * @author sdo
+ */
+@Data
+class SetAspectRatio {
+ String id
+ Double aspectRatio
+}
diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/rectpacking/RectpackingInteractiveLanguageServerExtension.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/rectpacking/RectpackingInteractiveLanguageServerExtension.xtend
index 7d9ef43d0..f7ab13f42 100644
--- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/rectpacking/RectpackingInteractiveLanguageServerExtension.xtend
+++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/interactive/rectpacking/RectpackingInteractiveLanguageServerExtension.xtend
@@ -18,23 +18,18 @@ package de.cau.cs.kieler.klighd.lsp.interactive.rectpacking
import com.google.inject.Inject
import com.google.inject.Singleton
-import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties
import de.cau.cs.kieler.klighd.kgraph.KNode
+import de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil
import de.cau.cs.kieler.klighd.lsp.KGraphDiagramState
import de.cau.cs.kieler.klighd.lsp.KGraphLanguageClient
import de.cau.cs.kieler.klighd.lsp.KGraphLanguageServerExtension
import de.cau.cs.kieler.klighd.lsp.LSPUtil
+import de.cau.cs.kieler.klighd.lsp.interactive.ConstraintProperty
import de.cau.cs.kieler.klighd.lsp.interactive.InteractiveUtil
-import java.io.ByteArrayOutputStream
import java.util.Arrays
import java.util.List
-import java.util.Map
import org.eclipse.elk.alg.rectpacking.options.RectPackingOptions
import org.eclipse.elk.core.options.CoreOptions
-import org.eclipse.elk.graph.ElkNode
-import org.eclipse.lsp4j.Position
-import org.eclipse.lsp4j.Range
-import org.eclipse.lsp4j.TextEdit
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtext.ide.server.ILanguageServerAccess
import org.eclipse.xtext.ide.server.ILanguageServerExtension
@@ -99,9 +94,10 @@ class RectpackingInteractiveLanguageServerExtension implements ILanguageServerEx
}
}
}
- kNode.setProperty(RectPackingOptions.DESIRED_POSITION, desiredPosition)
- refreshModelInEditor(changedNodes, uri)
+ refreshModelInEditor(new ConstraintProperty(kNode, RectPackingOptions.DESIRED_POSITION, desiredPosition), KGraphUtil.getRootNodeOf(kNode), uri)
+ } else {
+ languageServer.updateLayout(uri)
}
}
@@ -143,8 +139,10 @@ class RectpackingInteractiveLanguageServerExtension implements ILanguageServerEx
}
}
kNode.setProperty(RectPackingOptions.DESIRED_POSITION, null)
- refreshModelInEditor(changedNodes, uri)
+ refreshModelInEditor(new ConstraintProperty(kNode, RectPackingOptions.DESIRED_POSITION, null), KGraphUtil.getRootNodeOf(kNode), uri)
+ } else {
+ languageServer.updateLayout(uri)
}
}
@@ -157,69 +155,22 @@ class RectpackingInteractiveLanguageServerExtension implements ILanguageServerEx
def setAspectRatio(SetAspectRatio constraint, String clientId) {
val uri = diagramState.getURIString(clientId)
val kNode = LSPUtil.getKNode(diagramState, uri, constraint.id)
- kNode.setProperty(RectPackingOptions.ASPECT_RATIO, Double.valueOf(constraint.aspectRatio))
- val resource = languageServer.getResource(uri);
-
- // Get previous file content as String
- var outputStream = new ByteArrayOutputStream
- resource.save(outputStream, emptyMap)
- val codeBefore = outputStream.toString
-
- val elkNode = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT)
- if (elkNode instanceof ElkNode) {
- val Map> changes = newHashMap
- elkNode.setProperty(RectPackingOptions.ASPECT_RATIO, constraint.aspectRatio)
-
- // Get changed file as String
- outputStream = new ByteArrayOutputStream
- resource.save(outputStream, emptyMap)
- val codeAfter = outputStream.toString
-
- // The range is the length of the previous file.
- val Range range = new Range(new Position(0, 0), new Position(codeBefore.split("\r\n|\r|\n").length, 0))
- val TextEdit textEdit = new TextEdit(range, codeAfter)
- changes.put(uri, #[textEdit]);
- this.client.replaceContentInFile(uri, codeAfter, range)
- return
- }
+ refreshModelInEditor(new ConstraintProperty(kNode, RectPackingOptions.ASPECT_RATIO, Double.valueOf(constraint.aspectRatio)),
+ KGraphUtil.getRootNodeOf(kNode), uri
+ )
}
/**
* Applies property changes to the file given by the uri by sending by notifying the client to execute the changes.
*
- * @param changedNodes The KNodes that changed.
+ * @param constraint The constraint to serialize
+ * @param model The main KNode
* @param uri uri of resource
*/
- def refreshModelInEditor(List changedNodes, String uri) {
- val resource = languageServer.getResource(uri);
-
- // Get previous file content as String
- var outputStream = new ByteArrayOutputStream
- resource.save(outputStream, emptyMap)
- val codeBefore = outputStream.toString
-
- for (node : changedNodes) {
- val elkNode = node.getProperty(KlighdInternalProperties.MODEL_ELEMENT)
- if (elkNode instanceof ElkNode) {
- InteractiveUtil.copyAllConstraints(elkNode, node)
- }
- }
- val elkNode = changedNodes.get(0).getProperty(KlighdInternalProperties.MODEL_ELEMENT)
- if (elkNode instanceof ElkNode) {
- val Map> changes = newHashMap
-
- // Get changed file as String
- outputStream = new ByteArrayOutputStream
- resource.save(outputStream, emptyMap)
- val codeAfter = outputStream.toString().trim()
-
- // The range is the length of the previous file.
- val Range range = new Range(new Position(0, 0), new Position(codeBefore.split("\r\n|\r|\n").length, 0))
- val TextEdit textEdit = new TextEdit(range, codeAfter)
- changes.put(uri, #[textEdit]);
- this.client.replaceContentInFile(uri, codeAfter, range)
- return
- }
-
+ def refreshModelInEditor(ConstraintProperty