diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java b/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java index 8a518d1b2..143f0a486 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java @@ -1566,7 +1566,7 @@ protected static String getPathContent(PathIterator path) { StringBuffer result = new StringBuffer(); double[] coords = new double[6]; - result.append("d=\""); + result.append(" d=\""); while (!path.isDone()) { int segType = path.currentSegment(coords); @@ -1628,7 +1628,7 @@ protected static String getPathContent(PathIterator path) { protected String getPath(PathIterator path) { StringBuffer result = new StringBuffer(); - result.append(""); diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/KlighdNode.java b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/KlighdNode.java index 56c5ca569..929d3050c 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/KlighdNode.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/KlighdNode.java @@ -554,6 +554,8 @@ public KlighdFigureNode(final T rendering) { private T rendering; + private boolean isBackgroundFigure; + /** * Configures the {@link KRendering} element being represented by this {@link KlighdFigureNode}. * @@ -567,6 +569,8 @@ public void setRendering(final T rendering) { return; } + this.isBackgroundFigure = rendering.getProperty(KlighdProperties.BACKGROUND_FIGURE); + setVisibilityOn( rendering.getProperty(KlighdProperties.OUTLINE_INVISIBLE).booleanValue(), rendering.getProperty(KlighdProperties.EXPORTED_IMAGE_INVISIBLE).booleanValue(), @@ -678,6 +682,16 @@ protected boolean pickAfterChildren(final PPickPath pickPath) { } } + /** + * {@inheritDoc} + */ + @Override + public boolean isNotVisibleOn(KlighdPaintContext kpc) { + return kpc.isBackgroundFiguresOnly() && !this.isBackgroundFigure + || kpc.isNonBackgroundFiguresOnly() && this.isBackgroundFigure + || super.isNotVisibleOn(kpc); + } + /** * {@inheritDoc}
*
diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/AbstractKGERenderingController.java b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/AbstractKGERenderingController.java index bb8a4aacc..4c6fdc76f 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/AbstractKGERenderingController.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/AbstractKGERenderingController.java @@ -156,6 +156,9 @@ enum ElementMovement { /** whether to synchronize the rendering with the model. */ private boolean syncRendering = false; + /** A flag for capturing whether some figure description parts are tagged as background figures. */ + protected boolean backgroundFiguresPresent = false; + /** * A flag indicating the availability of {@link KStyle KStyles} with valid modifier ids in * {@link #currentRendering}. @@ -324,6 +327,9 @@ void updateRendering() { // this call updates the 'currentRendering' field getCurrentRendering(); + // reset the flag before re-evaluating the current figure description + backgroundFiguresPresent = false; + // reset that flag as potentially available styles with a modifier might be removed now modifiableStylesPresent = false; @@ -1172,6 +1178,9 @@ protected PNodeController createRendering(final KRendering rendering, final boolean isRenderingRef = rendering.eClass() == KRenderingPackage.eINSTANCE.getKRenderingRef(); + this.backgroundFiguresPresent = this.backgroundFiguresPresent + || rendering.getProperty(KlighdProperties.BACKGROUND_FIGURE); + final List renderingStyles = rendering.getStyles(); processModifiableStyles(renderingStyles); diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/KNodeRenderingController.java b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/KNodeRenderingController.java index c0d93ad25..d5248ee05 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/KNodeRenderingController.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/controller/KNodeRenderingController.java @@ -72,6 +72,8 @@ protected PNodeController internalUpdateRendering() { handleAreaAndPointPlacementRendering(createDefaultRendering(), repNode); } + repNode.setHasBackgroundFigures(this.backgroundFiguresPresent); + // make sure the child area is attached to something if (childAreaNode.getParent() == null) { // if the childArea is not part of the above created PNode rendering tree diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KChildAreaNode.java b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KChildAreaNode.java index 60023c193..e413b8c98 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KChildAreaNode.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KChildAreaNode.java @@ -16,9 +16,15 @@ */ package de.cau.cs.kieler.klighd.piccolo.internal.nodes; +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.impl.AdapterImpl; +import org.eclipse.emf.common.util.BasicEMap; + +import de.cau.cs.kieler.klighd.kgraph.KGraphPackage; import de.cau.cs.kieler.klighd.krendering.KChildArea; import de.cau.cs.kieler.klighd.piccolo.IKlighdNode; import de.cau.cs.kieler.klighd.piccolo.internal.util.KlighdPaintContext; +import de.cau.cs.kieler.klighd.util.KlighdProperties; import edu.umd.cs.piccolo.PLayer; import edu.umd.cs.piccolo.PNode; import edu.umd.cs.piccolo.util.PBounds; @@ -41,14 +47,17 @@ public class KChildAreaNode extends KlighdDisposingLayer implements IKlighdNode. private final KNodeAbstractNode parentNodeNode; - private final boolean edgesFirst; + private boolean edgesFirst; /** the node layer. */ - private PLayer nodeLayer; + private PLayer belowEdgesNodeLayer; /** the edge layer. */ private PLayer edgeLayer; + /** 2nd node layer containing nodes being drawn on top of edges. */ + private PLayer aboveEdgesNodeLayer; + /** the {@link KChildArea} represented by this {@link KChildAreaNode}, may be null. */ private KChildArea childArea; @@ -69,6 +78,37 @@ public KChildAreaNode(final KNodeAbstractNode parentNodeNode, final boolean edge this.setPickable(false); this.parentNodeNode = parentNodeNode; this.edgesFirst = edgesFirst; + + if (!edgesFirst && parentNodeNode instanceof KNodeTopNode) { + // special handling for the KNodeTopNode since the root KNode and the KNodeTopNode are + // kept for the entire diagram life time and the update strategy transfers settings on + // the root node after this constructor call; + // hence, 'edgesFirst' will always be false except KlighdProperties.EDGES_FIRST is set + // on the viewContext; + // thus, if no activation is done on the view context we need to listen for changes on + // the properties of the root node being performed by the update strategy, so... + + parentNodeNode.getViewModelElement().eAdapters().add(new AdapterImpl() { + + @Override + public void notifyChanged(Notification msg) { + if (msg.getFeature() == KGraphPackage.eINSTANCE.getEMapPropertyHolder_Properties()) { + final Object newValue = msg.getNewValue(); + if (msg.getEventType() == Notification.REMOVE_MANY && newValue == null) { + KChildAreaNode.this.edgesFirst = + KlighdProperties.EDGES_FIRST.getDefault().booleanValue(); + + } else if (msg.getEventType() == Notification.ADD + && newValue instanceof BasicEMap.Entry + && KlighdProperties.EDGES_FIRST + .equals(((BasicEMap.Entry) newValue).getKey())) { + KChildAreaNode.this.edgesFirst = + (Boolean) ((BasicEMap.Entry) newValue).getValue(); + } + } + } + }); + } } /** @@ -103,8 +143,8 @@ public PLayer getEdgeLayer() { * * @return a dedicated layer accommodating all attached {@link KNodeNode KNodeNodes}. */ - public PLayer getNodeLayer() { - return this.nodeLayer; + public PLayer getDefaultNodeLayer() { + return this.belowEdgesNodeLayer; } /** @@ -124,12 +164,22 @@ public void setClip(final boolean clip) { * the node representation */ public void addNode(final KNodeNode node) { - if (nodeLayer == null) { - nodeLayer = new KlighdDisposingLayer(); - addChild(edgesFirst ? getChildrenCount() : 0, nodeLayer); + if (edgesFirst || node.isTaggedAsForeground()) { + if (aboveEdgesNodeLayer == null) { + aboveEdgesNodeLayer = new KlighdDisposingLayer(); + addChild(getChildrenCount(), aboveEdgesNodeLayer); + } + aboveEdgesNodeLayer.addChild(node); + node.setParentNode(parentNodeNode, true); + + } else { + if (belowEdgesNodeLayer == null) { + belowEdgesNodeLayer = new KlighdDisposingLayer(); + addChild(0, belowEdgesNodeLayer); + } + belowEdgesNodeLayer.addChild(node); + node.setParentNode(parentNodeNode, false); } - nodeLayer.addChild(node); - node.setParentNode(parentNodeNode); } /** @@ -141,7 +191,7 @@ public void addNode(final KNodeNode node) { public void addEdge(final KEdgeNode edge) { if (edgeLayer == null) { edgeLayer = new KlighdDisposingLayer(); - addChild(edgesFirst ? 0 : getChildrenCount(), edgeLayer); + addChild(belowEdgesNodeLayer == null ? 0 : 1, edgeLayer); } edgeLayer.addChild(edge); } @@ -264,6 +314,26 @@ public void fullPaint(final PPaintContext paintContext) { super.validateFullBounds(); super.validateFullPaint(); - super.fullPaint(paintContext); + if (this.aboveEdgesNodeLayer == null || this.aboveEdgesNodeLayer.getChildrenCount() == 0) { + // no nodes to be drawn above edges, so draw as usual + super.fullPaint(paintContext); + + } else if (getVisible() && fullIntersects(paintContext.getLocalClip())) { + final KlighdPaintContext kpc = (KlighdPaintContext) paintContext; + + // assigning selected nodes explicitly to the 'aboveEdgesNodeLayer' or 'belowEdgesNodeLayer' + // is not supported as of now, and therefore a mixture of nodes to be drawn underneath the edge layer + // and nodes to be drawn on top of the edge layer should not occur at the time of writing this. + + kpc.pushFigureFilterBackgroundOnly(); + paintContext.pushTransform(getTransformReference(false)); + this.aboveEdgesNodeLayer.fullPaint(paintContext); + paintContext.popTransform(getTransformReference(false)); + kpc.popFigureFilter(); + + kpc.pushFigureFilterNonBackgroundOnly(); + super.fullPaint(paintContext); + kpc.popFigureFilter(); + } } } diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KNodeNode.java b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KNodeNode.java index 7911e8426..fb577c6c8 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KNodeNode.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/nodes/KNodeNode.java @@ -75,6 +75,15 @@ public class KNodeNode extends KNodeAbstractNode implements */ private final PCamera childAreaCamera; + /** Flag indicating that this node is tagged to be drawn on top of the edge layer within the parent child area. */ + private final boolean isTaggedAsForeground; + + /** Flag indicating that this node is considered be drawn on top of the edge layer within the parent child area by its containing child area. */ + private boolean isDrawnAsForeground = false; + + /** Flag indicating whether this node's figure contains background parts to be drawn before drawing all the edges within the parent child area. */ + private boolean hasBackgroundFigures = false; + /** this flag indicates whether this node is currently observed by the {@link KlighdMainCamera}. */ private boolean isRootLayer = false; @@ -114,6 +123,8 @@ public class KNodeNode extends KNodeAbstractNode implements public KNodeNode(final KNode node, final boolean edgesFirst) { super(node, edgesFirst); + this.isTaggedAsForeground = node.getProperty(KlighdProperties.FOREGROUND_NODE).booleanValue(); + this.visibilityHelper = KGraphElementNode.evaluateVisibilityDefinitions(node, null); this.childAreaCamera = new PCamera() { @@ -395,8 +406,9 @@ public KNodeAbstractNode getParentKNodeNode() { * @param parentINode * the {@link AbstractKNodeNode} being the new parent in terms of the structural nodes */ - public void setParentNode(final KNodeAbstractNode parentINode) { + public void setParentNode(final KNodeAbstractNode parentINode, final boolean addedAsForeground) { this.parent = parentINode; + this.isDrawnAsForeground = addedAsForeground; } /** @@ -408,6 +420,27 @@ public void removeFromParent() { this.parent = null; } + /** + * Getter. + * + * @return the value of getProperty({@link KlighdProperties#FOREGROUND_NODE}) for the + * corresponding {@link KNode}. + */ + public boolean isTaggedAsForeground() { + return this.isTaggedAsForeground; + } + + /** + * Setter. + * + * @param hasBackgroundFigures + * value denoting whether the attached node figure contains parts being tagged as + * background figure parts. + */ + public void setHasBackgroundFigures(final boolean hasBackgroundFigures) { + this.hasBackgroundFigures = hasBackgroundFigures; + } + /** * {@inheritDoc} */ @@ -645,8 +678,17 @@ protected void notifyCameras(PBounds parentBounds) { */ @Override public void fullPaint(final PPaintContext paintContext) { - final boolean isRootLayer = this.isRootLayer; final KlighdPaintContext kpc = (KlighdPaintContext) paintContext; + + final boolean backgroundFiguresOnly = kpc.isBackgroundFiguresOnly(); + if (backgroundFiguresOnly && !(this.isDrawnAsForeground && this.hasBackgroundFigures)) { + // if parent child area preforms the background figure drawings and this + // node is not to be drawn in foreground or doesn't have background figure parts + // exit early! + return; + } + + final boolean isRootLayer = this.isRootLayer; if (!isRootLayer && this.visibilityHelper != null && this.visibilityHelper.isNotVisibleOn(kpc)) { return; @@ -671,6 +713,14 @@ public void fullPaint(final PPaintContext paintContext) { paintContext.pushTransform(transform); paintContext.pushTransparency(getTransparency()); + final boolean pushAllFilter = !this.isDrawnAsForeground && kpc.isNonBackgroundFiguresOnly(); + if (pushAllFilter) { + // in case this node is to be drawn as a regular (non-foreground) node but has figure parts tagged as background figures + // and we're in non-background drawing mode override the configured non-background figure filter with the all filter, + // and revert the override below, of course! + kpc.pushFigureFilterAll(); + } + this.hasBeenDrawn = true; final boolean applyScale = this.nodeScale != null; @@ -678,13 +728,16 @@ public void fullPaint(final PPaintContext paintContext) { kpc.pushNodeScale(this.nodeScale.doubleValue()); } - if (!getOccluded()) { + if (!getOccluded() && !backgroundFiguresOnly) { + // contributes the opening group tag during SVG exports if semantic data are attached, + // don't do that during the background figure drawing paint(paintContext); } final int count = getChildrenCount(); + final List children = getChildrenReference(); for (int i = 0; i < count; i++) { - final PNode each = (PNode) getChildrenReference().get(i); + final PNode each = (PNode) children.get(i); if (isRootLayer) { if (isRootAndDrawnViaMainCamera) { @@ -705,17 +758,31 @@ public void fullPaint(final PPaintContext paintContext) { // Hence, it must be that of the outline diagram or any further one. continue; } + + } else if (backgroundFiguresOnly) { + if (i != 0 || each == this.childArea) { + // do not draw anything except the node's figure if just background drawing is requested + break; + } } each.fullPaint(paintContext); } - paintAfterChildren(paintContext); + if (!backgroundFiguresOnly) { + // contributes the closing group tag during SVG exports if semantic data are attached, + // don't do that during the background figure drawing + paintAfterChildren(paintContext); + } if (applyScale) { kpc.popNodeScale(); } + if (pushAllFilter) { + kpc.popFigureFilter(); + } + paintContext.popTransparency(getTransparency()); paintContext.popTransform(transform); } diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/util/KlighdPaintContext.java b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/util/KlighdPaintContext.java index 8197a03e2..6acaf1b55 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/util/KlighdPaintContext.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo/src/de/cau/cs/kieler/klighd/piccolo/internal/util/KlighdPaintContext.java @@ -18,13 +18,13 @@ import java.awt.Graphics2D; import java.awt.Shape; -import java.util.Stack; import de.cau.cs.kieler.klighd.piccolo.KlighdSWTGraphics; import de.cau.cs.kieler.klighd.piccolo.internal.nodes.KlighdMagnificationLensCamera; import de.cau.cs.kieler.klighd.piccolo.internal.nodes.KlighdMainCamera; import edu.umd.cs.piccolo.PCamera; import edu.umd.cs.piccolo.util.PPaintContext; +import edu.umd.cs.piccolo.util.PStack; /** * This is a specialization of {@link PPaintContext} suppressing the super class' clipping @@ -36,6 +36,14 @@ */ public class KlighdPaintContext extends PPaintContext { + /** + * Enum defining the figure filter values required for drawing foreground nodes with background + * figure parts. + */ + public static enum FigureFilter { + ALL, BG_ONLY, nBG_ONLY + } + /** * Factory method creating a {@link KlighdPaintContext} configured for on screen (main) diagram * drawing. @@ -143,6 +151,8 @@ protected KlighdPaintContext(final KlighdSWTGraphics graphics, final boolean out } private double cameraZoomScale = 1d; + private FigureFilter figureFilter = FigureFilter.ALL; + private final boolean mainDiagram; private final boolean outline; private final boolean export; @@ -151,7 +161,8 @@ protected KlighdPaintContext(final KlighdSWTGraphics graphics, final boolean out private final boolean addSemanticData; private final boolean setTextLengths; - private final Stack cameraScales = new Stack(); + private final PStack cameraScales = new PStack(); + private final PStack figureFilters = new PStack(); /** * Provides the current diagram zoom factor as determined by the active {@link KlighdMainCamera}'s @@ -271,7 +282,7 @@ public PCamera getTopCamera() { /** * {@inheritDoc}
- * This specialization suppresses the original clipping behavior as we don't need it or event + * This specialization suppresses the original clipping behavior as we don't need it or even * don't want to have it. Thus, this method does nothing. */ @Override @@ -281,7 +292,7 @@ public void pushClip(final Shape clip) { /** * {@inheritDoc}
- * This specialization suppresses the original clipping behavior as we don't need it or event + * This specialization suppresses the original clipping behavior as we don't need it or even * don't want to have it. Thus, this method does nothing. */ @Override @@ -327,6 +338,54 @@ public void pushNodeScale(final double scale) { * {@link de.cau.cs.kieler.klighd.piccolo.internal.nodes.KChildAreaNode KChildAreaNode} only! */ public void popNodeScale() { - cameraZoomScale = cameraScales.pop(); + cameraZoomScale = (double) cameraScales.pop(); + } + + /** + * @return true if this paint context expects only background figure parts of node + * figures to be drawn, and false otherwise. + */ + public boolean isBackgroundFiguresOnly() { + return this.figureFilter == FigureFilter.BG_ONLY; + } + + /** + * @return true if this paint context expects everything to be drawn except + * background figure parts of node figures, and false otherwise. + */ + public boolean isNonBackgroundFiguresOnly() { + return this.figureFilter == FigureFilter.nBG_ONLY; + } + + /** + * Saves the current figure filter and overrides it with {@link FigureFilter#ALL}. + */ + public void pushFigureFilterAll() { + this.figureFilters.push(this.figureFilter); + this.figureFilter = FigureFilter.ALL; + } + + /** + * Saves the current figure filter and overrides it with {@link FigureFilter#BG_ONLY}. + */ + public void pushFigureFilterBackgroundOnly() { + this.figureFilters.push(this.figureFilter); + this.figureFilter = FigureFilter.BG_ONLY; + } + + /** + * Saves the current figure filter and overrides it with {@link FigureFilter#nBG_ONLY}. + */ + public void pushFigureFilterNonBackgroundOnly() { + this.figureFilters.push(this.figureFilter); + this.figureFilter = FigureFilter.nBG_ONLY; + } + + /** + * Discards the current filter and restores the previous one. Must be called in sync with one of + * the pushFigureFilter... methods. + */ + public void popFigureFilter() { + this.figureFilter = (FigureFilter) this.figureFilters.pop(); } } \ No newline at end of file diff --git a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/syntheses/DiagramSyntheses.java b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/syntheses/DiagramSyntheses.java index 41641c33f..6e6844305 100644 --- a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/syntheses/DiagramSyntheses.java +++ b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/syntheses/DiagramSyntheses.java @@ -547,6 +547,41 @@ public static T propagateVisibilityBoundsToChildren(final return setPropagateVisibilityBoundsToChildren(krendering, true); } + /** + * Configures the provided {@link KNode} to be drawn as foreground node on top of the edges + * within the common parent node. This avoids overlaps of node drawings by edge paths, which can + * be relevant for special diagram types with predefined layout. + * + * @see {@link KlighdProperties#FOREGROUND_NODE} + * + * @param node + * the {@link KNode} to be configured + * @return node for convenience + */ + public static KNode drawAsForegroundNode(final KNode node) { + node.setProperty(KlighdProperties.FOREGROUND_NODE, true); + return node; + } + + /** + * Configures the provided {@link KRendering} to be drawn as background figure that avoids + * overlaps of edge drawings by node figures parts, provided the node is drawn as foreground + * node, i.e. on top of the edge drawings within containing parent node. + * + * @see {@link KlighdProperties#BACKGROUND_FIGURE} + * + * @param + * the concrete type of krendering + * @param krendering + * the {@link KRendering} to be configured, has no effect for + * {@link de.cau.cs.kieler.klighd.krendering.KChildArea KChildAreas} + * @return krendering for convenience + */ + public static T drawAsBackgroundFigure(final T krendering) { + krendering.setProperty(KlighdProperties.BACKGROUND_FIGURE, true); + return krendering; + } + /** * Configures the provided {@link KRendering} to be excluded from the outline diagram view. * diff --git a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java index 09e56a876..d7c897289 100644 --- a/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java +++ b/plugins/de.cau.cs.kieler.klighd/src/de/cau/cs/kieler/klighd/util/KlighdProperties.java @@ -169,6 +169,28 @@ public static boolean isSelectable(final EObject viewElement) { || (viewElement instanceof KText && isSelectable((KText) viewElement)); } + /** + * Property for distinguishing a {@link KNode} to be drawn on top of the edge drawings within + * the parent {@link KNode}, in contrast to the default behavior of drawing edges on top of + * nodes. If set to true for a particular node the corresponding drawing node shall + * be drawn after all edges within the parent node are drawn and should receive precedence in + * the element picking. + */ + public static final IProperty FOREGROUND_NODE = new Property( + "de.cau.cs.kieler.klighd.foregroundNode", false); + + /** + * Property for distinguishing a {@link de.cau.cs.kieler.klighd.krendering.KRendering + * KRendering} being part of a {@link KNode}'s figure description (!) to be drawn underneath + * (before) the edge drawings within the parent {@link KNode}. This property setting shall have + * no effect for standard diagram descriptions, but is supposed to have an effect if + * {@link KlighdProperties#FOREGROUND_NODE} is set to true for the corresponding + * {@link KNode}, or {@link KlighdProperties#EDGES_FIRST} is configured on the + * {@link de.cau.cs.kieler.klighd.ViewContext ViewContext}. This setting is ignored for + * {@link de.cau.cs.kieler.klighd.krendering.KChildArea KChildAreas}. + */ + public static final IProperty BACKGROUND_FIGURE = new Property( + "de.cau.cs.kieler.klighd.backgroundFigure", false); /** * Property determining the visibility of a certain diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/AdapterTest.launch b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/AdapterTest.launch index c8f09e5ab..dd5178883 100644 --- a/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/AdapterTest.launch +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/AdapterTest.launch @@ -1,45 +1,169 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/FreeHEPSVGOffscreenRenderingWithEdgesFirstAndBackgroundTest.launch b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/FreeHEPSVGOffscreenRenderingWithEdgesFirstAndBackgroundTest.launch new file mode 100644 index 000000000..369eb5df0 --- /dev/null +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/FreeHEPSVGOffscreenRenderingWithEdgesFirstAndBackgroundTest.launch @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/FreeHEPSVGOffscreenRenderingWithForegroundNodesTest.launch b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/FreeHEPSVGOffscreenRenderingWithForegroundNodesTest.launch new file mode 100644 index 000000000..80f788fca --- /dev/null +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/FreeHEPSVGOffscreenRenderingWithForegroundNodesTest.launch @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/HighlightedEdgeToForegroundTest.launch b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/HighlightedEdgeToForegroundTest.launch index 1eaf256d3..8904248df 100644 --- a/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/HighlightedEdgeToForegroundTest.launch +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/runConfigs/HighlightedEdgeToForegroundTest.launch @@ -25,6 +25,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/AdapterTest.java b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/AdapterTest.java index a36d1e9f4..19a6f1ca9 100644 --- a/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/AdapterTest.java +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/AdapterTest.java @@ -87,7 +87,7 @@ public void layoutTest() { ); final KNodeAbstractNode node = controller.getNode(); - final PLayer nodeLayer = node.getChildAreaNode().getNodeLayer(); + final PLayer nodeLayer = node.getChildAreaNode().getDefaultNodeLayer(); final PNode pnext = nodeLayer.getChild(0); final PLayer portlayer = ((KNodeNode) pnext).getPortLayer(); final PNode pport = portlayer.getChild(3); @@ -380,9 +380,7 @@ private boolean checkStructure(final KNode kgraph, final KNodeAbstractNode picco if (!piccoloTree.getViewModelElement().toString().equals(kgraph.toString())) { return false; } - final PLayer nodeLayer = piccoloTree.getChildAreaNode().getNodeLayer() != null - ? piccoloTree.getChildAreaNode().getNodeLayer() : new PLayer(); - // chsch: added some hotfix here + final PLayer nodeLayer = piccoloTree.getChildAreaNode().getDefaultNodeLayer(); PLayer edgeLayer = null; if (piccoloTree.getParentKNodeNode() != null) { @@ -439,7 +437,7 @@ private boolean checkStructure(final KNode kgraph, final KNodeAbstractNode picco } } // check recursively for children - if (kgraph.getChildren() != null && nodeLayer.getChildrenCount() == 0) { + if (kgraph.getChildren() != null && (nodeLayer == null || nodeLayer.getChildrenCount() == 0)) { return false; } if (!checkStructure(kgraph.getChildren().get(i), (KNodeAbstractNode) nodeLayer.getChild(i))) { diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingWithEdgesFirstAndBackgroundTest.xtend b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingWithEdgesFirstAndBackgroundTest.xtend new file mode 100644 index 000000000..e35723b79 --- /dev/null +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingWithEdgesFirstAndBackgroundTest.xtend @@ -0,0 +1,347 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2020 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package de.cau.cs.kieler.klighd.piccolo.test.freehep + +import de.cau.cs.kieler.klighd.kgraph.KNode +import de.cau.cs.kieler.klighd.krendering.Colors +import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions +import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions +import de.cau.cs.kieler.klighd.util.KlighdSemanticDiagramData +import org.eclipse.elk.alg.layered.options.LayeredOptions +import org.eclipse.elk.core.options.CoreOptions +import org.eclipse.swt.widgets.Display +import org.junit.BeforeClass +import org.junit.FixMethodOrder +import org.junit.Test + +import static de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil.* + +import static extension de.cau.cs.kieler.klighd.piccolo.test.freehep.FreeHEPSVGOffscreenRenderingTest.* +import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* + +/** + * @author chsch + */ + @FixMethodOrder(NAME_ASCENDING) +class FreeHEPSVGOffscreenRenderingWithEdgesFirstAndBackgroundTest { + + static extension KRenderingExtensions = new KRenderingExtensions() + + static extension KContainerRenderingExtensions = new KContainerRenderingExtensions() + + @BeforeClass + def static void initDisplay() { + Display.getDefault() + } + + // Note: The diagrams are auto-arranged by ELK Layered with the root's padding being fixed to 'ElkPadding(10)'. + // This way the additional space required by the oversized background figures exactly matches the padding, and + // the left-most located node's drawing starts at (x,y) == (0,0). Thus, no whitespace is trimmed/compensated + // on the left and top border by the exporter. (The exporter accumulates the nested transforms.) + // Otherwise we would see unexpected transforms on the KEdges' drawings, as their points always refer to the + // coordinate system of the source and target nodes' common parent node. + + @Test + def void test01_singleKNode() { + ''' + + + + + + + + + + + + + + + + + + + '''.equalsSVGwithEdgesFirstOf[ + addNode(null, Colors.RED) + ] + } + + @Test + def void test01b_singleKNodeBGfigureInvisible() { + ''' + + + + + + + + + + + + + '''.equalsSVGwithEdgesFirstOf[ + addNode(null, Colors.RED).KContainerRendering.children.head.invisible = true + ] + } + + @Test + def void test02_twoKNodes() { + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '''.equalsSVGwithEdgesFirstOf[ + setLayoutOption(CoreOptions.SPACING_COMPONENT_COMPONENT, 25d) + addNode("node1", Colors.RED) + addNode("node2", Colors.BLUE) + ] + } + + @Test + def void test02b_twoKNodesWithSemanticData() { + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '''.equalsSVGwithEdgesFirstOf[ + setLayoutOption(CoreOptions.SPACING_COMPONENT_COMPONENT, 25d) + addNode("node1", Colors.RED).semanticData = KlighdSemanticDiagramData.of(it).putID("node1") + addNode("node2", Colors.BLUE).semanticData = KlighdSemanticDiagramData.of(it).putID("node2") + ] + } + + @Test + def void test03_twoKNodesWithAnEdge() { + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '''.equalsSVGwithEdgesFirstOf[ + setLayoutOption(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 25d) + val n1 = addNode("node1", Colors.RED) + val n2 = addNode("node2", Colors.BLUE) + + createInitializedEdge() => [ + source = n1 + target = n2 + + semanticData = KlighdSemanticDiagramData.of(it).putID("theEdge") + ]; + ] + } + + @Test + def void test03b_twoKNodesWithAnEdgeAndSemanticData() { + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '''.equalsSVGwithEdgesFirstOf[ + setLayoutOption(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 25d) + val n1 = addNode("node1", Colors.RED).semanticData = KlighdSemanticDiagramData.of(it).putID("node1") + val n2 = addNode("node2", Colors.BLUE).semanticData = KlighdSemanticDiagramData.of(it).putID("node2") + + createInitializedEdge() => [ + source = n1 + target = n2 + + semanticData = KlighdSemanticDiagramData.of(it).putID("theEdge") + ]; + ] + } + + + def private static KNode addNode(KNode it, String id, Colors bgColor) { + val extension contExts = _kContainerRenderingExtensions + return addKNodeWithSizeOf(100, 100) => [ + addRectangle() => [ + invisible = true; + addRectangle().setAreaPlacementData().from(LEFT, -10, 0, TOP, -10, 0).to(RIGHT, -10, 0, BOTTOM, -10, 0).drawAsBackgroundFigure().setBackground(bgColor).setSemanticData( + KlighdSemanticDiagramData.of(it).putID((id !== null ? id + "-" : "") + "backgroundFigure") + ); + addRectangle().setBackground(Colors.WHITE).setSemanticData( + KlighdSemanticDiagramData.of(it).putID((id !== null ? id + "-" : "") + "non-backgroundFigure") + ); + ] + ] + } + + def private static equalsSVGwithEdgesFirstOf(CharSequence expectation, (KNode) => void viewModelBuilder) { + expectation.equalsSVGof(viewModelBuilder, true, false) + } +} diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingWithForegroundNodesTest.xtend b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingWithForegroundNodesTest.xtend new file mode 100644 index 000000000..a2efa9132 --- /dev/null +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingWithForegroundNodesTest.xtend @@ -0,0 +1,256 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2020 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package de.cau.cs.kieler.klighd.piccolo.test.freehep + +import de.cau.cs.kieler.klighd.kgraph.KNode +import de.cau.cs.kieler.klighd.krendering.Colors +import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions +import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions +import de.cau.cs.kieler.klighd.util.KlighdSemanticDiagramData +import org.eclipse.elk.alg.layered.options.LayeredOptions +import org.eclipse.elk.core.options.CoreOptions +import org.eclipse.swt.widgets.Display +import org.junit.BeforeClass +import org.junit.FixMethodOrder +import org.junit.Test + +import static de.cau.cs.kieler.klighd.kgraph.util.KGraphUtil.* + +import static extension de.cau.cs.kieler.klighd.piccolo.test.freehep.FreeHEPSVGOffscreenRenderingTest.* +import static extension de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses.* + +/** + * @author chsch + */ + @FixMethodOrder(NAME_ASCENDING) +class FreeHEPSVGOffscreenRenderingWithForegroundNodesTest { + + static extension KRenderingExtensions = new KRenderingExtensions() + + static extension KContainerRenderingExtensions = new KContainerRenderingExtensions() + + @BeforeClass + def static void initDisplay() { + Display.getDefault() + } + + // Note: The diagrams are auto-arranged by ELK Layered with the root's padding being fixed to 'ElkPadding(10)'. + // This way the additional space required by the oversized background figures exactly matches the padding, and + // the left-most located node's drawing starts at (x,y) == (0,0). Thus, no whitespace is trimmed/compensated + // on the left and top border by the exporter. (The exporter accumulates the nested transforms.) + // Otherwise we would see unexpected transforms on the KEdges' drawings, as their points always refer to the + // coordinate system of the source and target nodes' common parent node. + + @Test + def void test01_twoKNodesWithAnEdgeAndSomeForegroundNode() { + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '''.equalsSVGof[ + setLayoutOption(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 100d) + val n1 = addNode("node1", Colors.RED) + val n2 = addNode("node2", Colors.BLUE) + + addNode("node3", Colors.GREEN) => [ + setSize(60, 60) + setPos(130, 30) + setProperty(CoreOptions.NO_LAYOUT, true) + drawAsForegroundNode() + ] + + createInitializedEdge() => [ + source = n1 + target = n2 + + semanticData = KlighdSemanticDiagramData.of(it).putID("theEdge") + ]; + + + ] + } + + @Test + def void test01b_twoKNodesWithAnEdgeAndSomeForegroundNodeAndSemanticData() { + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '''.equalsSVGof[ + setLayoutOption(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 100d) + val n1 = addNode("node1", Colors.RED).semanticData = KlighdSemanticDiagramData.of(it).putID("node1") + val n2 = addNode("node2", Colors.BLUE).semanticData = KlighdSemanticDiagramData.of(it).putID("node2") + + createInitializedEdge() => [ + source = n1 + target = n2 + + semanticData = KlighdSemanticDiagramData.of(it).putID("theEdge") + ]; + + addNode("node3", Colors.GREEN) => [ + setSize(60, 60) + setPos(130, 30) + setProperty(CoreOptions.NO_LAYOUT, true) + drawAsForegroundNode() + semanticData = KlighdSemanticDiagramData.of(it).putID("node3") + ] + ] + } + + + def private static KNode addNode(KNode it, String id, Colors bgColor) { + val extension contExts = _kContainerRenderingExtensions + return addKNodeWithSizeOf(100, 100) => [ + addRectangle() => [ + invisible = true; + addRectangle().setAreaPlacementData().from(LEFT, -10, 0, TOP, -10, 0).to(RIGHT, -10, 0, BOTTOM, -10, 0).drawAsBackgroundFigure().setBackground(bgColor).setSemanticData( + KlighdSemanticDiagramData.of(it).putID((id !== null ? id + "-" : "") + "backgroundFigure") + ); + addRectangle().setBackground(Colors.WHITE).setSemanticData( + KlighdSemanticDiagramData.of(it).putID((id !== null ? id + "-" : "") + "non-backgroundFigure") + ); + ] + ] + } +}