diff --git a/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.klighd.lsp.IActionHandler b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.klighd.lsp.IActionHandler new file mode 100644 index 0000000000..ed4b1b8b26 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.klighd.lsp.IActionHandler @@ -0,0 +1 @@ +de.cau.cs.kieler.language.server.sccharts.structurebasedediting.SCChartsStructureBasedEditingActionHandler \ No newline at end of file diff --git a/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.klighd.lsp.interactive.IConstraintSerializer b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.klighd.lsp.interactive.IConstraintSerializer new file mode 100644 index 0000000000..c20236902b --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.klighd.lsp.interactive.IConstraintSerializer @@ -0,0 +1 @@ +de.cau.cs.kieler.language.server.sccharts.SCTXConstraintSerializer \ No newline at end of file diff --git a/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.language.server.ILanguageServerContribution b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.language.server.ILanguageServerContribution index 4dcb5ba1fb..a142937f1d 100644 --- a/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.language.server.ILanguageServerContribution +++ b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.language.server.ILanguageServerContribution @@ -1,4 +1,5 @@ de.cau.cs.kieler.language.server.registration.RegistrationLanguageServerContribution de.cau.cs.kieler.language.server.kicool.KiCoolLanguageServerContribution de.cau.cs.kieler.language.server.simulation.SimulationLanguageServerContribution -de.cau.cs.kieler.language.server.verification.VerificationLanguageServerContribution \ No newline at end of file +de.cau.cs.kieler.language.server.verification.VerificationLanguageServerContribution +de.cau.cs.kieler.language.server.sccharts.structurebasedediting.SCChartsStructeBasedEditingLanguageServerContribution \ No newline at end of file diff --git a/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.sccharts.ui.synthesis.hooks.SynthesisHook b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.sccharts.ui.synthesis.hooks.SynthesisHook new file mode 100644 index 0000000000..4dd1df1999 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/META-INF/services/de.cau.cs.kieler.sccharts.ui.synthesis.hooks.SynthesisHook @@ -0,0 +1 @@ +de.cau.cs.kieler.sccharts.ui.synthesis.hooks.StructuralEditingHook \ No newline at end of file diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/LSCreator.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/LSCreator.xtend index 60e178f3ef..bf55391719 100644 --- a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/LSCreator.xtend +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/LSCreator.xtend @@ -15,6 +15,7 @@ package de.cau.cs.kieler.language.server import de.cau.cs.kieler.core.services.KielerServiceLoader import de.cau.cs.kieler.klighd.lsp.KGraphLanguageClient import de.cau.cs.kieler.klighd.lsp.interactive.layered.LayeredInteractiveLanguageServerExtension +import de.cau.cs.kieler.klighd.lsp.interactive.mrtree.MrTreeInteractiveLanguageServerExtension import de.cau.cs.kieler.klighd.lsp.interactive.rectpacking.RectpackingInteractiveLanguageServerExtension import de.cau.cs.kieler.klighd.lsp.launch.AbstractLsCreator import java.util.List @@ -32,6 +33,8 @@ class LSCreator extends AbstractLsCreator { RectpackingInteractiveLanguageServerExtension rectPack + MrTreeInteractiveLanguageServerExtension mrTree + List diagramHighlighters List iLanguageServerExtensions @@ -39,7 +42,8 @@ class LSCreator extends AbstractLsCreator { override getLanguageServerExtensions() { constraints = injector.getInstance(LayeredInteractiveLanguageServerExtension) rectPack = injector.getInstance(RectpackingInteractiveLanguageServerExtension) - iLanguageServerExtensions = newArrayList(constraints, rectPack) + mrTree = injector.getInstance(MrTreeInteractiveLanguageServerExtension) + iLanguageServerExtensions = newArrayList(constraints, rectPack, mrTree) for (lse : KielerServiceLoader.load(ILanguageServerContribution)) { iLanguageServerExtensions.add(lse.getLanguageServerExtension(injector)) } @@ -60,6 +64,7 @@ class LSCreator extends AbstractLsCreator { } constraints.client = languageClient as KGraphLanguageClient rectPack.client = languageClient as KGraphLanguageClient + mrTree.client = languageClient as KGraphLanguageClient diagramHighlighters = newArrayList for (iLSdhc : KielerServiceLoader.load(ILSDiagramHighlighterContribution)) { var highlighter = iLSdhc.getHighlighter(injector) diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/SCTXConstraintSerializer.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/SCTXConstraintSerializer.xtend new file mode 100644 index 0000000000..d7f3c717e9 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/SCTXConstraintSerializer.xtend @@ -0,0 +1,111 @@ +/* + * 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.language.server.sccharts + +import de.cau.cs.kieler.annotations.Annotatable +import de.cau.cs.kieler.annotations.AnnotationsFactory +import de.cau.cs.kieler.annotations.TypedStringAnnotation +import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties +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 de.cau.cs.kieler.klighd.lsp.interactive.ConstraintProperty +import de.cau.cs.kieler.klighd.lsp.interactive.IConstraintSerializer +import de.cau.cs.kieler.sccharts.impl.StateImpl +import java.io.ByteArrayOutputStream +import java.util.List +import java.util.Map +import org.eclipse.elk.graph.properties.IProperty +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range +import org.eclipse.lsp4j.TextEdit + +/** + * Serialize a constraint for an SCChart node (region or state) as a layout annotation. + * + * @author sdo + * + */ +class SCTXConstraintSerializer implements IConstraintSerializer { + + override canHandle(Object graph) { + return graph instanceof StateImpl + } + + override serializeConstraints(List> changedNodes, Object graph, String uri, + KGraphLanguageServerExtension ls, KGraphLanguageClient client) { + // Serialize model into given uri. + val resource = ls.getResource(uri) + + // Get previous file content as String + var outputStream = new ByteArrayOutputStream + resource.save(outputStream, emptyMap) + val codeBefore = outputStream.toString + val Map> changes = newHashMap + changedNodes.forEach[c| + val Annotatable anno = c.KNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as Annotatable + copyConstraintAnnotations(anno, c.KNode, c.property.id, c.property) + ] + // 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. + // Just make sure the range is big enough + val Range range = new Range(new Position(0, 0), new Position(codeBefore.split("\r\n|\r|\n").length * 2, 0)) + val TextEdit textEdit = new TextEdit(range, codeAfter) + changes.put(uri, #[textEdit]); + client.replaceContentInFile(uri, codeAfter, range) + } + + /** + * Copies an arbitrary IProperty of a KNode to a State if the value on the KNode + * is different to the value on the State. + * If the new value on the KNode was the default value of the property + * then the property is set to null on the State. + * + * @param state The target state. + * @param kNode The source KNode of the property. + * @param annotation The annotation that should be set. + * @param property Determines which IProperty should be copied. + */ + static def copyConstraintAnnotations(Annotatable state, KNode kNode, String annotation, IProperty property) { + val String value = "" + kNode.getProperty(property) + + val annotations = state.getAnnotations().filter(TypedStringAnnotation) + + // Remove old annotation if it exists. + var TypedStringAnnotation annotationToRemove = null + for (a : annotations) { + if (a.type.equals(annotation)) { + annotationToRemove = a + } + } + if (annotationToRemove !== null) { + state.annotations.remove(annotationToRemove) + } + + // Add annotation with new value if the value is not the default one. + if (kNode.getProperty(property) !== null && !kNode.getProperty(property).equals(property.^default)) { + var newA = AnnotationsFactory::eINSTANCE.createTypedStringAnnotation => [ + it.name = "layout" + it.type = annotation + it.values += value + ] + state.annotations.add(newA) + } + + } + +} diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/EdgeActions.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/EdgeActions.xtend new file mode 100644 index 0000000000..8ba4f6eae9 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/EdgeActions.xtend @@ -0,0 +1,292 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +import de.cau.cs.kieler.klighd.structurebasedediting.InputType +import de.cau.cs.kieler.klighd.structurebasedediting.StructureBasedEditingMessage +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 + +/** + * Action received from client when a change of target is requested. + * The transmitted information is the new target state id and the id of the edge. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ChangeTargetStateAction implements Action { + public static val LABEL = "Change target state" + public static val KIND = 'SCChart_graph_changeTargetState' + String kind = KIND + + public String id + public String new_target + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("new_target", "SelectTarget", "New target state"); + return #[input1]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ChangeTargetStateAction.LABEL, + ChangeTargetStateAction.KIND, + false, + ChangeTargetStateAction.getInputs() + ) + } +} + +/** + * Action received from client when a change of source is requested. + * The transmitted information is the new source state id and the id of the edge. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ChangeSourceStateAction implements Action { + public static val LABEL = "Change source state" + public static val KIND = 'SCChart_graph_changeSourceState' + String kind = KIND + + public String id + public String new_source + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("new_source", "SelectSource", "New source state"); + return #[input1]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ChangeSourceStateAction.LABEL, + ChangeSourceStateAction.KIND, + false, + ChangeSourceStateAction.getInputs() + ) + } +} + +/** + * Action received from client if a change of trigger or effect is requested. + * The given information is the id of the edge and the new trigger and effects as string representations. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ChangeTriggerEffectAction implements Action { + public static val LABEL = "Change trigger and effect" + public static val KIND = 'SCChart_graph_changeTriggerAndEffect' + String kind = KIND + + public String id + public String trigger + public String effect + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("trigger", "String", "New trigger"); + val input2 = new InputType("effect", "String", "New effect"); + return #[input1, input2]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ChangeTriggerEffectAction.LABEL, + ChangeTriggerEffectAction.KIND, + false, + ChangeTriggerEffectAction.getInputs() + ) + } +} + +/** + * Action received from the client if a edge priority should change. + * The given information is the new priority and the edge id. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ChangePriorityAction implements Action { + public static val LABEL = "Change priority" + public static val KIND = 'SCChart_graph_changePriorityOfEdge' + String kind = KIND + + public String id + public String priority + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("priority", "String", "Change Priority"); + return #[input1]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ChangePriorityAction.LABEL, + ChangePriorityAction.KIND, + false, + ChangePriorityAction.getInputs() + ) + } +} + +/** + * Action received from the client if a transition should be weak + * The given information is the id of the edge + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ChangeToWeakTransitionAction implements Action { + public static val LABEL = "Change to weak transition" + public static val KIND = 'SCChart_graph_changeToWeakTransition' + String kind = KIND + + public String id + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + return #[]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ChangeToWeakTransitionAction.LABEL, + ChangeToWeakTransitionAction.KIND, + false, + ChangeToWeakTransitionAction.getInputs() + ) + } +} + +/** + * Action received from the client if a transition should be aborting + * The given information is the id of the edge + * + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ChangeToAbortingTransitionAction implements Action { + public static val LABEL = "Change to aborting transition" + public static val KIND = 'SCChart_graph_changeToAbortTransition' + String kind = KIND + + public String id + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + return #[]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ChangeToAbortingTransitionAction.LABEL, + ChangeToAbortingTransitionAction.KIND, + false, + ChangeToAbortingTransitionAction.getInputs() + ) + } +} + +/** + * Action received from the client if a transition should be terminating + * The given information is the id of the edge + * + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ChangeToTerminatingTransitionAction implements Action { + public static val LABEL = "Change to terminating transition" + public static val KIND = 'SCChart_graph_changeToTerminatingTransition' + String kind = KIND + + public String id + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + return #[]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ChangeToTerminatingTransitionAction.LABEL, + ChangeToTerminatingTransitionAction.KIND, + false, + ChangeToTerminatingTransitionAction.getInputs() + ) + } +} diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/GeneralActions.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/GeneralActions.xtend new file mode 100644 index 0000000000..478e436dd4 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/GeneralActions.xtend @@ -0,0 +1,59 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +import de.cau.cs.kieler.klighd.structurebasedediting.InputType +import de.cau.cs.kieler.klighd.structurebasedediting.StructureBasedEditingMessage +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 + +/** + * The delete action is supported by all selectable elements. + * The given information are one or many nodes ids seperated by : + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class DeleteAction implements Action { + public static val LABEL = "Delete" + public static val KIND = 'SCChart_graph_Delete' + String kind = KIND + + public String id + public Boolean mergable = true + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + return #[]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + DeleteAction.LABEL, + DeleteAction.KIND, + true, + DeleteAction.getInputs() + ) + } +} diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/RegionActions.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/RegionActions.xtend new file mode 100644 index 0000000000..4920775db9 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/RegionActions.xtend @@ -0,0 +1,101 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +import de.cau.cs.kieler.klighd.structurebasedediting.InputType +import de.cau.cs.kieler.klighd.structurebasedediting.StructureBasedEditingMessage +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 + +/** + * Action received from client to rename a region. + * Given information is the regions id and the new name of the region. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class RenameRegionAction implements Action { + public static val LABEL = "Rename region" + public static val KIND = 'SCChart_graph_RenameRegion' + String kind = KIND + + public String id + public String region_name + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("region_name", "String", "New Name"); + return #[input1]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + RenameRegionAction.LABEL, + RenameRegionAction.KIND, + false, + RenameRegionAction.getInputs() + ) + } +} + +/** + * Action received from client to add concurrent behavior. + * Given information is the new concurrent regions name and the name of the initial state in it. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class AddConcurrentRegionAction implements Action { + public static val LABEL = "Add concurrent region" + public static val KIND = 'SCChart_graph_AddConcurrentRegion' + String kind = KIND + + public String id + public String region_name + public String state_name + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("state_name", "String", "New initial state name"); + val input2 = new InputType("region_name", "String", "New Region Name"); + return #[input1, input2]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + AddConcurrentRegionAction.LABEL, + AddConcurrentRegionAction.KIND, + false, + AddConcurrentRegionAction.getInputs() + ) + } +} diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructeBasedEditingLanguageServerContribution.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructeBasedEditingLanguageServerContribution.xtend new file mode 100644 index 0000000000..2cbe6ca176 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructeBasedEditingLanguageServerContribution.xtend @@ -0,0 +1,27 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +import com.google.inject.Injector +import de.cau.cs.kieler.language.server.ILanguageServerContribution + +/** + * Used during creation of a language server and to set the client accordingly. + * + */ +class SCChartsStructeBasedEditingLanguageServerContribution implements ILanguageServerContribution { + + override getLanguageServerExtension(Injector injector) { + return injector.getInstance(SCChartsStructureBasedEditingLanguageServerExtension) + } +} diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructureBasedEditingActionHandler.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructureBasedEditingActionHandler.xtend new file mode 100644 index 0000000000..8ef8dd7557 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructureBasedEditingActionHandler.xtend @@ -0,0 +1,122 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +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 + +class SCChartsStructureBasedEditingActionHandler extends AbstractActionHandler { + + @Inject + SCChartsStructureBasedEditingLanguageServerExtension lsExtension + + new() { + this.supportedMessages = newHashMap( + DeleteAction.KIND -> DeleteAction, + RenameStateAction.KIND -> RenameStateAction, + AddSuccessorStateAction.KIND -> AddSuccessorStateAction, + AddHierarchicalStateAction.KIND -> AddHierarchicalStateAction, + ChangeTargetStateAction.KIND -> ChangeTargetStateAction, + ChangeSourceStateAction.KIND -> ChangeSourceStateAction, + ChangeTriggerEffectAction.KIND -> ChangeTriggerEffectAction, + RenameRegionAction.KIND -> RenameRegionAction, + AddConcurrentRegionAction.KIND -> AddConcurrentRegionAction, + ChangeToAbortingTransitionAction.KIND -> ChangeToAbortingTransitionAction, + ChangeToTerminatingTransitionAction.KIND -> ChangeToTerminatingTransitionAction, + ChangeToWeakTransitionAction.KIND -> ChangeToWeakTransitionAction, + AddTransitionAction.KIND -> AddTransitionAction, + ToggleFinalStateAction.KIND -> ToggleFinalStateAction, + MakeInitialStateAction.KIND -> MakeInitialStateAction, + EditSemanticDeclarationAction.KIND -> EditSemanticDeclarationAction, + ChangePriorityAction.KIND -> ChangePriorityAction + ) + } + + override handle(Action action, String clientId, KGraphDiagramServer server) { + + if (action.kind == DeleteAction.KIND) { + synchronized (server.modelLock) { + lsExtension.delete(action as DeleteAction, clientId) + } + } else if (action.kind == RenameStateAction.KIND) { + synchronized (server.modelLock) { + lsExtension.rename(action, clientId) + } + } else if (action.kind == AddSuccessorStateAction.KIND) { + synchronized (server.modelLock) { + lsExtension.addSuccessorState(action as AddSuccessorStateAction, clientId) + } + } else if (action.kind == AddHierarchicalStateAction.KIND) { + synchronized (server.modelLock) { + lsExtension.addHirachicalNode(action as AddHierarchicalStateAction, clientId) + } + } else if (action.kind == ChangeTargetStateAction.KIND) { + synchronized (server.modelLock) { + lsExtension.changeDestination(action as ChangeTargetStateAction, clientId) + } + } else if (action.kind == ChangeSourceStateAction.KIND) { + synchronized (server.modelLock) { + lsExtension.changeSource(action as ChangeSourceStateAction, clientId) + } + } else if (action.kind == ChangeTriggerEffectAction.KIND) { + synchronized (server.modelLock) { + lsExtension.changeIO(action as ChangeTriggerEffectAction, clientId) + } + } else if (action.kind == RenameRegionAction.KIND) { + synchronized (server.modelLock) { + lsExtension.rename(action, clientId) + } + } else if (action.kind == AddConcurrentRegionAction.KIND) { + synchronized (server.modelLock) { + lsExtension.addConcurrentRegion(action as AddConcurrentRegionAction, clientId) + } + } else if (action.kind == ChangeToAbortingTransitionAction.KIND) { + synchronized (server.modelLock) { + lsExtension.changeToAbort(action as ChangeToAbortingTransitionAction, clientId) + } + } else if (action.kind == ChangeToTerminatingTransitionAction.KIND) { + synchronized (server.modelLock) { + lsExtension.changeToTerminating(action as ChangeToTerminatingTransitionAction, clientId) + } + } else if (action.kind == ChangeToWeakTransitionAction.KIND) { + synchronized (server.modelLock) { + lsExtension.changeToWeak(action as ChangeToWeakTransitionAction, clientId) + } + } else if (action.kind == AddTransitionAction.KIND) { + synchronized (server.modelLock) { + lsExtension.addNewTransition(action as AddTransitionAction, clientId) + } + } else if (action.kind == ToggleFinalStateAction.KIND) { + synchronized (server.modelLock) { + lsExtension.toggleFinalState(action as ToggleFinalStateAction, clientId) + } + } else if (action.kind == MakeInitialStateAction.KIND) { + synchronized (server.modelLock) { + lsExtension.makeInitialState(action as MakeInitialStateAction, clientId) + } + } else if (action.kind == EditSemanticDeclarationAction.KIND) { + synchronized (server.modelLock) { + lsExtension.editSemanticDeclaration(action as EditSemanticDeclarationAction, clientId) + } + } else if (action.kind == ChangePriorityAction.KIND) { + synchronized (server.modelLock) { + lsExtension.changeEdgePriority(action as ChangePriorityAction, clientId) + } + } else { + throw new IllegalArgumentException("Action " + action.kind + " not supported by handler " + + this.class.simpleName) + } + } +} \ No newline at end of file diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructureBasedEditingLanguageServerExtension.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructureBasedEditingLanguageServerExtension.xtend new file mode 100644 index 0000000000..8cd8f22c7c --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/SCChartsStructureBasedEditingLanguageServerExtension.xtend @@ -0,0 +1,892 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +import com.google.inject.Inject +import de.cau.cs.kieler.kexpressions.impl.OperatorExpressionImpl +import de.cau.cs.kieler.kexpressions.impl.ValuedObjectImpl +import de.cau.cs.kieler.kexpressions.impl.ValuedObjectReferenceImpl +import de.cau.cs.kieler.kexpressions.keffects.impl.AssignmentImpl +import de.cau.cs.kieler.kexpressions.kext.KExtStandaloneParser +import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties +import de.cau.cs.kieler.klighd.kgraph.KEdge +import de.cau.cs.kieler.klighd.kgraph.KNode +import de.cau.cs.kieler.klighd.lsp.KGraphDiagramState +import de.cau.cs.kieler.klighd.lsp.KGraphLanguageServerExtension +import de.cau.cs.kieler.klighd.lsp.LSPUtil +import de.cau.cs.kieler.language.server.ILanguageClientProvider +import de.cau.cs.kieler.language.server.KeithLanguageClient +import de.cau.cs.kieler.sccharts.ControlflowRegion +import de.cau.cs.kieler.sccharts.PreemptionType +import de.cau.cs.kieler.sccharts.Region +import de.cau.cs.kieler.sccharts.State +import de.cau.cs.kieler.sccharts.Transition +import de.cau.cs.kieler.sccharts.extensions.SCChartsTransitionExtensions +import de.cau.cs.kieler.sccharts.impl.SCChartsFactoryImpl +import java.io.ByteArrayOutputStream +import java.util.List +import java.util.Map +import javax.inject.Singleton +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range +import org.eclipse.lsp4j.TextEdit +import org.eclipse.lsp4j.services.LanguageClient +import org.eclipse.sprotty.Action +import org.eclipse.xtext.ide.server.ILanguageServerAccess +import org.eclipse.xtext.ide.server.ILanguageServerExtension + +/** + * Structure based editing language server extension for SCCharts. This handles all actions that may be send by the client. + */ +@Singleton +class SCChartsStructureBasedEditingLanguageServerExtension implements ILanguageServerExtension, ILanguageClientProvider { + + @Inject extension SCChartsTransitionExtensions + + @Inject + extension KGraphLanguageServerExtension + + @Inject KGraphDiagramState diagramState + + /** + * The language client allows to send notifications or requests from the server to the client. + * Notifications are preferred, since they allow more asynchronity. + */ + KeithLanguageClient client + + SCChartsFactoryImpl factory = new SCChartsFactoryImpl + + Position previsionRange + + override initialize(ILanguageServerAccess access) { + factory = new SCChartsFactoryImpl() + } + + override setLanguageClient(LanguageClient client) { + this.client = client as KeithLanguageClient + } + + override getLanguageClient() { + return this.client + } + + /** + * Finds the range, i.e. the number of lines, in the file currently active in the diagram state of the client. + * + * @param clientId The clientId + */ + def initCurrentResource(String clientId) { + val uri = diagramState.getURIString(clientId) + val resource = getResource(uri); + val outputStream = new ByteArrayOutputStream + resource.save(outputStream, emptyMap) + val codeBefore = outputStream.toString().trim() + val lines = codeBefore.split("\r\n|\r|\n") + // The range is the length of the previous file. + val lastLine = lines.get(lines.length - 1) + previsionRange = new Position(lines.length, lastLine.length) + } + + /** + * Called to add a new transition to a state. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def addNewTransition(AddTransitionAction action, String clientId) { + initCurrentResource(clientId) + + // get the node corresponding to the selected state on the client. Since the contextmenu was opened for a specific + // state we know the id exists and is a state so we can omit a try catch block + val uri = diagramState.getURIString(clientId) + val kNode = LSPUtil.getKNode(diagramState, uri, action.id) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + + if (node.parentRegion === null) { + this.client.sendMessage("The root may not have a successor.", "error") + return + } + val new_transition = factory.createTransition() + + // Since trigger and effect are given as strings there may be variables that are not inputs / outputs + // also we only allow assignments in effects. + try { + changeTrigger(new_transition, action.trigger, uri) + changeEffect(new_transition, action.effect, uri) + } catch (ValuedObjectNotFoundException ex) { + client.sendMessage("During the parsing of the expression " + action.trigger + " the object: " + ex.message + + " could not be found.", "error") + return + } catch (ExpressionParseException ex) { + client.sendMessage( + "During the parsing of the expression " + action.effect + " the expression: " + ex.message + + " could not be converted to an assignment expression.", "error") + return + } catch (NullPointerException ex) { + client.sendMessage("The expression could not be parsed.", "error") + return + } + + // Since the destination was selected it may be a region or a state outside the parent region of the selected node + try { + val kDest = LSPUtil.getKNode(diagramState, uri, action.destination) + val dest = kDest.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + // need to check if the destination is in the same region as the source + if (dest.parentRegion !== node.parentRegion) { + client.sendMessage("The selected state is not part of the same region.", "error") + return + } + new_transition.sourceState = node + new_transition.targetState = dest + } catch (ClassCastException|NullPointerException ex) { + // Catching the moment when not a state is selected as destination + client.sendMessage("The selected element is not a targetable state.", "error") + return + } + + updateDocument(uri) + } + + /** + * Changes a given transition to a weak transition. + * + * @param action The action to perform. + * @param clientId The id of the frontend client + * @param server The diagram server + */ + def changeToWeak(ChangeToWeakTransitionAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kEdge = LSPUtil.getKEdge(diagramState, uri, action.id) + val edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as Transition + + edge.preemption = PreemptionType.WEAK + + updateDocument(uri) + } + + /** + * Changes a given transition to a terminating transition. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def changeToTerminating(ChangeToTerminatingTransitionAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kEdge = LSPUtil.getKEdge(diagramState, uri, action.id) + val edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as Transition + + edge.preemption = PreemptionType.TERMINATION + + updateDocument(uri) + } + + /** + * Changes a given transition to a aborting transition. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def changeToAbort(ChangeToAbortingTransitionAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kEdge = LSPUtil.getKEdge(diagramState, uri, action.id) + val edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as Transition + + edge.preemption = PreemptionType.STRONG + + updateDocument(uri) + } + + /** + * Changes the trigger and effect of a transition. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def changeIO(ChangeTriggerEffectAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kEdge = LSPUtil.getKEdge(diagramState, uri, action.id) + var edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as Transition + + // currently as workaround we delete the edge and add it again since adjusting trigger and effect directly + // introduces some weird spacing in the updateDocument step + val prio = edge.priority + val target = edge.targetState + val source = edge.sourceState + + val newedge = createTransitionTo(source, target) + + // Since trigger and effect are given as strings there may be variables that are not inputs / outputs + // also we only allow assignments in effects. + try { + changeTrigger(newedge, action.trigger, uri) + changeEffect(newedge, action.effect, uri) + setSpecificPriority(newedge, prio) + } catch (ValuedObjectNotFoundException ex) { + client.sendMessage("During the parsing of the expression " + action.trigger + " the object: " + ex.message + + " could not be found.", "error") + return + } catch (ExpressionParseException ex) { + client.sendMessage( + "During the parsing of the expression " + action.effect + " the expression: " + ex.message + + " could not be converted to an assignment expression.", "error") + return + } catch (NullPointerException ex) { + client.sendMessage("The expression could not be parsed.", "error") + return + } + deleteEdge(kEdge) + + updateDocument(uri) + } + + /** + * Method to change the destination of a given transition. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def changeDestination(ChangeTargetStateAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kEdge = LSPUtil.getKEdge(diagramState, uri, action.id) + val edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as Transition + + // since the destination is selected by the user it may be inside another region or be no state atall + try { + val kNode = LSPUtil.getKNode(diagramState, uri, action.new_target) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + // checking if the selected state is inside the same region as the source + if (node.parentRegion !== edge.targetState.parentRegion) { + client.sendMessage("The selected state is not part of the same region.", "error") + return + } + + edge.targetState.incomingTransitions.remove(edge) + edge.targetState = node + + node.incomingTransitions.add(edge) + } catch (ClassCastException|NullPointerException ex) { + // catching the cases where the selected element is not a state + client.sendMessage("The selected element is not a targetable state.", "error") + return + } + + updateDocument(uri) + } + + /** + * Method to change the source of a given transition. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def changeSource(ChangeSourceStateAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kEdge = LSPUtil.getKEdge(diagramState, uri, action.id) + val edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as Transition + + // we need to check if the selected element is a state and if the selected element is inside the same region + try { + val kNode = LSPUtil.getKNode(diagramState, uri, action.new_source) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + // checking the the target and source are in the same region + if (node.parentRegion !== edge.targetState.parentRegion) { + client.sendMessage("The selected state is not part of the same region.", "error") + return + } + + edge.sourceState.outgoingTransitions.remove(edge) + edge.sourceState = node + + node.outgoingTransitions.add(edge) + } catch (ClassCastException|NullPointerException ex) { + // catching all cases where not a state was selected. + client.sendMessage("The selected element is not a targetable state.", "error") + return + } + + updateDocument(uri) + } + + /** + * Method to add an hierarchical state. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def addHirachicalNode(AddHierarchicalStateAction action, String clientId) { + initCurrentResource(clientId) + // we need to make sure that the new states name is given + if (action.state_name.equals("")) { + this.client.sendMessage("You must provide a state id.", "error") + return + } + val newState = factory.createState() + val state_id = getId(action.state_name) + // we need to make sure the new name obeys the rules for state names. + if (state_id.equals("_") || state_id.equals("")) { + this.client.sendMessage("The state id needs to have atleast one number or letter in it.", "error") + return + } + // we want to display the desired name as label in the graph + if (!state_id.equals(action.state_name)) { + newState.label = action.state_name + } + newState.name = state_id + newState.initial = true + + // Regions follow a laxer definition for id's there may be regions that have no name etc. + val newRegion = factory.createControlflowRegion() + if (!action.region_name.equals("")) { + val region_id = getId(action.region_name) + + if (!region_id.equals(action.region_name)) + newRegion.label = action.region_name + + newRegion.name = region_id + } + + newRegion.states.add(newState) + + val uri = diagramState.getURIString(clientId) + val kNode = LSPUtil.getKNode(diagramState, uri, action.id) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + + node.regions.add(newRegion) + + updateDocument(uri) + } + + /** + * Method to add a concurrent behavior. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def addConcurrentRegion(AddConcurrentRegionAction action, String clientId) { + initCurrentResource(clientId) + + // To make sure there is a name given for the state. + if (action.state_name.equals("")) { + this.client.sendMessage("You must provide a state id.", "error") + return + } + val newState = factory.createState() + val state_id = getId(action.state_name) + // we need to make sure the new name obeys the rules for state names. + if (state_id.equals("_") || state_id.equals("")) { + this.client.sendMessage("The state id needs to have atleast one number or letter in it.", "error") + return + } + // we want to display the desired name as label in the graph + if (!state_id.equals(action.state_name)) { + newState.label = action.state_name + } + newState.name = state_id + newState.initial = true + + // Regions follow a laxer definition for id's there may be regions that have no name etc. + val newRegion = factory.createControlflowRegion() + if (!action.region_name.equals("")) { + val region_id = getId(action.region_name) + + if (!region_id.equals(action.region_name)) + newRegion.label = action.region_name + + if (!(region_id.equals("") || region_id.equals("_"))) + newRegion.name = region_id + } + + newRegion.states.add(newState) + + val uri = diagramState.getURIString(clientId) + val kNode = LSPUtil.getKNode(diagramState, uri, action.id) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) + + if (node instanceof ControlflowRegion) { + (node as ControlflowRegion).parentState.regions.add(newRegion) + } + + updateDocument(uri) + } + + /** + * Method to rename states and regions. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def rename(Action action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + // We need to differentiate between states and regions + if (action.kind === RenameStateAction.KIND) { + val kNode = LSPUtil.getKNode(diagramState, uri, (action as RenameStateAction).id) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) + // we need to make sure the new name obeys the rules for state ids + if ((action as RenameStateAction).state_name.equals("")) { + this.client.sendMessage("You must provide a state id.", "error") + return + } + val state_id = getId((action as RenameStateAction).state_name) + if (state_id.equals("_") || state_id.equals("")) { + this.client.sendMessage("The state id needs to have atleast one number or letter in it.", "error") + return + } + if (!state_id.equals((action as RenameStateAction).state_name)) { + (node as State).label = (action as RenameStateAction).state_name + } + (node as State).name = state_id + + } else if (action.kind === RenameRegionAction.KIND) { + val kNode = LSPUtil.getKNode(diagramState, uri, (action as RenameRegionAction).id) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) + // we need to make sure the new name obeys the rules for region names. + if((action as RenameRegionAction).region_name.equals("")) return + + val region_id = getId((action as RenameRegionAction).region_name) + + if (!region_id.equals((action as RenameRegionAction).region_name)) { + (node as Region).label = (action as RenameRegionAction).region_name + } + + (node as Region).name = region_id + + } + + updateDocument(uri) + } + + /** + * Method to delete elements ie. Regions, States and Transitions. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def delete(DeleteAction action, String clientId) { + initCurrentResource(clientId) + + try { + // for multiple slected elements + val nodes = action.id.split(":"); + for (x : nodes) { + this.deleteSingleElem(x, clientId) + } + } catch (NullPointerException ex) { + // single element was send and should be deleted + this.deleteSingleElem(action.id, clientId) + } + + val uri = diagramState.getURIString(clientId) + + updateDocument(uri) + } + + /** + * Method to add a successor state to a given state. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def addSuccessorState(AddSuccessorStateAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kNode = LSPUtil.getKNode(diagramState, uri, action.id) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + if (node.parentRegion === null) { + this.client.sendMessage("The root may not have a successor.", "error") + return + } + + // we need to make sure a name for the new state is given + if (action.state_name.equals("")) { + this.client.sendMessage("You must provide a state id.", "error") + return + } + val newState = factory.createState() + val state_id = getId(action.state_name) + // we need to make sure the new name obeys the rules for state id's + if (state_id.equals("_") || state_id.equals("")) { + this.client.sendMessage("The state id needs to have atleast one number or letter in it.", "error") + return + } + // we want to display the given name in the graph and have the id only as id + if (!state_id.equals(action.state_name)) { + newState.label = action.state_name + } + newState.name = state_id + + val new_transition = factory.createTransition() + + // the trigger and effect are given as strings and can therefore can be false. + // The Variables may not be initialised or the effect may not be an expression that assigns somthing. + try { + changeTrigger(new_transition, action.trigger, uri) + changeEffect(new_transition, action.effect, uri) + } catch (ValuedObjectNotFoundException ex) { + client.sendMessage("During the parsing of the expression " + action.trigger + " the object: " + ex.message + + " could not be found.", "error") + return + } catch (ExpressionParseException ex) { + client.sendMessage( + "During the parsing of the expression " + action.effect + " the expression: " + ex.message + + " could not be converted to an assignment expression.", "error") + return + } catch (NullPointerException ex) { + client.sendMessage("The expression could not be parsed.", "error") + return + } + + new_transition.sourceState = node + new_transition.targetState = newState + + node.parentRegion.states.add(newState) + + updateDocument(uri) + } + + /** + * Method to toggle a state to be final or not. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def toggleFinalState(ToggleFinalStateAction action, String clientId) { + initCurrentResource(clientId) + + val uri = diagramState.getURIString(clientId) + val kNode = LSPUtil.getKNode(diagramState, uri, action.id) + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + if (node.parentRegion === null) { + this.client.sendMessage("The root may not be final.", "error") + return + } + node.final = !node.final + + updateDocument(uri) + } + + /** + * Send from client to server to indicate that the focused tab should be different. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def editSemanticDeclaration(EditSemanticDeclarationAction action, String clientId) { + val uri = diagramState.getURIString(clientId) + this.client.sendMessage(uri, "switchEditor") + } + + /** + * Makes the desired state initial and changes the old initial state to be normal. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def makeInitialState(MakeInitialStateAction action, String clientId) { + initCurrentResource(clientId) + + // since the action is triggered with the contextmenu for states the action id is the id of a state and thus we can omit the try catch's + val uri = diagramState.getURIString(clientId) + val kNode = LSPUtil.getKNode(diagramState, uri, action.id) + val newInitial = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + if (newInitial.parentRegion === null) { + this.client.sendMessage("The root may not be an initial node.", "error") + return + } + // changes the old initial state to be normal + for (node : newInitial.parentRegion.states) { + if(node.initial) node.initial = false + } + + newInitial.initial = true + + updateDocument(uri) + } + + /** + * Method to change a transitions priority. + * The edge moves up/down the hierarchy and the others are moved up or down by one. + * + * @param action The action to perform. + * @param clientId The id of the client. + */ + def changeEdgePriority(ChangePriorityAction action, String clientId) { + initCurrentResource(clientId) + val uri = diagramState.getURIString(clientId) + val kEdge = LSPUtil.getKEdge(diagramState, uri, action.id) + val edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) + val priority = Integer.parseInt(action.priority) + setSpecificPriority(edge as Transition, priority) + updateDocument(uri) + } + + /** + * Helper method to delete a single element. + * + * @param id The element id. + * @param clientId The id of the client. + */ + def deleteSingleElem(String id, String clientId) { + val uri = diagramState.getURIString(clientId) + // kNode may be states or regions. + val kNode = LSPUtil.getKNode(diagramState, uri, id) + + if (kNode !== null && kNode.parent !== null) { + deleteNode(kNode); + } + // edges are transitions + val kEdge = LSPUtil.getKEdge(diagramState, uri, id) + if (kEdge !== null) { + deleteEdge(kEdge); + } + } + + /** + * Helper method to delete edges by removing the edge from source and target. + * + * @param kEdge The KEdge to delete. + */ + def deleteEdge(KEdge kEdge) { + val edge = kEdge.getProperty(KlighdInternalProperties.MODEL_ELEMENT) + val source = kEdge.source.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + val target = kEdge.target.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + + source.outgoingTransitions.remove(edge) + target.incomingTransitions.remove(edge) + } + + /** + * Helper method to delete nodes or regions. + * + * @param kNode The KNode to delete. + */ + def void deleteNode(KNode kNode) { + val node = kNode.getProperty(KlighdInternalProperties.MODEL_ELEMENT) + + if (node instanceof State) { + // if we delete the last state in a region we instead want to delete the region itself + if (node.parentRegion.states.length === 1) { + node.parentRegion.parentState.regions.remove(node.parentRegion) + return + } + // for all incomming edges we need to delete edge and remove it from the source + for (incommingEdge : kNode.incomingEdges) { + + val source = incommingEdge.source.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + + val transitions = source.getOutgoingTransitions() + val toDelete = newArrayList + + for (transition : transitions) { + if (transition.getTargetState() === node) { + toDelete.add(transition) + } + } + for (transition : toDelete) { + source.outgoingTransitions.remove(transition) + } + + } + // for all outgoing edges we need to delete the edge and remove it from the target + for (outgoingEdge : kNode.outgoingEdges) { + val target = outgoingEdge.target.getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + + val transitions = target.getIncomingTransitions() + val toDelete = newArrayList + + for (transition : transitions) { + if (transition.getSourceState() === node) { + toDelete.add(transition) + } + } + for (transition : toDelete) { + target.incomingTransitions.remove(transition) + } + } + // if the node is initial we want to make any other node initial + if (node.initial) { + if (node.parentRegion.states.get(0) !== node) { + node.parentRegion.states.get(0).initial = true + } else { + node.parentRegion.states.get(1).initial = true + } + + } + // finally we want to remove the node from the region. + node.parentRegion.states.remove(node) + } else { + // in case of regions we want to delete them from the parent state + (node as ControlflowRegion).parentState.regions.remove(node) + } + + } + + /** + * Helper method to change the trigger of an transition. + * + * @param transition The transition + * @param trigger The trigger + * @param uri The uri String + */ + def changeTrigger(Transition transition, String trigger, String uri) { + if (trigger != "") { + // may yield nothing and thus throw a nullpointer exception (Handeled when the mehtod is used) + val newTrigger = KExtStandaloneParser.parseExpression(trigger) + + // The expression may consist of multiple operators + if ((newTrigger instanceof OperatorExpressionImpl)) { + changeTriggerSubExpressions(transition, newTrigger, uri) + + } else if (newTrigger instanceof ValuedObjectReferenceImpl) { + // If there is a valued objectreverence we need to change the reference to the one in the model + (newTrigger as ValuedObjectReferenceImpl).valuedObject = getValuedObjectReference(diagramState, uri, + (newTrigger as ValuedObjectReferenceImpl).valuedObject.name) + } + + transition.trigger = newTrigger + } + } + + /** + * Helper method to change the valued object references recursively for all valued objects. + * + * @param transition The transition + * @param trigger The trigger + * @param uri The uri string + */ + def void changeTriggerSubExpressions(Transition transition, OperatorExpressionImpl trigger, String uri) { + for (exp : trigger.subExpressions) { + // If we have an operator we want to change the subexpressions + if (exp instanceof OperatorExpressionImpl) { + changeTriggerSubExpressions(transition, exp, uri) + + } else if (exp instanceof ValuedObjectReferenceImpl) { + // if we have a object reference we want to change it to the one in the model + exp.valuedObject = getValuedObjectReference(diagramState, uri, exp.valuedObject.name) + } + } + } + + /** + * Helper function to change the effect of a transition. + * + * @param transition The transition + * @param rawEffectString The raw effect string + * @param uri The uri string + */ + def changeEffect(Transition transition, String rawEffectString, String uri) { + // we want to delete any old effects + transition.effects.removeAll(transition.effects) + + if (rawEffectString != "") { + // since there may be multiple assignments in a effect we need to split those up before parsing. + val effectStrings = rawEffectString.split(";") + for (effectString : effectStrings) { + try { + // may be null if no expression could be generated + val effect = KExtStandaloneParser.parseEffect(effectString); + // need to update the valued object since the parser generates dummys + // cast can throw a class cast exception since the user input may be no assingment + (effect as AssignmentImpl).reference.valuedObject = getValuedObjectReference(diagramState, uri, + (effect as AssignmentImpl).reference.valuedObject.name) + transition.effects.add(effect) + } catch (ClassCastException ex) { + // simply rethrown for readability in the catch cases outside of method + throw new ExpressionParseException(effectString) + } + } + } + } + + /** + * Updates the textual representation on the client. + * For now everything is replaced. + * + * @param uri String uri of resource + */ + def updateDocument(String uri) { + val Map> changes = newHashMap + + val resource = getResource(uri); + + val 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), previsionRange) + + val TextEdit textEdit = new TextEdit(range, codeAfter) + changes.put(uri, #[textEdit]); + + this.client.replaceContentInFile(uri, codeAfter, range) + } + + /** + * Removes all characters that can not be in an id from a string and returns the resulting string. + * + * @param name String name to replace characters in + * @reurn The same string instance with deletes characters. + */ + def getId(String name) { + return name.replaceAll("[^a-zA-Z0-9_]", "") + } + + static def ValuedObjectImpl getValuedObjectReference(KGraphDiagramState diagramState, String uri, String name) { + + val root = LSPUtil.getRoot(diagramState, uri) + val node = root.children.get(0).getProperty(KlighdInternalProperties.MODEL_ELEMENT) as State + for (declaration : node.declarations) { + for (obj : declaration.valuedObjects) { + if(obj.name == name) return obj as ValuedObjectImpl + } + } + throw new ValuedObjectNotFoundException(name) + } + +} + +/** + * Simple exception to throw if a valued object could not be found. + */ +class ValuedObjectNotFoundException extends Exception { + + new(String notFound) { + super(notFound) + } +} + +/** + * Simple exception for readability when error arises during the parsing of a expression + */ +class ExpressionParseException extends Exception { + + new(String expression) { + super(expression) + } + +} diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/StateActions.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/StateActions.xtend new file mode 100644 index 0000000000..a31a776816 --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/StateActions.xtend @@ -0,0 +1,299 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +import de.cau.cs.kieler.klighd.structurebasedediting.InputType +import de.cau.cs.kieler.klighd.structurebasedediting.StructureBasedEditingMessage +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 + +/** + * Action received from the client if the window should switch to the code base. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class EditSemanticDeclarationAction implements Action { + public static val LABEL = "Edit sematic declarations" + public static val KIND = 'SCChart_EditSemanticDeclarations' + String kind = KIND + public String id + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + return #[]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + EditSemanticDeclarationAction.LABEL, + EditSemanticDeclarationAction.KIND, + false, + EditSemanticDeclarationAction.getInputs() + ) + } +} + +/** + * Action received from the client if a state should be renamed. + * The given information is the state id and the new name. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class RenameStateAction implements Action { + public static val LABEL = "Rename state" + public static val KIND = 'SCChart_graph_RenameState' + String kind = KIND + + public String id + public String state_name + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("state_name", "String", "New Name"); + return #[input1]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + RenameStateAction.LABEL, + RenameStateAction.KIND, + false, + RenameStateAction.getInputs() + ) + } +} + +/** + * Action received from client if a new transition should be added. + * Given from client are the id of the source node the new destination + * as well as trigger and effect as strings. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class AddTransitionAction implements Action { + public static val LABEL = "Add new transition" + public static val KIND = 'SCChart_graph_AddTransition' + String kind = KIND + + public String id + public String destination + public String trigger + public String effect + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("destination", "SelectTarget", "Destination"); + val input2 = new InputType("trigger", "String", "Trigger"); + val input3 = new InputType("effect", "String", "Effect") + return #[input1, input2, input3]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + AddTransitionAction.LABEL, + AddTransitionAction.KIND, + false, + AddTransitionAction.getInputs() + ) + } +} + +/** + * Action received from client for adding a new successor state. + * the given information is the predecessor node the new nodes name as well as + * trigger and effect + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class AddSuccessorStateAction implements Action { + public static val LABEL = "Add successor state" + public static val KIND = 'SCChart_graph_AddSuccessorState' + String kind = KIND + + public String id + public String state_name + public String trigger + public String effect + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("state_name", "String", "Name of state"); + val input2 = new InputType("trigger", "String", "Trigger"); + val input3 = new InputType("effect", "String", "Effect") + return #[input1, input2, input3]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + AddSuccessorStateAction.LABEL, + AddSuccessorStateAction.KIND, + false, + AddSuccessorStateAction.getInputs() + ) + } +} + +/** + * Action received from the client to add a hirachical behavior to a state. + * Given information is the states id the new regions name and the new states name. + * + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class AddHierarchicalStateAction implements Action { + public static val LABEL = "Add region" + public static val KIND = 'SCChart_graph_AddHierarchicalState' + String kind = KIND + + public String id + public String state_name + public String region_name + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + val input1 = new InputType("state_name", "String", "State Name"); + val input2 = new InputType("region_name", "String", "Region Name"); + return #[input1, input2]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + AddHierarchicalStateAction.LABEL, + AddHierarchicalStateAction.KIND, + false, + AddHierarchicalStateAction.getInputs() + ) + } +} + +/** + * Action received from the client if a state should be made initial. + * Given information is the state id of the state that should be initial. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class MakeInitialStateAction implements Action { + public static val LABEL = "Make initial state" + public static val KIND = 'SCChart_graph_MakeInitialState' + String kind = KIND + + public String id + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + return #[]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + MakeInitialStateAction.LABEL, + MakeInitialStateAction.KIND, + false, + MakeInitialStateAction.getInputs() + ) + } +} + +/** + * Action received to toggle a state to a final state or back. + * Given information is the state id which should be toggled. + */ +@Accessors +@EqualsHashCode +@ToString(skipNulls=true) +class ToggleFinalStateAction implements Action { + public static val LABEL = "Toggle final state" + public static val KIND = 'SCChart_graph_MakeFinalState' + String kind = KIND + + public String id + + new() { + } + + new(Consumer initializer) { + initializer.accept(this) + } + + /* Returns the array of inputs requested from the user to perform the action. */ + def static InputType[] getInputs() { + return #[]; + } + + /* Used in the synthesis to append the supported actions to the root node for the use on the client. */ + def static StructureBasedEditingMessage getMsg() { + return new StructureBasedEditingMessage( + ToggleFinalStateAction.LABEL, + ToggleFinalStateAction.KIND, + false, + ToggleFinalStateAction.getInputs() + ) + } +} \ No newline at end of file diff --git a/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/StructureBasedEditingHook.xtend b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/StructureBasedEditingHook.xtend new file mode 100644 index 0000000000..277810acbb --- /dev/null +++ b/language-server/de.cau.cs.kieler.language.server/src/de/cau/cs/kieler/language/server/sccharts/structurebasedediting/StructureBasedEditingHook.xtend @@ -0,0 +1,82 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2024 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.language.server.sccharts.structurebasedediting + +import de.cau.cs.kieler.klighd.kgraph.KNode +import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared +import de.cau.cs.kieler.klighd.structurebasedediting.StructureBasedEditingOptions +import de.cau.cs.kieler.klighd.util.KlighdProperties +import de.cau.cs.kieler.sccharts.Scope +import de.cau.cs.kieler.sccharts.ui.synthesis.filtering.SCChartsSemanticFilterTags +import de.cau.cs.kieler.sccharts.ui.synthesis.hooks.SynthesisHook +import java.util.HashMap + +/** + * Class for adding a property to the root state of a scchart diagram + */ +@ViewSynthesisShared +class StructureBasedEditingHook extends SynthesisHook { + + override finish(Scope scope, KNode node) { + val map = new HashMap() + + // all states support the following actions + map.put(SCChartsSemanticFilterTags.STATE, #[ + EditSemanticDeclarationAction.getMsg(), + RenameStateAction.getMsg(), + AddSuccessorStateAction.getMsg(), + AddHierarchicalStateAction.getMsg(), + AddTransitionAction.getMsg(), + ToggleFinalStateAction.getMsg(), + DeleteAction.getMsg() + ]) + + // Non initial states support one more action + map.put(SCChartsSemanticFilterTags.NOT_INITIAL_STATE, #[ + MakeInitialStateAction.getMsg() + ]) + + // Transitions supported actions + map.put(SCChartsSemanticFilterTags.TRANSITION, #[ + ChangeTargetStateAction.getMsg(), + ChangeSourceStateAction.getMsg(), + ChangeTriggerEffectAction.getMsg(), + DeleteAction.getMsg(), + ChangePriorityAction.getMsg() + ]) + // Depending on the type of transition we can change it to the other two options + map.put(SCChartsSemanticFilterTags.WEAK_TRANSITION, #[ + ChangeToTerminatingTransitionAction.getMsg(), + ChangeToAbortingTransitionAction.getMsg() + ]) + map.put(SCChartsSemanticFilterTags.ABORTING_TRANSITION, #[ + ChangeToWeakTransitionAction.getMsg(), + ChangeToTerminatingTransitionAction.getMsg() + ]) + map.put(SCChartsSemanticFilterTags.TERMINATING_TRANSITION, #[ + ChangeToWeakTransitionAction.getMsg(), + ChangeToAbortingTransitionAction.getMsg() + ]) + + // Actions supported by regions + map.put(SCChartsSemanticFilterTags.REGION, #[ + RenameRegionAction.getMsg(), + AddConcurrentRegionAction.getMsg(), + DeleteAction.getMsg() + ]) + + val options = new StructureBasedEditingOptions(map) + // sets the property for the root node + node.setProperty(KlighdProperties.STRUCTURED_EDITING, options) + } +} \ No newline at end of file diff --git a/plugins/de.cau.cs.kieler.sccharts.ide/META-INF/MANIFEST.MF b/plugins/de.cau.cs.kieler.sccharts.ide/META-INF/MANIFEST.MF index bb71375fbd..1d8b265266 100644 --- a/plugins/de.cau.cs.kieler.sccharts.ide/META-INF/MANIFEST.MF +++ b/plugins/de.cau.cs.kieler.sccharts.ide/META-INF/MANIFEST.MF @@ -19,7 +19,8 @@ Require-Bundle: org.antlr.runtime;bundle-version="[3.2.0,3.2.1)", de.cau.cs.kieler.simulation.ide, de.cau.cs.kieler.scl.ide, com.google.gson, - de.cau.cs.kieler.kicool.ide + de.cau.cs.kieler.kicool.ide, + org.eclipse.lsp4j Export-Package: de.cau.cs.kieler.sccharts.ide.simulation, de.cau.cs.kieler.sccharts.ide.synthesis, de.cau.cs.kieler.sccharts.ide.text.contentassist.antlr, diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/ControlflowRegionSynthesis.xtend b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/ControlflowRegionSynthesis.xtend index 06facd8182..3cb7a0ae80 100644 --- a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/ControlflowRegionSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/ControlflowRegionSynthesis.xtend @@ -32,9 +32,11 @@ import de.cau.cs.kieler.sccharts.State import de.cau.cs.kieler.sccharts.extensions.SCChartsScopeExtensions import de.cau.cs.kieler.sccharts.extensions.SCChartsSerializeHRExtensions import de.cau.cs.kieler.sccharts.ui.synthesis.actions.ReferenceExpandAction +import de.cau.cs.kieler.sccharts.ui.synthesis.filtering.SCChartsSemanticFilterTags import de.cau.cs.kieler.sccharts.ui.synthesis.hooks.actions.MemorizingExpandCollapseAction import de.cau.cs.kieler.sccharts.ui.synthesis.styles.ColorStore import de.cau.cs.kieler.sccharts.ui.synthesis.styles.ControlflowRegionStyles +import de.cau.cs.kieler.sccharts.ui.synthesis.styles.ProxyStyles import java.util.EnumSet import org.eclipse.elk.alg.layered.options.CenterEdgeLabelPlacementStrategy import org.eclipse.elk.alg.layered.options.FixedAlignment @@ -76,12 +78,18 @@ class ControlflowRegionSynthesis extends SubSynthesis override performTranformation(ControlflowRegion region) { val node = region.createNode().associateWith(region); + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).addAll( + SCChartsSemanticFilterTags.REGION, + SCChartsSemanticFilterTags.CONTROLFLOW_REGION + ) + val proxy = createNode().associateWith(region) node.configureNodeLOD(region) // Set KIdentifier for use with incremental update if (!region.name.nullOrEmpty) { node.KID = region.name + proxy.KID = '''«region.name»-proxy''' } if (USE_KLAY.booleanValue) { @@ -195,19 +203,36 @@ class ControlflowRegionSynthesis extends SubSynthesis ] } else { node.addRegionFigure => [ - if (region.override) addOverrideRegionStyle - if (region.abort) addAbortRegionStyle - if (region.final) addFinalRegionStyle + addCorrespondingRegionFigure(region) ] } + + proxy.addRegionFigure => [ + addCorrespondingRegionFigure(region) + val label = region.serializeHighlighted(true) + if (label.length > 0) { + val name = label.get(0) + if (name.key.length > ProxyStyles.MAX_PROXY_LABEL_LENGTH) { + label.set(0, new Pair(name.key.subSequence(0, ProxyStyles.MAX_PROXY_LABEL_LENGTH) + "...", name.value)) + } + } + addRegionLabel(label) + ] val returnNodes = newArrayList(node) if (SHOW_COMMENTS.booleanValue) { region.getCommentAnnotations.forEach[ - node.children += it.transform + val comments = it.transform + node.children += comments + // Comments shouldn't be rendered as proxies + comments.forEach[ + setProperty(KlighdProperties.PROXY_VIEW_RENDER_NODE_AS_PROXY, false) + ] ] - } + } + + ProxyStyles.setProxySize(node, proxy) return returnNodes } diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/DataflowRegionSynthesis.xtend b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/DataflowRegionSynthesis.xtend index 99c2797eaf..bd4528692c 100644 --- a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/DataflowRegionSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/DataflowRegionSynthesis.xtend @@ -22,13 +22,17 @@ import de.cau.cs.kieler.klighd.kgraph.KNode import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions +import de.cau.cs.kieler.klighd.microlayout.PlacementUtil import de.cau.cs.kieler.klighd.util.KlighdProperties import de.cau.cs.kieler.sccharts.DataflowRegion import de.cau.cs.kieler.sccharts.extensions.SCChartsSerializeHRExtensions import de.cau.cs.kieler.sccharts.extensions.TextFormat import de.cau.cs.kieler.sccharts.ui.synthesis.actions.ReferenceExpandAction +import de.cau.cs.kieler.sccharts.ui.synthesis.filtering.SCChartsSemanticFilterTags import de.cau.cs.kieler.sccharts.ui.synthesis.hooks.actions.MemorizingExpandCollapseAction import de.cau.cs.kieler.sccharts.ui.synthesis.styles.DataflowRegionStyles +import de.cau.cs.kieler.sccharts.ui.synthesis.styles.ProxyStyles +import de.cau.cs.kieler.sccharts.ui.synthesis.styles.StateStyles import org.eclipse.elk.alg.layered.options.GreedySwitchType import org.eclipse.elk.alg.layered.options.LayeredOptions import org.eclipse.elk.alg.layered.options.NodePlacementStrategy @@ -80,6 +84,11 @@ class DataflowRegionSynthesis extends SubSynthesis { override performTranformation(DataflowRegion region) { val node = region.createNode().associateWith(region) + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).addAll( + SCChartsSemanticFilterTags.REGION, + SCChartsSemanticFilterTags.DATAFLOW_REGION + ) + val proxy = createNode().associateWith(region) node.addLayoutParam(CoreOptions::ALGORITHM, LayeredOptions.ALGORITHM_ID) //node.setLayoutOption(LayeredOptions.CONSIDER_MODEL_ORDER, OrderingStrategy.PREFER_EDGES) @@ -173,11 +182,31 @@ class DataflowRegionSynthesis extends SubSynthesis { ] ] + proxy.addRegionFigure => [ + if (sLabel.length > 0) it.setUserScheduleStyle + if (region.override) addOverrideRegionStyle + if (!CIRCUIT.booleanValue) { + if (label.length > 0) { + val name = label.get(0) + if (name.key.length > ProxyStyles.MAX_PROXY_LABEL_LENGTH) { + label.set(0, new Pair(name.key.subSequence(0, ProxyStyles.MAX_PROXY_LABEL_LENGTH) + "...", name.value)) + } + } + addRegionLabel(label) + } + ] + node.setSelectionStyle + proxy.setSelectionStyle if (SHOW_COMMENTS.booleanValue) { region.getCommentAnnotations.forEach[ - node.children += it.transform + val comments = it.transform + node.children += comments + // Comments shouldn't be rendered as proxies + comments.forEach[ + setProperty(KlighdProperties.PROXY_VIEW_RENDER_NODE_AS_PROXY, false) + ] ] } @@ -187,6 +216,8 @@ class DataflowRegionSynthesis extends SubSynthesis { if (!CIRCUIT.booleanValue) { node.setLayoutOption(CoreOptions::PADDING, new ElkPadding(18d, 7d, 7d, 7d)); } + + ProxyStyles.setProxySize(node, proxy) return newArrayList(node) } diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/SCChartsSynthesis.xtend b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/SCChartsSynthesis.xtend index accdc3b81b..185c9493fa 100644 --- a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/SCChartsSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/SCChartsSynthesis.xtend @@ -55,6 +55,10 @@ import org.eclipse.elk.graph.properties.IProperty import org.eclipse.elk.graph.properties.Property import static de.cau.cs.kieler.sccharts.ui.synthesis.GeneralSynthesisOptions.* +import de.cau.cs.kieler.klighd.filtering.SemanticFilterRule +import de.cau.cs.kieler.klighd.filtering.SemanticFilterTag +import de.cau.cs.kieler.klighd.KlighdOptions +import de.cau.cs.kieler.sccharts.ui.synthesis.filtering.SCChartsSemanticFilterRules /** * Main diagram synthesis for SCCharts. @@ -167,6 +171,9 @@ class SCChartsSynthesis extends AbstractDiagramSynthesis { } else sccharts val rootNode = createNode + + // Set semantic filter rules + rootNode.setProperty(KlighdProperties.SEMANTIC_FILTER_RULES, SCChartsSemanticFilterRules.allFilters) // If dot is used draw edges first to prevent overlapping with states when layout is bad usedContext.setProperty(KlighdProperties.EDGES_FIRST, !USE_KLAY.booleanValue) @@ -351,13 +358,23 @@ class SCChartsSynthesis extends AbstractDiagramSynthesis { override List getAdditionalLayoutConfigs(KNode viewModel) { val List additionalLayoutRuns = newArrayList // Add interactive Layout run. - if ((!viewModel.getChildren().isEmpty() && viewModel.getChildren().get(0) - .getProperty(CoreOptions.INTERACTIVE_LAYOUT))) { + if (!viewModel.getChildren().isEmpty() && (viewModel.getChildren().get(0) + .getProperty(CoreOptions.INTERACTIVE_LAYOUT) || isChildInteractive(viewModel))) { additionalLayoutRuns.add(new CompoundGraphElementVisitor( new InteractiveRectPackingGraphVisitor(), new InteractiveLayeredGraphVisitor())); } return additionalLayoutRuns; } + + private def isChildInteractive(KNode node) { + val children = node.getChildren.get(0).getChildren() + for (n : children) { + if (n.getProperty(CoreOptions.INTERACTIVE_LAYOUT)) { + return true + } + } + return false + } } diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/StateSynthesis.xtend b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/StateSynthesis.xtend index 98d2506a9a..f9f654ec9b 100644 --- a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/StateSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/StateSynthesis.xtend @@ -45,6 +45,8 @@ import de.cau.cs.kieler.klighd.krendering.extensions.KEdgeExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KPolylineExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions +import de.cau.cs.kieler.klighd.microlayout.PlacementUtil +import de.cau.cs.kieler.klighd.util.KlighdProperties import de.cau.cs.kieler.sccharts.Action import de.cau.cs.kieler.sccharts.ControlflowRegion import de.cau.cs.kieler.sccharts.DataflowRegion @@ -66,7 +68,9 @@ import de.cau.cs.kieler.sccharts.processors.dataflow.ControlDependencies import de.cau.cs.kieler.sccharts.processors.dataflow.RegionDependencies import de.cau.cs.kieler.sccharts.processors.dataflow.RegionLCAFMap import de.cau.cs.kieler.sccharts.processors.dataflow.StateDependencies +import de.cau.cs.kieler.sccharts.ui.synthesis.filtering.SCChartsSemanticFilterTags import de.cau.cs.kieler.sccharts.ui.synthesis.hooks.actions.ToggleDependencyAction +import de.cau.cs.kieler.sccharts.ui.synthesis.styles.ProxyStyles import de.cau.cs.kieler.sccharts.ui.synthesis.styles.StateStyles import de.cau.cs.kieler.scl.MethodImplementationDeclaration import java.util.ArrayList @@ -88,6 +92,7 @@ import static de.cau.cs.kieler.sccharts.ui.synthesis.GeneralSynthesisOptions.* import static extension de.cau.cs.kieler.annotations.ide.klighd.CommonSynthesisUtil.* import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* +import de.cau.cs.kieler.kexpressions.VariableDeclaration /** * Transforms {@link State} into {@link KNode} diagram elements. @@ -140,10 +145,13 @@ class StateSynthesis extends SubSynthesis { override List performTranformation(State state) { val node = state.createNode().associateWith(state) + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).add(SCChartsSemanticFilterTags.STATE) + val proxy = createNode().associateWith(state) // Set KIdentifier for use with incremental update if (!state.name.nullOrEmpty) { node.KID = state.name + proxy.KID = '''«state.name»-proxy''' } // configure region dependency layout config if an appropriate result is present. @@ -164,38 +172,56 @@ class StateSynthesis extends SubSynthesis { // Basic state style switch state { - case isConnector: + case isConnector: { node.addConnectorFigure - case state.isMacroState: + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).add(SCChartsSemanticFilterTags.CONNECTOR_STATE) + proxy.addConnectorFigure + } + case state.isMacroState: { node.addMacroFigure - default: + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).add(SCChartsSemanticFilterTags.HIERARCHICAL_STATE) + proxy.addMacroFigure + } + default: { node.addDefaultFigure + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).add(SCChartsSemanticFilterTags.SIMPLE_STATE) + // Proxy should be more square-like + proxy.addMacroFigure + } } // Styles from modifiers if (state.isReferencing) { node.setReferencedStyle + proxy.setReferencedStyle } if (state.isInitial) { node.setInitialStyle + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).add(SCChartsSemanticFilterTags.INITIAL) + proxy.setInitialStyle if (USE_KLAY.booleanValue && state.parentRegion.states.head == state) { node.setLayoutOption(LayeredOptions::LAYERING_LAYER_CONSTRAINT, LayerConstraint::FIRST); } } if (state.isFinal) { node.setFinalStyle + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).add(SCChartsSemanticFilterTags.FINAL) + proxy.setFinalStyle } if (state.isViolation) { val isHaltState = state.outgoingTransitions.size == 0 || !state.outgoingTransitions.exists[ targetState != state ] node.setViolationStyle(isHaltState) + proxy.setViolationStyle(isHaltState) } node.setSelectionStyle + proxy.setSelectionStyle // Shadow if (!isConnector) { node.setShadowStyle + proxy.setShadowStyle } // Add content @@ -241,10 +267,22 @@ class StateSynthesis extends SubSynthesis { } } node.addMacroStateLabel(label) + if (label.length > 0) { + val name = label.get(0) + if (name.key.length > ProxyStyles.MAX_PROXY_LABEL_LENGTH) { + label.set(0, new Pair(name.key.subSequence(0, ProxyStyles.MAX_PROXY_LABEL_LENGTH) + "...", name.value)) + } + } + proxy.addMacroStateLabel(label) } else { - node.addSimpleStateLabel(state.serializeHR.toString) + val label = state.serializeHR.toString + node.addSimpleStateLabel(label) + if (label.length > ProxyStyles.MAX_PROXY_LABEL_LENGTH) { + proxy.addSimpleStateLabel(label.substring(0, ProxyStyles.MAX_PROXY_LABEL_LENGTH) + "...") + } else { + proxy.addSimpleStateLabel(label) + } }) => [ - setProperty(TracingVisualizationProperties.TRACING_NODE, true) associateWith(state) if (it instanceof KText) configureTextLOD(state) eAllContents.filter(KRendering).toList.forEach[ @@ -254,12 +292,14 @@ class StateSynthesis extends SubSynthesis { ] } else { node.addEmptyStateLabel + proxy.addEmptyStateLabel } // Add declarations val declarations = new ArrayList(state.declarations) if (SHOW_INHERITANCE.booleanValue) declarations.addAll(0, state.allVisibleInheritedDeclarations.toList) - for (declaration : declarations.filter[!(it instanceof MethodImplementationDeclaration) || !SHOW_METHOD_BODY.booleanValue]) { + val filteredDeclarations = declarations.filter[!(it instanceof MethodImplementationDeclaration) || !SHOW_METHOD_BODY.booleanValue] + for (declaration : filteredDeclarations) { if (declaration instanceof ClassDeclaration) { node.addStructDeclarations(declaration, 0) } else { @@ -272,7 +312,46 @@ class StateSynthesis extends SubSynthesis { ] ] } - } + } + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).add( + SCChartsSemanticFilterTags.DECLARATIONS( + filteredDeclarations + .size as double + ) + ) + + // Set declaration tags + var numInput = 0 + var numOutput = 0 + var numStatic = 0 + var numSignal = 0 + var numConst = 0 + var numExtern = 0 + var numVolatile = 0 + var numGlobal = 0 + for (declaration : filteredDeclarations) { + if (declaration instanceof VariableDeclaration) { + // Note that a declaration may have an arbitrary combination of these + if (declaration.input) numInput++ + if (declaration.output) numOutput++ + if (declaration.static) numStatic++ + if (declaration.signal) numSignal++ + if (declaration.const) numConst++ + if (declaration.extern) numExtern++ + if (declaration.volatile) numVolatile++ + if (declaration.global) numGlobal++ + } + } + node.getProperty(KlighdProperties.SEMANTIC_FILTER_TAGS).addAll( + SCChartsSemanticFilterTags.INPUT_DECLARATIONS(numInput as double), + SCChartsSemanticFilterTags.OUTPUT_DECLARATIONS(numOutput as double), + SCChartsSemanticFilterTags.STATIC_DECLARATIONS(numStatic as double), + SCChartsSemanticFilterTags.SIGNAL_DECLARATIONS(numSignal as double), + SCChartsSemanticFilterTags.CONST_DECLARATIONS(numConst as double), + SCChartsSemanticFilterTags.EXTERN_DECLARATIONS(numExtern as double), + SCChartsSemanticFilterTags.VOLATILE_DECLARATIONS(numVolatile as double), + SCChartsSemanticFilterTags.GLOBAL_DECLARATIONS(numGlobal as double) + ) // Add actions val actions = new ArrayList(state.actions) @@ -353,9 +432,17 @@ class StateSynthesis extends SubSynthesis { if (SHOW_COMMENTS.booleanValue) { state.getCommentAnnotations.forEach[ - returnNodes += it.transform - ] - } + val comments = it.transform + returnNodes += comments + // Comments shouldn't be rendered as proxies + comments.forEach[ + setProperty(KlighdProperties.PROXY_VIEW_RENDER_NODE_AS_PROXY, false) + ] + ] + } + + node.setProperty(KlighdProperties.PROXY_VIEW_RENDER_NODE_AS_PROXY, true) + node.setProperty(KlighdProperties.PROXY_VIEW_PROXY_RENDERING, proxy.data) return returnNodes } diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/filtering/SCChartsSemanticFilterRules.java b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/filtering/SCChartsSemanticFilterRules.java new file mode 100644 index 0000000000..5aaae96587 --- /dev/null +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/filtering/SCChartsSemanticFilterRules.java @@ -0,0 +1,80 @@ +/* + * 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.sccharts.ui.synthesis.filtering; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import de.cau.cs.kieler.klighd.filtering.AndConnective; +import de.cau.cs.kieler.klighd.filtering.NegationConnective; +import de.cau.cs.kieler.klighd.filtering.SemanticFilterRule; + +/** + * Contains semantic filter rules for SCCharts. + * + * @author tik + */ +public abstract class SCChartsSemanticFilterRules { + + /** Returns all semantic filter rules defined in this class. */ + public static List getAllFilters() { + Field[] fields = SCChartsSemanticFilterRules.class.getFields(); + List filters = new ArrayList<>(fields.length); + // Map the fields to the actual rules + for (Field f : fields) { + try { + filters.add((SemanticFilterRule) f.get(null)); + } catch (IllegalArgumentException | IllegalAccessException e) { + // Neither of these should ever occur as + // 1. the fields are static, i.e. no instance is needed + // 2. the fields are accessible by this class + } + } + return filters; + } + + // The descriptions should utilize the checkbox as the verb + // The filters exclude element types, but on the UI of the client the checkbox will be inverted i.e. indicating + // that the user excludes these elements by unchecking the box + + /** Rule to exclude elements that are states. */ + public static final SemanticFilterRule SHOW_STATES = + new NegationConnective(SCChartsSemanticFilterTags.STATE, true, "All States"); + /** Rule to exclude elements that are regions. */ + public static final SemanticFilterRule SHOW_REGIONS = + new NegationConnective(SCChartsSemanticFilterTags.REGION, true, "All Regions"); + + /** Rule to exclude elements that are simple states. */ + public static final SemanticFilterRule SHOW_SIMPLE_STATE = + new NegationConnective(SCChartsSemanticFilterTags.SIMPLE_STATE, true, "Simple States"); + /** Rule to exclude elements that are hierarchical states. */ + public static final SemanticFilterRule SHOW_HIERARCHICAL_STATE = new NegationConnective( + SCChartsSemanticFilterTags.HIERARCHICAL_STATE, true, "Hierarchical States"); + /** Rule to exclude elements that are connector states. */ + public static final SemanticFilterRule SHOW_CONNECTOR_STATE = new NegationConnective( + SCChartsSemanticFilterTags.CONNECTOR_STATE, false, "Connector States"); + /** Rule to exclude elements that are controlflow regions. */ + public static final SemanticFilterRule SHOW_CONTROLFLOW_REGION = new NegationConnective( + SCChartsSemanticFilterTags.CONTROLFLOW_REGION, true, "Controlflow Regions"); + /** Rule to exclude elements that are dataflow regions. */ + public static final SemanticFilterRule SHOW_DATAFLOW_REGION = new NegationConnective( + SCChartsSemanticFilterTags.DATAFLOW_REGION, true, "Dataflow Regions"); + + /** Rule to exclude elements that are initial states. */ + public static final SemanticFilterRule SHOW_INITIAL_STATE = new NegationConnective( + SCChartsSemanticFilterTags.INITIAL, true, "Initial States"); + /** Rule to exclude elements that are final states. */ + public static final SemanticFilterRule SHOW_FINAL_STATE = new NegationConnective( + SCChartsSemanticFilterTags.FINAL, true, "Final States"); +} diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/filtering/SCChartsSemanticFilterTags.java b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/filtering/SCChartsSemanticFilterTags.java new file mode 100644 index 0000000000..ef2249b198 --- /dev/null +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/filtering/SCChartsSemanticFilterTags.java @@ -0,0 +1,93 @@ +/* + * 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.sccharts.ui.synthesis.filtering; + +import de.cau.cs.kieler.klighd.filtering.SemanticFilterTag; + +/** + * Contains semantic filter tags for SCCharts. + * + * @author tik + */ +public abstract class SCChartsSemanticFilterTags { + /** Tag giving semantic meaning that the element is a state. */ + public static final SemanticFilterTag STATE = new SemanticFilterTag("state"); + /** Tag giving semantic meaning that the element is a region. */ + public static final SemanticFilterTag REGION = new SemanticFilterTag("region"); + + /** Tag giving semantic meaning that the element is a simple state. */ + public static final SemanticFilterTag SIMPLE_STATE = new SemanticFilterTag("simpleState"); + /** Tag giving semantic meaning that the element is a hierarchical state. */ + public static final SemanticFilterTag HIERARCHICAL_STATE = + new SemanticFilterTag("hierarchicalState"); + /** Tag giving semantic meaning that the element is a connector state. */ + public static final SemanticFilterTag CONNECTOR_STATE = new SemanticFilterTag("connectorState"); + /** Tag giving semantic meaning that the element is a controlflow region. */ + public static final SemanticFilterTag CONTROLFLOW_REGION = + new SemanticFilterTag("controlflowRegion"); + /** Tag giving semantic meaning that the element is a dataflow region. */ + public static final SemanticFilterTag DATAFLOW_REGION = new SemanticFilterTag("dataflowRegion"); + + /** Tag giving semantic meaning that the element is a transition. */ + public static final SemanticFilterTag TRANSITION = new SemanticFilterTag("transition"); + /** Tag giving semantic meaning that the element is a transition. */ + public static final SemanticFilterTag WEAK_TRANSITION = new SemanticFilterTag("weakTransition"); + /** Tag giving semantic meaning that the element is a transition. */ + public static final SemanticFilterTag TERMINATING_TRANSITION = new SemanticFilterTag("terminatingTransition"); + /** Tag giving semantic meaning that the element is a transition. */ + public static final SemanticFilterTag ABORTING_TRANSITION = new SemanticFilterTag("abortingTransition"); + + /** Tag giving semantic meaning that the element is initial. */ + public static final SemanticFilterTag INITIAL = new SemanticFilterTag("initial"); + /** Tag giving semantic meaning that the element is a dataflow region. */ + public static final SemanticFilterTag NOT_INITIAL_STATE = new SemanticFilterTag("notInitialState"); + /** Tag giving semantic meaning that the element is final. */ + public static final SemanticFilterTag FINAL = new SemanticFilterTag("final"); + + /** Returns a tag giving semantic meaning how many declarations an element has. */ + public static SemanticFilterTag DECLARATIONS(Double num) { + return new SemanticFilterTag("numDeclarations", num); + } + /** Returns a tag giving semantic meaning how many input declarations an element has. */ + public static SemanticFilterTag INPUT_DECLARATIONS(Double num) { + return new SemanticFilterTag("numInputDeclarations", num); + } + /** Returns a tag giving semantic meaning how many output declarations an element has. */ + public static SemanticFilterTag OUTPUT_DECLARATIONS(Double num) { + return new SemanticFilterTag("numOutputDeclarations", num); + } + /** Returns a tag giving semantic meaning how many static declarations an element has. */ + public static SemanticFilterTag STATIC_DECLARATIONS(Double num) { + return new SemanticFilterTag("numStaticDeclarations", num); + } + /** Returns a tag giving semantic meaning how many signal declarations an element has. */ + public static SemanticFilterTag SIGNAL_DECLARATIONS(Double num) { + return new SemanticFilterTag("numSignalDeclarations", num); + } + /** Returns a tag giving semantic meaning how many const declarations an element has. */ + public static SemanticFilterTag CONST_DECLARATIONS(Double num) { + return new SemanticFilterTag("numConstDeclarations", num); + } + /** Returns a tag giving semantic meaning how many extern declarations an element has. */ + public static SemanticFilterTag EXTERN_DECLARATIONS(Double num) { + return new SemanticFilterTag("numExternDeclarations", num); + } + /** Returns a tag giving semantic meaning how many volatile declarations an element has. */ + public static SemanticFilterTag VOLATILE_DECLARATIONS(Double num) { + return new SemanticFilterTag("numVolatileDeclarations", num); + } + /** Returns a tag giving semantic meaning how many global declarations an element has. */ + public static SemanticFilterTag GLOBAL_DECLARATIONS(Double num) { + return new SemanticFilterTag("numGlobalDeclarations", num); + } +} diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/ControlflowRegionStyles.xtend b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/ControlflowRegionStyles.xtend index e7a2e85109..5e68ab9050 100644 --- a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/ControlflowRegionStyles.xtend +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/ControlflowRegionStyles.xtend @@ -18,6 +18,7 @@ import de.cau.cs.kieler.klighd.kgraph.KNode import de.cau.cs.kieler.klighd.krendering.KContainerRendering import de.cau.cs.kieler.klighd.krendering.KForeground import de.cau.cs.kieler.klighd.krendering.KGridPlacement +import de.cau.cs.kieler.klighd.krendering.KPolygon import de.cau.cs.kieler.klighd.krendering.KRectangle import de.cau.cs.kieler.klighd.krendering.KRendering import de.cau.cs.kieler.klighd.krendering.KText @@ -131,27 +132,39 @@ class ControlflowRegionStyles { } /** - * Adds a button with text. + * Adds a button with text and a label. */ private def KRendering addRegionButton(KContainerRendering container, String text, List> label) { - val button = container.addPolygon => [ - lineWidth = 0 - background = container.foreground.color.copy - selectionBackground = SELECTION.color - addKPosition(LEFT, 0.5f, 0, TOP, 0.5f, 0) - addKPosition(LEFT, 0.5f, 0, TOP, 19, 0) - addKPosition(LEFT, 18, 0, TOP, 0.5f, 0) - ] - button.addText(text) => [ - suppressSelectability - foreground = REGION_BUTTON_FOREGROUND.color - selectionForeground = REGION_BUTTON_FOREGROUND.color - fontSize = 8; - fontBold = true - val size = estimateTextSize; - setPointPlacementData(LEFT, if (text.equals("-")) 3f else 2f, 0, TOP, 0, 0, H_LEFT, V_TOP, 0, 0, size.width, size.height); - ] + return addRegionButton(container, text, label, false) + } + + /** + * Adds a button with text and a label. + * For a proxy, only adds the label and no button. + */ + private def KRendering addRegionButton(KContainerRendering container, String text, List> label, boolean proxy) { + var KPolygon button = null + if (!proxy) { + button = container.addPolygon => [ + lineWidth = 0 + background = container.foreground.color.copy + selectionBackground = SELECTION.color + addKPosition(LEFT, 0.5f, 0, TOP, 0.5f, 0) + addKPosition(LEFT, 0.5f, 0, TOP, 19, 0) + addKPosition(LEFT, 18, 0, TOP, 0.5f, 0) + ] + button.addText(text) => [ + suppressSelectability + foreground = REGION_BUTTON_FOREGROUND.color + selectionForeground = REGION_BUTTON_FOREGROUND.color + fontSize = 8; + fontBold = true + val size = estimateTextSize; + setPointPlacementData(LEFT, if (text.equals("-")) 3f else 2f, 0, TOP, 0, 0, H_LEFT, V_TOP, 0, 0, size.width, size.height); + ] + } if (!label.nullOrEmpty) { + val absLeftOffset = if(proxy) 1 else 14; if (label.size == 1 && label.head.value == TextFormat.TEXT) { container.addText(label.head.key.toString) => [ suppressSelectability @@ -159,14 +172,14 @@ class ControlflowRegionStyles { fontSize = 10; selectionTextUnderline = Underline.NONE // prevents default selection style val size = estimateTextSize; - setPointPlacementData(LEFT, 14, 0, TOP, 1, 0, H_LEFT, V_TOP, 0, 0, size.width + 5, size.height) + setPointPlacementData(LEFT, absLeftOffset, 0, TOP, 1, 0, H_LEFT, V_TOP, 0, 0, size.width + 5, size.height) setProperty(KlighdProperties.IS_NODE_TITLE, true) ] } else { container.addKeywordLabel(label, 0) => [ foreground = REGION_LABEL.color fontSize = 10 - setPointPlacementData(LEFT, 14, 0, TOP, 1, 0, H_LEFT, V_TOP, 0, 0, 0, 0) + setPointPlacementData(LEFT, absLeftOffset, 0, TOP, 1, 0, H_LEFT, V_TOP, 0, 0, 0, 0) setProperty(KlighdProperties.IS_NODE_TITLE, true) (children.last as KContainerRendering) => [ // Just for spacing at the end val grid = it?.getChildPlacement() @@ -191,6 +204,16 @@ class ControlflowRegionStyles { return button } + /** + * Adds the corresponding region figure to the node, + * taking its supposed style into account. + */ + def void addCorrespondingRegionFigure(KRectangle rect, ControlflowRegion region) { + if (region.override) rect.addOverrideRegionStyle + if (region.abort) rect.addAbortRegionStyle + if (region.final) rect.addFinalRegionStyle + } + /** * Adds an expand region button with label. */ @@ -216,6 +239,13 @@ class ControlflowRegionStyles { def KRendering addCollapseButton(KContainerRendering container, String label) { return container.addRegionButton("-", newArrayList(new Pair(label, TextFormat.TEXT))) } + + /** + * Adds a region with a label and no button. + */ + def KRendering addRegionLabel(KContainerRendering container, List> label) { + return container.addRegionButton(null, label, true) + } /** * Adds an area for inner states.
diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/ProxyStyles.xtend b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/ProxyStyles.xtend new file mode 100644 index 0000000000..006d57fd0d --- /dev/null +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/ProxyStyles.xtend @@ -0,0 +1,47 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 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.sccharts.ui.synthesis.styles + +import de.cau.cs.kieler.klighd.kgraph.KNode +import de.cau.cs.kieler.klighd.microlayout.PlacementUtil +import de.cau.cs.kieler.klighd.util.KlighdProperties + +/** + * @author mka + * + */ +class ProxyStyles { + + /** The maximum number of characters to be shown in a proxy label. */ + public static final int MAX_PROXY_LABEL_LENGTH = 5 + + /** Minimum size of proxies. */ + public static final int MIN_PROXY_SIZE = 10 + + static def setProxySize(KNode node, KNode proxy) { + // Set size to be at least minimal node size + val proxyBounds = PlacementUtil.estimateSize(proxy) + val minProxySize = StateStyles.DEFAULT_FIGURE_MIN_NODE_SIZE + // Don't need to resize proxy if the node is already big enough by itself + val isBigEnough = proxyBounds.width > ProxyStyles.MIN_PROXY_SIZE + && proxyBounds.height > ProxyStyles.MIN_PROXY_SIZE + + proxy.width = isBigEnough ? proxyBounds.width : minProxySize + proxy.height = isBigEnough ? proxyBounds.height : minProxySize + + node.setProperty(KlighdProperties.PROXY_VIEW_RENDER_NODE_AS_PROXY, true) + node.setProperty(KlighdProperties.PROXY_VIEW_PROXY_RENDERING, proxy.data) + + } + +} \ No newline at end of file diff --git a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/StateStyles.xtend b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/StateStyles.xtend index ae9e92360b..afd8910176 100644 --- a/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/StateStyles.xtend +++ b/plugins/de.cau.cs.kieler.sccharts.ui/src/de/cau/cs/kieler/sccharts/ui/synthesis/styles/StateStyles.xtend @@ -37,6 +37,7 @@ import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KPolylineExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KPortExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions +import de.cau.cs.kieler.klighd.util.KlighdProperties import de.cau.cs.kieler.sccharts.State import de.cau.cs.kieler.sccharts.extensions.TextFormat import java.util.EnumSet @@ -53,7 +54,6 @@ import static de.cau.cs.kieler.sccharts.ui.synthesis.styles.ColorStore.Color.* import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* import static extension org.eclipse.emf.ecore.util.EcoreUtil.* -import de.cau.cs.kieler.klighd.util.KlighdProperties /** * Styles for {@link State}. @@ -89,6 +89,13 @@ class StateStyles { /** This property is set on the content container rendering and points to the container holding the declaration labels */ public static final IProperty DECLARATIONS_CONTAINER = new Property( "de.cau.cs.kieler.sccharts.ui.synthesis.style.state.declarations", null); + + /** The default figure's corner radius. */ + public static final int DEFAULT_FIGURE_CORNER_RADIUS = 17; + /** The default figure's minimal node size. */ + public static final int DEFAULT_FIGURE_MIN_NODE_SIZE = 2 * DEFAULT_FIGURE_CORNER_RADIUS; + /** The connector figure's size. */ + public static final int CONNECTOR_FIGURE_SIZE = 7; protected var baseLineWidth = 1; protected var stateLabelTextSize = 11; @@ -97,7 +104,7 @@ class StateStyles { * Adds a connector figure. */ def KRoundedRectangle addConnectorFigure(KNode node) { - node.addRoundedRectangle(7, 7, baseLineWidth) => [ + node.addRoundedRectangle(CONNECTOR_FIGURE_SIZE, CONNECTOR_FIGURE_SIZE, baseLineWidth) => [ background = STATE_CONNECTOR.color; foreground = STATE_CONNECTOR.color; ] @@ -107,8 +114,8 @@ class StateStyles { * Adds a small state figure. */ def KRoundedRectangle addDefaultFigure(KNode node) { - node.setMinimalNodeSize(34, 34); // 2 x corner radius - node.addRoundedRectangle(17, 17, baseLineWidth) => [ + node.setMinimalNodeSize(DEFAULT_FIGURE_MIN_NODE_SIZE, DEFAULT_FIGURE_MIN_NODE_SIZE); + node.addRoundedRectangle(DEFAULT_FIGURE_CORNER_RADIUS, DEFAULT_FIGURE_CORNER_RADIUS, baseLineWidth) => [ // Mark this figure as container for further content setProperty(IS_CONTENT_CONTAINER, true); setBackgroundGradient(STATE_BACKGROUND_GRADIENT_1.color, STATE_BACKGROUND_GRADIENT_2.color, 90); @@ -120,7 +127,7 @@ class StateStyles { * Adds a macro state figure. */ def KRoundedRectangle addMacroFigure(KNode node) { - node.setMinimalNodeSize(34, 34); // same as default figure + node.setMinimalNodeSize(DEFAULT_FIGURE_MIN_NODE_SIZE, DEFAULT_FIGURE_MIN_NODE_SIZE); // same as default figure node.addRoundedRectangle(8, 8, baseLineWidth) => [ // Mark this figure as container for further content setProperty(IS_CONTENT_CONTAINER, true); diff --git a/plugins/de.cau.cs.kieler.scg.klighd/META-INF/MANIFEST.MF b/plugins/de.cau.cs.kieler.scg.klighd/META-INF/MANIFEST.MF index 25228c45bc..effa3dea0e 100644 --- a/plugins/de.cau.cs.kieler.scg.klighd/META-INF/MANIFEST.MF +++ b/plugins/de.cau.cs.kieler.scg.klighd/META-INF/MANIFEST.MF @@ -21,7 +21,8 @@ Require-Bundle: org.eclipse.elk.core;bundle-version="0.7.1", de.cau.cs.kieler.kicool.ui, de.cau.cs.kieler.simulation.ide, de.cau.cs.kieler.simulation.ui, - de.cau.cs.kieler.annotations.ide + de.cau.cs.kieler.annotations.ide, + org.eclipse.elk.alg.mrtree Bundle-RequiredExecutionEnvironment: JavaSE-11 Export-Package: de.cau.cs.kieler.scg.klighd Bundle-ActivationPolicy: lazy diff --git a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphComponentSynthesis.xtend b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphComponentSynthesis.xtend index 0a654040be..80b6a0c605 100644 --- a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphComponentSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphComponentSynthesis.xtend @@ -30,6 +30,7 @@ import de.cau.cs.kieler.klighd.krendering.extensions.KLabelExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KPortExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions +import de.cau.cs.kieler.klighd.microlayout.PlacementUtil import de.cau.cs.kieler.klighd.util.KlighdProperties import de.cau.cs.kieler.klighd.util.KlighdSynthesisProperties import de.cau.cs.kieler.scg.Assignment @@ -85,10 +86,15 @@ class SCGraphComponentSynthesis { */ def dispatch createComponentNode(Assignment assignment) { val node = assignment.createNode().associateWith(assignment) + val proxy = createNode().associateWith(assignment) + // Straightforward rectangle drawing val figure = node.addRoundedRectangle(SCGraphSynthesisHelper.CORNERRADIUS, SCGraphSynthesisHelper.CORNERRADIUS, SCGraphSynthesisHelper.LINEWIDTH) figure.addText("").setSurroundingSpace(6, 0, 2, 0) + val proxyFigure = proxy.addRoundedRectangle(SCGraphSynthesisHelper.CORNERRADIUS, SCGraphSynthesisHelper.CORNERRADIUS, + SCGraphSynthesisHelper.LINEWIDTH) + proxyFigure.addText("").setSurroundingSpace(6, 0, 2, 0) var isSCGRef = false if (assignment.expression instanceof ReferenceCall) { val call = assignment.expression as ReferenceCall @@ -99,10 +105,13 @@ class SCGraphComponentSynthesis { } if (isSCGRef) { figure.setBackgroundGradient("#fcf7fc".color, "#e6cbf2".color, 90.0f) + proxyFigure.setBackgroundGradient("#fcf7fc".color, "#e6cbf2".color, 90.0f) } else { figure.background = "white".color; + proxyFigure.background = "white".color; } node.initialiseFigure(assignment) + proxy.initialiseAssignmentProxyFigure() // Add ports for control-flow and dependency routing. if (isGuardSCG) { @@ -164,6 +173,14 @@ class SCGraphComponentSynthesis { if (assignment.hasAnnotation(PriorityAuxiliaryData.OPTIMIZED_NODE_PRIORITIES_ANNOTATION)) { node.createOptimizedPriorityLabel(assignment).setAreaPlacementData.from(LEFT, 0, 0.9f, TOP, 0, 0.3f).to(RIGHT, 0, 0.9f, BOTTOM, 0, 0) } + + // Estimate proxy size + val proxyBounds = PlacementUtil.estimateSize(proxy) + proxy.width = proxyBounds.width + proxy.height = proxyBounds.height + + node.setProperty(KlighdProperties.PROXY_VIEW_PROXY_RENDERING, proxy.data) + return node } @@ -176,9 +193,14 @@ class SCGraphComponentSynthesis { */ def dispatch createComponentNode(Conditional conditional) { val node = conditional.createNode().associateWith(conditional) + val proxy = createNode().associateWith(conditional) + // Draw a diamond figure for conditionals. node.addPolygon.createDiamondShape node.initialiseFigure(conditional) + proxy.addPolygon.createDiamondShape + proxy.initialiseConditionalProxyFigure + // Add ports for control-flow and dependency routing. var switchBranch = false val branchAnnotation = conditional.getAnnotation(ANNOTATION_BRANCH) @@ -244,6 +266,16 @@ class SCGraphComponentSynthesis { if (conditional.hasAnnotation(PriorityAuxiliaryData.OPTIMIZED_NODE_PRIORITIES_ANNOTATION)) { node.createOptimizedPriorityLabel(conditional).setAreaPlacementData.from(LEFT, 0, 0, TOP, 0, 0.6f).to(RIGHT, 0, 0, BOTTOM, 0, 0) } + + // Estimate proxy size + val proxyBounds = PlacementUtil.estimateSize(proxy) + // Use additionalWidth since the estimation doesn't consider the diamond shape + val additionalWidth = 5 + proxy.width = proxyBounds.width + additionalWidth + proxy.height = proxyBounds.height + + node.setProperty(KlighdProperties.PROXY_VIEW_PROXY_RENDERING, proxy.data) + return node } @@ -338,6 +370,7 @@ class SCGraphComponentSynthesis { */ def dispatch createComponentNode(Entry entry) { val node = entry.createNode().associateWith(entry) + val scg = entry.eContainer as SCGraph // If the corresponding option is set to true, exit nodes are placed in the first layer; if (ALIGN_ENTRYEXIT_NODES.booleanValue) @@ -396,6 +429,7 @@ class SCGraphComponentSynthesis { if (entry.hasAnnotation(PriorityAuxiliaryData.OPTIMIZED_NODE_PRIORITIES_ANNOTATION)) { node.createOptimizedPriorityLabel(entry).setAreaPlacementData.from(LEFT, 0, 0.8f, TOP, 0, 0.1f) } + return node } diff --git a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphDiagramSynthesis.xtend b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphDiagramSynthesis.xtend index 470f7bff97..31e78e2b5d 100644 --- a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphDiagramSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphDiagramSynthesis.xtend @@ -289,6 +289,7 @@ class SCGraphDiagramSynthesis extends AbstractDiagramSynthesis { */ private def KNode synthesize(SCGraph scg) { val node = scg.createNode().associateWith(scg) + // Set root node and layout options. rootNode = node isSCPDG = scg.hasAnnotation(ANNOTATION_SCPDGTRANSFORMATION) diff --git a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphHierarchySynthesis.xtend b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphHierarchySynthesis.xtend index a94da92ea6..5b1c2aea45 100644 --- a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphHierarchySynthesis.xtend +++ b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphHierarchySynthesis.xtend @@ -46,6 +46,7 @@ import de.cau.cs.kieler.scg.extensions.SCGDependencyExtensions import de.cau.cs.kieler.scg.extensions.SCGSerializeHRExtensions import de.cau.cs.kieler.scg.extensions.SCGThreadExtensions import de.cau.cs.kieler.scg.extensions.ThreadPathType +import de.cau.cs.kieler.scg.processors.SCGAnnotations import de.cau.cs.kieler.scg.processors.analyzer.LoopAnalyzerV2 import de.cau.cs.kieler.scg.processors.analyzer.LoopData import de.cau.cs.kieler.scg.processors.priority.PriorityAuxiliaryData @@ -67,7 +68,6 @@ import static de.cau.cs.kieler.scg.processors.SCGAnnotations.* import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* import static extension org.eclipse.emf.ecore.util.EcoreUtil.* -import de.cau.cs.kieler.scg.processors.SCGAnnotations /** * @author kolja @@ -374,6 +374,7 @@ class SCGraphHierarchySynthesis { kNodeList += annotationNode } ] + val proxy = createNode("hierarchy" + nodeGrouping.toString) // Set options for the container. if (topdown()) @@ -496,6 +497,10 @@ class SCGraphHierarchySynthesis { } } + + // Don't render container + kContainer.setProperty(KlighdProperties.PROXY_VIEW_RENDER_NODE_AS_PROXY, false) + kContainer } } diff --git a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphSynthesisHelper.xtend b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphSynthesisHelper.xtend index 75cdc6f054..fe58981514 100644 --- a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphSynthesisHelper.xtend +++ b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphSynthesisHelper.xtend @@ -171,6 +171,30 @@ class SCGraphSynthesisHelper { figure.children.filter(KText).head.text = label } + /** + * Assignment proxy figures consist of a "=". + */ + def initialiseAssignmentProxyFigure(KNode node) { + node.initialiseProxyFigure("=") + } + + /** + * Conditional proxy figures consist of a "?". + */ + def initialiseConditionalProxyFigure(KNode node) { + node.initialiseProxyFigure("?") + } + + /** + * Initialises a proxy figure with the given label. + */ + def initialiseProxyFigure(KNode node, String label) { + node.setMinimalNodeSize(MINIMALWIDTH, MINIMALHEIGHT) + val figure = node.data.filter(KContainerRendering).last + if(SHOW_SHADOW.booleanValue) figure.shadow = "black".color + figure.children.filter(KText).head.text = label + } + /** * Draw a dotted line from the corresponding surface node to the given depth node. * diff --git a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphsDiagramSynthesis.xtend b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphsDiagramSynthesis.xtend index 49c1e40dd9..e4a8b89305 100644 --- a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphsDiagramSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/SCGraphsDiagramSynthesis.xtend @@ -18,6 +18,7 @@ import de.cau.cs.kieler.klighd.ViewContext import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis +import de.cau.cs.kieler.klighd.util.KlighdProperties import de.cau.cs.kieler.scg.SCGraphs import com.google.inject.Inject @@ -57,6 +58,9 @@ class SCGraphsDiagramSynthesis extends AbstractDiagramSynthesis { addRectangle => [invisible = true] ] } + + // All nodes should be shown as proxies + node.setProperty(KlighdProperties.PROXY_VIEW_HIERARCHICAL_OFF_SCREEN_DEPTH, -1) // Report duration usedContext?.setProperty(KiCoDiagramViewProperties.SYNTHESIS_TIME, System.currentTimeMillis - timestamp) diff --git a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/ssa/domtree/DominatorTreeSynthesis.xtend b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/ssa/domtree/DominatorTreeSynthesis.xtend index 527644312c..8abbec5285 100644 --- a/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/ssa/domtree/DominatorTreeSynthesis.xtend +++ b/plugins/de.cau.cs.kieler.scg.klighd/src/de/cau/cs/kieler/scg/klighd/ssa/domtree/DominatorTreeSynthesis.xtend @@ -16,6 +16,7 @@ package de.cau.cs.kieler.scg.klighd.ssa.domtree import com.google.inject.Inject import de.cau.cs.kieler.annotations.extensions.AnnotationsExtensions import de.cau.cs.kieler.klighd.LightDiagramServices +import de.cau.cs.kieler.klighd.SynthesisOption import de.cau.cs.kieler.klighd.internal.util.SourceModelTrackingAdapter import de.cau.cs.kieler.klighd.kgraph.KNode import de.cau.cs.kieler.klighd.krendering.SimpleUpdateStrategy @@ -33,13 +34,15 @@ import de.cau.cs.kieler.scg.SchedulingBlock import de.cau.cs.kieler.scg.extensions.SCGControlFlowExtensions import de.cau.cs.kieler.scg.extensions.SCGCoreExtensions import de.cau.cs.kieler.scg.processors.ssa.DominatorTree +import java.util.List import java.util.Map +import org.eclipse.elk.alg.mrtree.options.EdgeRoutingMode +import org.eclipse.elk.alg.mrtree.options.MrTreeOptions +import org.eclipse.elk.alg.mrtree.options.OrderWeighting import org.eclipse.elk.core.options.CoreOptions import org.eclipse.elk.core.options.Direction import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* -import de.cau.cs.kieler.klighd.SynthesisOption -import java.util.List class DominatorTreeSynthesis extends AbstractDiagramSynthesis { @@ -133,8 +136,11 @@ class DominatorTreeSynthesis extends AbstractDiagramSynthesis { nodes.entrySet.forEach[nodes.createDTEdge(value, key, dt)] val dtDiagram = createNode - dtDiagram.addLayoutParam(CoreOptions::ALGORITHM, "org.eclipse.elk.mrtree") + dtDiagram.addLayoutParam(CoreOptions::ALGORITHM, MrTreeOptions.ALGORITHM_ID) dtDiagram.addLayoutParam(CoreOptions::DIRECTION, Direction.DOWN) + dtDiagram.addLayoutParam(MrTreeOptions::EDGE_ROUTING_MODE, EdgeRoutingMode.AVOID_OVERLAP) + dtDiagram.addLayoutParam(MrTreeOptions::WEIGHTING, OrderWeighting.MODEL_ORDER) + dtDiagram.addLayoutParam(MrTreeOptions::COMPACTION, false) dtDiagram.children += nodes.values return dtDiagram