From 31d51a2e85e75895ad5268c58d8b5497a37e80c9 Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Thu, 1 Jun 2023 10:31:00 +0200 Subject: [PATCH 1/4] Added annotation to influence port side or switch to free port placement --- org.lflang/src/org/lflang/AttributeUtils.java | 5 + .../synthesis/LinguaFrancaSynthesis.java | 96 +++++++--- .../synthesis/SynthesisRegistration.java | 12 +- .../postprocessor/ReactionPortAdjustment.java | 7 +- .../postprocessor/ReactorPortAdjustment.java | 172 ++++++++++++++++++ .../styles/LinguaFrancaShapeExtensions.java | 14 +- .../org/lflang/validation/AttributeSpec.java | 4 + 7 files changed, 270 insertions(+), 40 deletions(-) create mode 100644 org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java index 6703df2d0e..2da57d5c2f 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -230,6 +230,11 @@ public static String getLabel(EObject node) { public static String getIconPath(EObject node) { return getAttributeValue(node, "icon"); } + + /** Return the declared side of the port, as given by the @side annotation. */ + public static String getPortSide(EObject node) { + return getAttributeValue(node, "side"); + } /** * Return the {@code @enclave} attribute annotated on the given node. diff --git a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 45f3342a0a..cdea160a53 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -102,6 +102,7 @@ import org.lflang.diagram.synthesis.action.MemorizingExpandCollapseAction; import org.lflang.diagram.synthesis.action.ShowCycleAction; import org.lflang.diagram.synthesis.postprocessor.ReactionPortAdjustment; +import org.lflang.diagram.synthesis.postprocessor.ReactorPortAdjustment; import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; import org.lflang.diagram.synthesis.styles.LinguaFrancaStyleExtensions; import org.lflang.diagram.synthesis.styles.ReactorFigureComponents; @@ -163,6 +164,8 @@ public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { "org.lflang.linguafranca.diagram.synthesis.reactor.recursive.instantiation", false); public static final Property REACTOR_HAS_BANK_PORT_OFFSET = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.bank.offset", false); + public static final Property REACTOR_MULTIPORT = + new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.multiport", false); public static final Property REACTOR_INPUT = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.input", false); public static final Property REACTOR_OUTPUT = @@ -244,6 +247,8 @@ public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { SynthesisOption.createRangeOption("Reactor Parameter/Variable Columns", 1, 10, 1) .setCategory(APPEARANCE); + public static final SynthesisOption FIXED_PORT_SIDE = + SynthesisOption.createCheckOption("Fixed Port Sides", true).setCategory(LAYOUT); public static final SynthesisOption SPACING = SynthesisOption.createRangeOption("Spacing (%)", 0, 150, 5, 75).setCategory(LAYOUT); @@ -280,6 +285,7 @@ public List getDisplayedSynthesisOptions() { SHOW_STATE_VARIABLES, REACTOR_BODY_TABLE_COLS, LAYOUT, + FIXED_PORT_SIDE, LayoutPostProcessing.MODEL_ORDER, SPACING); } @@ -481,6 +487,11 @@ private Collection createReactorNode( _kRenderingExtensions.addDoubleClickAction(figure, MemorizingExpandCollapseAction.ID); } _reactorIcons.handleIcon(comps.getReactor(), reactor, false); + + if (!getBooleanValue(FIXED_PORT_SIDE)) { + // Port figures will need post-processing to fix IO indication if portside is not fixed + ReactorPortAdjustment.apply(node, comps.getFigures()); + } if (getBooleanValue(SHOW_HYPERLINKS)) { // Collapse button @@ -607,6 +618,11 @@ private Collection createReactorNode( } } _reactorIcons.handleIcon(comps.getReactor(), reactor, true); + + if (!getBooleanValue(FIXED_PORT_SIDE)) { + // Port figures will need post-processing to fix IO indication if portside is not fixed + ReactorPortAdjustment.apply(node, comps.getFigures()); + } if (getBooleanValue(SHOW_HYPERLINKS)) { // Expand button @@ -728,16 +744,24 @@ public KNode configureReactorNodeLayout(KNode node, boolean main) { node, CoreOptions.NODE_SIZE_CONSTRAINTS, EnumSet.of(SizeConstraint.MINIMUM_SIZE, SizeConstraint.PORTS)); - - // Allows to freely shuffle ports on each side - setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); - // Adjust port label spacing to be closer to edge but not overlap with port figure - // TODO: Add PortLabelPlacement.NEXT_TO_PORT_IF_POSSIBLE back into the configuration, as soon as - // ELK provides a fix for LF issue #1273 + + + if (getBooleanValue(FIXED_PORT_SIDE)) { + // Allows to freely shuffle ports on each side + setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + } else { + // Ports are no longer fixed based on input or output + setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FREE); + } + setLayoutOption( node, CoreOptions.PORT_LABELS_PLACEMENT, EnumSet.of(PortLabelPlacement.ALWAYS_OTHER_SAME_SIDE, PortLabelPlacement.OUTSIDE)); + // TODO: Add PortLabelPlacement.NEXT_TO_PORT_IF_POSSIBLE back into the configuration, as soon as + // ELK provides a fix for LF issue #1273 + + // Adjust port label spacing to be closer to edge but not overlap with port figure setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_HORIZONTAL, 2.0); setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_VERTICAL, -3.0); @@ -1034,8 +1058,8 @@ private Collection transformReactorNetwork( int outputSize = reaction.effects.size(); if (!getBooleanValue(REACTIONS_USE_HYPEREDGES) && (inputSize > 1 || outputSize > 1)) { // If this node will have more than one input/output port, the port positions must be - // adjusted to the - // pointy shape. However, this is only possible after the layout. + // adjusted to the pointy shape. + // However, this is only possible after the layout. ReactionPortAdjustment.apply(node, figure); } @@ -1577,27 +1601,32 @@ private KEdge connect(KEdge edge, KPort src, KPort dst) { } /** Translate an input/output into a port. */ - private KPort addIOPort( - KNode node, PortInstance lfPort, boolean input, boolean multiport, boolean bank) { + private KPort addIOPort(KNode node, PortInstance lfPort, + boolean input, boolean multiport, boolean bank) { KPort port = _kPortExtensions.createPort(); node.getPorts().add(port); associateWith(port, lfPort.getDefinition()); NamedInstanceUtil.linkInstance(port, lfPort); _kPortExtensions.setPortSize(port, 6, 6); - - if (input) { - // multiports are smaller by an offset at the right, hence compensate in inputs - double offset = multiport ? -3.4 : -3.3; - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); - } else { - double offset = multiport ? -2.6 : -3.3; // multiports are smaller - offset = - bank - ? offset - LinguaFrancaShapeExtensions.BANK_FIGURE_X_OFFSET_SUM - : offset; // compensate bank figure width - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST); - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); + + var side = input ? PortSide.WEST : PortSide.EAST; + var userSideAttr = AttributeUtils.getPortSide(lfPort.getDefinition()); + if (userSideAttr != null) { + try { + var userSide = PortSide.valueOf(userSideAttr.toUpperCase()); + if (userSide != null) { + side = userSide; + } + } catch(Exception e) { + // ignore and use default + } + } + double offset = getReactorPortOffset(side == PortSide.WEST, multiport, bank); + setLayoutOption(port, CoreOptions.PORT_SIDE, side); + setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); + + if (multiport) { + node.setProperty(REACTOR_MULTIPORT, true); } if (bank && !node.getProperty(REACTOR_HAS_BANK_PORT_OFFSET)) { // compensate bank figure height @@ -1608,7 +1637,9 @@ private KPort addIOPort( node.setProperty(REACTOR_HAS_BANK_PORT_OFFSET, true); // only once } - _linguaFrancaShapeExtensions.addTrianglePort(port, multiport); + // If fixed port sides are active and the port is put on the opposite side, reverse it + var reverse = getBooleanValue(FIXED_PORT_SIDE) && input != (side == PortSide.WEST); + _linguaFrancaShapeExtensions.addTrianglePort(port, multiport, reverse); String label = lfPort.getName(); if (!getBooleanValue(SHOW_PORT_NAMES)) { @@ -1622,6 +1653,21 @@ private KPort addIOPort( associateWith(_kLabelExtensions.addOutsidePortLabel(port, label, 8), lfPort.getDefinition()); return port; } + + public static double getReactorPortOffset(boolean sideLeft, boolean multiport, boolean bank) { + var offset = -3.3; + + if (multiport) { + offset = sideLeft ? -3.4 : -2.6; + } + + if (bank && !sideLeft) { + // compensate bank figure width + offset -= LinguaFrancaShapeExtensions.BANK_FIGURE_X_OFFSET_SUM; + } + + return offset; + } private KPort addInvisiblePort(KNode node) { KPort port = _kPortExtensions.createPort(); diff --git a/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java b/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java index c48d2e59ee..bf78f2862d 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java @@ -1,17 +1,19 @@ package org.lflang.diagram.synthesis; -import de.cau.cs.kieler.klighd.IKlighdStartupHook; -import de.cau.cs.kieler.klighd.KlighdDataManager; import org.lflang.diagram.synthesis.action.CollapseAllReactorsAction; import org.lflang.diagram.synthesis.action.ExpandAllReactorsAction; import org.lflang.diagram.synthesis.action.FilterCycleAction; import org.lflang.diagram.synthesis.action.MemorizingExpandCollapseAction; import org.lflang.diagram.synthesis.action.ShowCycleAction; import org.lflang.diagram.synthesis.postprocessor.ReactionPortAdjustment; +import org.lflang.diagram.synthesis.postprocessor.ReactorPortAdjustment; import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; import org.lflang.diagram.synthesis.styles.LinguaFrancaStyleExtensions; import org.lflang.diagram.synthesis.util.NamedInstanceUtil; +import de.cau.cs.kieler.klighd.IKlighdStartupHook; +import de.cau.cs.kieler.klighd.KlighdDataManager; + /** * Registration of all diagram synthesis related classes in Klighd. * @@ -35,6 +37,7 @@ public void execute() { // Style Mod reg.registerStyleModifier(ReactionPortAdjustment.ID, new ReactionPortAdjustment()); + reg.registerStyleModifier(ReactorPortAdjustment.ID, new ReactorPortAdjustment()); // Blacklist LF-specific properties that should be removed when a diagram is sent from the // diagram server to a client. @@ -46,8 +49,7 @@ public void execute() { reg.registerBlacklistedProperty(ReactionPortAdjustment.PROCESSED); reg.registerBlacklistedProperty(LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER); reg.registerBlacklistedProperty(LinguaFrancaStyleExtensions.LABEL_PARENT_BACKGROUND); - reg.registerBlacklistedProperty( - NamedInstanceUtil - .LINKED_INSTANCE); // Very important since its values can not be synthesized easily! + // Very important since its values can not be synthesized easily! + reg.registerBlacklistedProperty(NamedInstanceUtil.LINKED_INSTANCE); } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java index 4507039b28..64c517a336 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java @@ -66,9 +66,10 @@ public class ReactionPortAdjustment implements IStyleModifier { public static void apply(KNode node, KRendering rendering) { // Add modifier that fixes port positions such that edges are properly attached to the shape var invisible = _kRenderingFactory.createKInvisibility(); - invisible.setInvisible(false); // make it ineffective (just for purpose of holding modifier) - invisible.setModifierId( - ReactionPortAdjustment.ID); // Add modifier to receive callback after layout + // make it ineffective (just for purpose of holding modifier) + invisible.setInvisible(false); + // Add modifier to receive callback after layout + invisible.setModifierId(ReactionPortAdjustment.ID); rendering.getStyles().add(invisible); node.setProperty(PROCESSED, false); } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java new file mode 100644 index 0000000000..3548fd5aa1 --- /dev/null +++ b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java @@ -0,0 +1,172 @@ +/************* + * Copyright (c) 2023, Kiel University. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ +package org.lflang.diagram.synthesis.postprocessor; + +import java.util.List; + +import javax.inject.Inject; + +import org.eclipse.elk.core.options.CoreOptions; +import org.eclipse.elk.graph.properties.Property; +import org.eclipse.xtext.xbase.lib.Extension; +import org.lflang.diagram.synthesis.LinguaFrancaSynthesis; +import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; + +import com.google.inject.Binder; +import com.google.inject.Guice; +import com.google.inject.Scopes; +import com.google.inject.TypeLiteral; + +import de.cau.cs.kieler.klighd.IStyleModifier; +import de.cau.cs.kieler.klighd.IViewer; +import de.cau.cs.kieler.klighd.internal.ILayoutRecorder; +import de.cau.cs.kieler.klighd.kgraph.KGraphElement; +import de.cau.cs.kieler.klighd.kgraph.KGraphFactory; +import de.cau.cs.kieler.klighd.kgraph.KNode; +import de.cau.cs.kieler.klighd.krendering.KRendering; +import de.cau.cs.kieler.klighd.krendering.KRenderingFactory; +import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; +import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis; + +/** + * Adjusts the port figures of reactors when fixed side are off to keep the input output indication correct. + * + * @author Alexander Schulz-Rosengarten + */ +public class ReactorPortAdjustment implements IStyleModifier { + + public static final String ID = + "org.lflang.diagram.synthesis.postprocessor.ReactorPortAdjustment"; + + /** INTERNAL property to mark node as flipped. */ + public static final Property FLIPPED = + new Property<>("org.lflang.diagram.synthesis.postprocessor.reactor.ports.flipped", false); + + @Inject @Extension private LinguaFrancaShapeExtensions _linguaFrancaShapeExtensions; + @Extension private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; + private static KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; + + /** Register this modifier on a reaction rendering. */ + public static void apply(KNode node, List renderings) { + var rendering = renderings.get(0); // Get first in bank + // Add modifier that fixes port positions such that edges are properly attached to the shape + var invisible = _kRenderingFactory.createKRotation(); + // make it ineffective (just for purpose of holding modifier) + invisible.setRotation(0); + // Add modifier to receive callback after layout + invisible.setModifierId(ID); + rendering.getStyles().add(invisible); + } + + public ReactorPortAdjustment() { + // Inject extension + if (_linguaFrancaShapeExtensions == null) { + var injector = Guice.createInjector(new com.google.inject.Module() { + // This Module is created to satisfy ViewSynthesisShared scope of used synthesis-extensions + public void configure(Binder binder) { + binder.bindScope(ViewSynthesisShared.class, Scopes.SINGLETON); + binder.bind(new TypeLiteral>(){}).toInstance(new LinguaFrancaSynthesis()); + } + }); + _linguaFrancaShapeExtensions = injector.getInstance(LinguaFrancaShapeExtensions.class); + } + } + + @Override + public boolean modify(IStyleModifier.StyleModificationContext context) { + try { + KGraphElement node = context.getGraphElement(); + if (node instanceof KNode) { + KNode knode = (KNode) node; + + // Find root node + KNode parent = knode; + while (parent.eContainer() != null) { + parent = (KNode) parent.eContainer(); + } + + // Get viewer (this is a bit brittle because it fetches the viewer from some internal + // property) + Object viewer = + parent.getAllProperties().entrySet().stream() + .filter( + entry -> + entry.getKey().getId().equals("de.cau.cs.kieler.klighd.viewer") + || entry.getKey().getId().equals("klighd.layout.viewer")) + .findAny() + .map(entry -> entry.getValue()) + .orElse(null); + + ILayoutRecorder recorder = null; + if (viewer instanceof IViewer) { + recorder = ((IViewer) viewer).getViewContext().getLayoutRecorder(); + } + + if (recorder != null) { + recorder.startRecording(); + } + for (var port : knode.getPorts()) { + var isInput = port.getProperty(LinguaFrancaSynthesis.REACTOR_INPUT).booleanValue(); + if (!isInput && !port.getProperty(LinguaFrancaSynthesis.REACTOR_OUTPUT)) { + continue; // skip + } + + var xPos = port.getXpos(); + var isLeft = xPos < 0; + var flip = isInput != isLeft; + var isFlipped = port.getProperty(FLIPPED).booleanValue(); + var needsUpdate = flip != isFlipped; + + if (needsUpdate) { + // Remove figure + port.getData().removeIf(it -> it instanceof KRendering); + + // Get port type + var isMultiport = port.getProperty(LinguaFrancaSynthesis.REACTOR_MULTIPORT); + var isBank = port.getProperty(LinguaFrancaSynthesis.REACTOR_HAS_BANK_PORT_OFFSET); + + // Add new figure + _linguaFrancaShapeExtensions.addTrianglePort(port, isMultiport, flip); + port.setProperty(FLIPPED, flip); + + // Compute new offset + var oldOffset = port.getProperty(CoreOptions.PORT_BORDER_OFFSET); + var newOffset = LinguaFrancaSynthesis.getReactorPortOffset(!isLeft, isMultiport, isBank); + + // Apply offset directly + port.setPos((float) (port.getXpos() + (oldOffset - newOffset)), port.getYpos()); + } + } + if (recorder != null) { + recorder.stopRecording(0); + } + } + } catch (Exception e) { + e.printStackTrace(); + // do not disturb rendering process + } + return false; + } +} diff --git a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java index efdc2ff4d5..3d7d45e86b 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java @@ -802,7 +802,7 @@ public KEllipse addResetFigure(KNode node) { } /** Creates the visual representation of a reactor port. */ - public KPolygon addTrianglePort(KPort port, boolean multiport) { + public KPolygon addTrianglePort(KPort port, boolean multiport, boolean reverse) { port.setSize(8, 8); // Create triangle port @@ -822,15 +822,15 @@ public KPolygon addTrianglePort(KPort port, boolean multiport) { // between parallel connections pointsToAdd = List.of( - _kRenderingExtensions.createKPosition(LEFT, 0, 0, TOP, 0.6f, 0), - _kRenderingExtensions.createKPosition(RIGHT, 1.2f, 0, TOP, 0, 0.5f), - _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0.6f, 0)); + _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, TOP, 0.6f, 0), + _kRenderingExtensions.createKPosition(reverse ? LEFT: RIGHT, 1.2f, 0, TOP, 0, 0.5f), + _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, BOTTOM, 0.6f, 0)); } else { pointsToAdd = List.of( - _kRenderingExtensions.createKPosition(LEFT, 0, 0, TOP, 0, 0), - _kRenderingExtensions.createKPosition(RIGHT, 0, 0, TOP, 0, 0.5f), - _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0)); + _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, TOP, 0, 0), + _kRenderingExtensions.createKPosition(reverse ? LEFT: RIGHT, 0, 0, TOP, 0, 0.5f), + _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, BOTTOM, 0, 0)); } trianglePort.getPoints().addAll(pointsToAdd); return trianglePort; diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index 45c984204e..90e7f9cf6d 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -209,6 +209,10 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put( "icon", new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); + // @side("value") + ATTRIBUTE_SPECS_BY_NAME.put( + "side", + new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); // @enclave(each=boolean) ATTRIBUTE_SPECS_BY_NAME.put( "enclave", From effd7328b73fc025733f49092a56f73415d5b8fe Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 1 Jun 2023 15:41:53 -0700 Subject: [PATCH 2/4] Apply formatter. --- org.lflang/src/org/lflang/AttributeUtils.java | 2 +- .../synthesis/LinguaFrancaSynthesis.java | 39 ++++++------ .../synthesis/SynthesisRegistration.java | 7 +-- .../postprocessor/ReactorPortAdjustment.java | 61 ++++++++++--------- .../styles/LinguaFrancaShapeExtensions.java | 4 +- 5 files changed, 57 insertions(+), 56 deletions(-) diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java index 2da57d5c2f..15013ac6ba 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -230,7 +230,7 @@ public static String getLabel(EObject node) { public static String getIconPath(EObject node) { return getAttributeValue(node, "icon"); } - + /** Return the declared side of the port, as given by the @side annotation. */ public static String getPortSide(EObject node) { return getAttributeValue(node, "side"); diff --git a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index cdea160a53..060e73507d 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -165,7 +165,7 @@ public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { public static final Property REACTOR_HAS_BANK_PORT_OFFSET = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.bank.offset", false); public static final Property REACTOR_MULTIPORT = - new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.multiport", false); + new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.multiport", false); public static final Property REACTOR_INPUT = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.input", false); public static final Property REACTOR_OUTPUT = @@ -247,8 +247,8 @@ public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { SynthesisOption.createRangeOption("Reactor Parameter/Variable Columns", 1, 10, 1) .setCategory(APPEARANCE); - public static final SynthesisOption FIXED_PORT_SIDE = - SynthesisOption.createCheckOption("Fixed Port Sides", true).setCategory(LAYOUT); + public static final SynthesisOption FIXED_PORT_SIDE = + SynthesisOption.createCheckOption("Fixed Port Sides", true).setCategory(LAYOUT); public static final SynthesisOption SPACING = SynthesisOption.createRangeOption("Spacing (%)", 0, 150, 5, 75).setCategory(LAYOUT); @@ -487,7 +487,7 @@ private Collection createReactorNode( _kRenderingExtensions.addDoubleClickAction(figure, MemorizingExpandCollapseAction.ID); } _reactorIcons.handleIcon(comps.getReactor(), reactor, false); - + if (!getBooleanValue(FIXED_PORT_SIDE)) { // Port figures will need post-processing to fix IO indication if portside is not fixed ReactorPortAdjustment.apply(node, comps.getFigures()); @@ -618,7 +618,7 @@ private Collection createReactorNode( } } _reactorIcons.handleIcon(comps.getReactor(), reactor, true); - + if (!getBooleanValue(FIXED_PORT_SIDE)) { // Port figures will need post-processing to fix IO indication if portside is not fixed ReactorPortAdjustment.apply(node, comps.getFigures()); @@ -744,8 +744,7 @@ public KNode configureReactorNodeLayout(KNode node, boolean main) { node, CoreOptions.NODE_SIZE_CONSTRAINTS, EnumSet.of(SizeConstraint.MINIMUM_SIZE, SizeConstraint.PORTS)); - - + if (getBooleanValue(FIXED_PORT_SIDE)) { // Allows to freely shuffle ports on each side setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); @@ -753,14 +752,14 @@ public KNode configureReactorNodeLayout(KNode node, boolean main) { // Ports are no longer fixed based on input or output setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FREE); } - + setLayoutOption( node, CoreOptions.PORT_LABELS_PLACEMENT, EnumSet.of(PortLabelPlacement.ALWAYS_OTHER_SAME_SIDE, PortLabelPlacement.OUTSIDE)); // TODO: Add PortLabelPlacement.NEXT_TO_PORT_IF_POSSIBLE back into the configuration, as soon as // ELK provides a fix for LF issue #1273 - + // Adjust port label spacing to be closer to edge but not overlap with port figure setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_HORIZONTAL, 2.0); setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_VERTICAL, -3.0); @@ -1601,30 +1600,30 @@ private KEdge connect(KEdge edge, KPort src, KPort dst) { } /** Translate an input/output into a port. */ - private KPort addIOPort(KNode node, PortInstance lfPort, - boolean input, boolean multiport, boolean bank) { + private KPort addIOPort( + KNode node, PortInstance lfPort, boolean input, boolean multiport, boolean bank) { KPort port = _kPortExtensions.createPort(); node.getPorts().add(port); associateWith(port, lfPort.getDefinition()); NamedInstanceUtil.linkInstance(port, lfPort); _kPortExtensions.setPortSize(port, 6, 6); - + var side = input ? PortSide.WEST : PortSide.EAST; var userSideAttr = AttributeUtils.getPortSide(lfPort.getDefinition()); if (userSideAttr != null) { try { var userSide = PortSide.valueOf(userSideAttr.toUpperCase()); if (userSide != null) { - side = userSide; + side = userSide; } - } catch(Exception e) { + } catch (Exception e) { // ignore and use default } } double offset = getReactorPortOffset(side == PortSide.WEST, multiport, bank); setLayoutOption(port, CoreOptions.PORT_SIDE, side); setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); - + if (multiport) { node.setProperty(REACTOR_MULTIPORT, true); } @@ -1653,19 +1652,19 @@ private KPort addIOPort(KNode node, PortInstance lfPort, associateWith(_kLabelExtensions.addOutsidePortLabel(port, label, 8), lfPort.getDefinition()); return port; } - + public static double getReactorPortOffset(boolean sideLeft, boolean multiport, boolean bank) { var offset = -3.3; - + if (multiport) { - offset = sideLeft ? -3.4 : -2.6; + offset = sideLeft ? -3.4 : -2.6; } - + if (bank && !sideLeft) { // compensate bank figure width offset -= LinguaFrancaShapeExtensions.BANK_FIGURE_X_OFFSET_SUM; } - + return offset; } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java b/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java index bf78f2862d..577e0c26ba 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java @@ -1,5 +1,7 @@ package org.lflang.diagram.synthesis; +import de.cau.cs.kieler.klighd.IKlighdStartupHook; +import de.cau.cs.kieler.klighd.KlighdDataManager; import org.lflang.diagram.synthesis.action.CollapseAllReactorsAction; import org.lflang.diagram.synthesis.action.ExpandAllReactorsAction; import org.lflang.diagram.synthesis.action.FilterCycleAction; @@ -11,9 +13,6 @@ import org.lflang.diagram.synthesis.styles.LinguaFrancaStyleExtensions; import org.lflang.diagram.synthesis.util.NamedInstanceUtil; -import de.cau.cs.kieler.klighd.IKlighdStartupHook; -import de.cau.cs.kieler.klighd.KlighdDataManager; - /** * Registration of all diagram synthesis related classes in Klighd. * @@ -50,6 +49,6 @@ public void execute() { reg.registerBlacklistedProperty(LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER); reg.registerBlacklistedProperty(LinguaFrancaStyleExtensions.LABEL_PARENT_BACKGROUND); // Very important since its values can not be synthesized easily! - reg.registerBlacklistedProperty(NamedInstanceUtil.LINKED_INSTANCE); + reg.registerBlacklistedProperty(NamedInstanceUtil.LINKED_INSTANCE); } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java index 3548fd5aa1..315eb0ebf4 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactorPortAdjustment.java @@ -24,21 +24,10 @@ ***************/ package org.lflang.diagram.synthesis.postprocessor; -import java.util.List; - -import javax.inject.Inject; - -import org.eclipse.elk.core.options.CoreOptions; -import org.eclipse.elk.graph.properties.Property; -import org.eclipse.xtext.xbase.lib.Extension; -import org.lflang.diagram.synthesis.LinguaFrancaSynthesis; -import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; - import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; - import de.cau.cs.kieler.klighd.IStyleModifier; import de.cau.cs.kieler.klighd.IViewer; import de.cau.cs.kieler.klighd.internal.ILayoutRecorder; @@ -49,9 +38,17 @@ import de.cau.cs.kieler.klighd.krendering.KRenderingFactory; import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis; +import java.util.List; +import javax.inject.Inject; +import org.eclipse.elk.core.options.CoreOptions; +import org.eclipse.elk.graph.properties.Property; +import org.eclipse.xtext.xbase.lib.Extension; +import org.lflang.diagram.synthesis.LinguaFrancaSynthesis; +import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; /** - * Adjusts the port figures of reactors when fixed side are off to keep the input output indication correct. + * Adjusts the port figures of reactors when fixed side are off to keep the input output indication + * correct. * * @author Alexander Schulz-Rosengarten */ @@ -63,7 +60,7 @@ public class ReactorPortAdjustment implements IStyleModifier { /** INTERNAL property to mark node as flipped. */ public static final Property FLIPPED = new Property<>("org.lflang.diagram.synthesis.postprocessor.reactor.ports.flipped", false); - + @Inject @Extension private LinguaFrancaShapeExtensions _linguaFrancaShapeExtensions; @Extension private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; private static KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; @@ -79,17 +76,22 @@ public static void apply(KNode node, List renderings) { invisible.setModifierId(ID); rendering.getStyles().add(invisible); } - + public ReactorPortAdjustment() { // Inject extension if (_linguaFrancaShapeExtensions == null) { - var injector = Guice.createInjector(new com.google.inject.Module() { - // This Module is created to satisfy ViewSynthesisShared scope of used synthesis-extensions - public void configure(Binder binder) { - binder.bindScope(ViewSynthesisShared.class, Scopes.SINGLETON); - binder.bind(new TypeLiteral>(){}).toInstance(new LinguaFrancaSynthesis()); - } - }); + var injector = + Guice.createInjector( + new com.google.inject.Module() { + // This Module is created to satisfy ViewSynthesisShared scope of used + // synthesis-extensions + public void configure(Binder binder) { + binder.bindScope(ViewSynthesisShared.class, Scopes.SINGLETON); + binder + .bind(new TypeLiteral>() {}) + .toInstance(new LinguaFrancaSynthesis()); + } + }); _linguaFrancaShapeExtensions = injector.getInstance(LinguaFrancaShapeExtensions.class); } } @@ -130,31 +132,32 @@ public boolean modify(IStyleModifier.StyleModificationContext context) { for (var port : knode.getPorts()) { var isInput = port.getProperty(LinguaFrancaSynthesis.REACTOR_INPUT).booleanValue(); if (!isInput && !port.getProperty(LinguaFrancaSynthesis.REACTOR_OUTPUT)) { - continue; // skip + continue; // skip } - + var xPos = port.getXpos(); var isLeft = xPos < 0; var flip = isInput != isLeft; var isFlipped = port.getProperty(FLIPPED).booleanValue(); var needsUpdate = flip != isFlipped; - + if (needsUpdate) { // Remove figure port.getData().removeIf(it -> it instanceof KRendering); - + // Get port type var isMultiport = port.getProperty(LinguaFrancaSynthesis.REACTOR_MULTIPORT); var isBank = port.getProperty(LinguaFrancaSynthesis.REACTOR_HAS_BANK_PORT_OFFSET); - + // Add new figure _linguaFrancaShapeExtensions.addTrianglePort(port, isMultiport, flip); port.setProperty(FLIPPED, flip); - + // Compute new offset var oldOffset = port.getProperty(CoreOptions.PORT_BORDER_OFFSET); - var newOffset = LinguaFrancaSynthesis.getReactorPortOffset(!isLeft, isMultiport, isBank); - + var newOffset = + LinguaFrancaSynthesis.getReactorPortOffset(!isLeft, isMultiport, isBank); + // Apply offset directly port.setPos((float) (port.getXpos() + (oldOffset - newOffset)), port.getYpos()); } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java index 3d7d45e86b..b8ee69fa1c 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java @@ -823,13 +823,13 @@ public KPolygon addTrianglePort(KPort port, boolean multiport, boolean reverse) pointsToAdd = List.of( _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, TOP, 0.6f, 0), - _kRenderingExtensions.createKPosition(reverse ? LEFT: RIGHT, 1.2f, 0, TOP, 0, 0.5f), + _kRenderingExtensions.createKPosition(reverse ? LEFT : RIGHT, 1.2f, 0, TOP, 0, 0.5f), _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, BOTTOM, 0.6f, 0)); } else { pointsToAdd = List.of( _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, TOP, 0, 0), - _kRenderingExtensions.createKPosition(reverse ? LEFT: RIGHT, 0, 0, TOP, 0, 0.5f), + _kRenderingExtensions.createKPosition(reverse ? LEFT : RIGHT, 0, 0, TOP, 0, 0.5f), _kRenderingExtensions.createKPosition(reverse ? RIGHT : LEFT, 0, 0, BOTTOM, 0, 0)); } trianglePort.getPoints().addAll(pointsToAdd); From 559ae5ae7ee4c8d7abb6abd4b44d84bcdaa7a56e Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 1 Jun 2023 23:24:38 -0700 Subject: [PATCH 3/4] Update org.lflang/src/org/lflang/AttributeUtils.java Co-authored-by: Edward A. Lee --- org.lflang/src/org/lflang/AttributeUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java index 15013ac6ba..23e2b82c3b 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -231,7 +231,10 @@ public static String getIconPath(EObject node) { return getAttributeValue(node, "icon"); } - /** Return the declared side of the port, as given by the @side annotation. */ + /** + * Return the {@code @side} annotation for the given node (presumably a port) + * or null if there is no such annotation. + */ public static String getPortSide(EObject node) { return getAttributeValue(node, "side"); } From 72fe448b68b9ca3b1d81ba8bd7620cfd4362cb03 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 6 Jun 2023 22:24:44 -0700 Subject: [PATCH 4/4] Fix formatting --- core/src/main/java/org/lflang/AttributeUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/AttributeUtils.java b/core/src/main/java/org/lflang/AttributeUtils.java index 23e2b82c3b..b49158c6e7 100644 --- a/core/src/main/java/org/lflang/AttributeUtils.java +++ b/core/src/main/java/org/lflang/AttributeUtils.java @@ -232,8 +232,8 @@ public static String getIconPath(EObject node) { } /** - * Return the {@code @side} annotation for the given node (presumably a port) - * or null if there is no such annotation. + * Return the {@code @side} annotation for the given node (presumably a port) or null if there is + * no such annotation. */ public static String getPortSide(EObject node) { return getAttributeValue(node, "side");