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.elk org.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 constraint, KNode model, String uri) { + val KNode kNode = constraint.KNode + kNode.setProperty(constraint.property, constraint.value) + InteractiveUtil.serializeConstraints(#[constraint], model, uri, this.languageServer, this.client) + return } } diff --git a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/launch/AbstractLsCreator.xtend b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/launch/AbstractLsCreator.xtend index 246644a01..efd04e19c 100644 --- a/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/launch/AbstractLsCreator.xtend +++ b/plugins/de.cau.cs.kieler.klighd.lsp/src/de/cau/cs/kieler/klighd/lsp/launch/AbstractLsCreator.xtend @@ -124,7 +124,7 @@ abstract class AbstractLsCreator implements ILsCreator { // TypeAdapter is needed to be able to send recursive data in json val Consumer configureGson = [ gsonBuilder | - KGraphTypeAdapterUtil.configureGson(gsonBuilder) + KGraphTypeAdapterUtil.configureGson(gsonBuilder, injector) ] // Get all LSExtensions to use them as local services val localServices = newArrayList