--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2011 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.g2d.nodes.connection;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.RenderingHints;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.event.KeyEvent;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.lang.reflect.Constructor;\r
+import java.util.Collection;\r
+import java.util.Map;\r
+\r
+import org.simantics.diagram.connection.RouteGraph;\r
+import org.simantics.diagram.connection.RouteLine;\r
+import org.simantics.diagram.connection.RouteLink;\r
+import org.simantics.diagram.connection.RoutePoint;\r
+import org.simantics.diagram.connection.RouteTerminal;\r
+import org.simantics.diagram.connection.actions.IAction;\r
+import org.simantics.diagram.connection.actions.IReconnectAction;\r
+import org.simantics.diagram.connection.actions.MoveAction;\r
+import org.simantics.diagram.connection.actions.ReconnectLineAction;\r
+import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
+import org.simantics.diagram.connection.rendering.BasicConnectionStyle;\r
+import org.simantics.diagram.connection.rendering.ConnectionStyle;\r
+import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
+import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;\r
+import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
+import org.simantics.diagram.connection.splitting.SplittedRouteGraph;\r
+import org.simantics.scenegraph.INode;\r
+import org.simantics.scenegraph.ISelectionPainterNode;\r
+import org.simantics.scenegraph.g2d.G2DNode;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.IG2DNode;\r
+import org.simantics.scenegraph.g2d.events.EventTypes;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
+import org.simantics.scenegraph.g2d.events.command.Commands;\r
+import org.simantics.scenegraph.g2d.nodes.GridNode;\r
+import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
+import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Action;\r
+import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Pick;\r
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
+import org.simantics.scenegraph.utils.GeometryUtils;\r
+import org.simantics.scenegraph.utils.InitValueSupport;\r
+import org.simantics.scenegraph.utils.NodeUtil;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, InitValueSupport {\r
+\r
+ private static final long serialVersionUID = -917194130412280965L;\r
+\r
+ private static final double TOLERANCE = IAction.TOLERANCE;\r
+ private static final Stroke SELECTION_STROKE = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
+ private static final Color SELECTION_COLOR = new Color(255, 0, 255, 96);\r
+\r
+ private static final HighlightActionPointsAction highlightActions = new HighlightActionPointsAction(null);\r
+\r
+ protected RouteGraph rg;\r
+ protected IRouteGraphRenderer baseRenderer;\r
+ protected IRouteGraphRenderer renderer;\r
+ protected double pickTolerance = TOLERANCE;\r
+ protected boolean editable = true;\r
+ private boolean branchable = true;\r
+\r
+ protected IRouteGraphListener rgListener;\r
+ protected RouteGraphDelta rgDelta;\r
+\r
+ protected transient double mouseX;\r
+ protected transient double mouseY;\r
+ protected transient Point2D pt = new Point2D.Double();\r
+\r
+ protected transient MoveAction dragAction;\r
+ protected transient IAction currentAction;\r
+ protected transient Rectangle2D bounds;\r
+\r
+ /**\r
+ * Dynamic color for connection rendering.\r
+ */\r
+ protected transient Color dynamicColor;\r
+\r
+ /**\r
+ * Dynamic stroke for connection rendering.\r
+ */\r
+ protected transient Stroke dynamicStroke;\r
+\r
+ protected transient Path2D selectionPath = new Path2D.Double();\r
+ protected transient Stroke selectionStroke = null;\r
+\r
+ protected transient boolean highlightActionsEnabled = false;\r
+ protected transient AffineTransform lastViewTransform = null;\r
+\r
+ /**\r
+ * x = NaN is used to indicate that possible branch point should not be\r
+ * rendered but interaction has not ended yet.\r
+ */\r
+ protected transient Point2D newBranchPointPosition = null;\r
+\r
+\r
+ protected transient Map<Object,ILineEndStyle> dynamicStyles = null;\r
+ \r
+ @Override\r
+ public void initValues() {\r
+ dynamicColor = null;\r
+ wrapRenderer();\r
+ }\r
+\r
+ @PropertySetter("color")\r
+ @SyncField(value = {"dynamicColor"})\r
+ public void setDynamicColor(Color color) {\r
+ this.dynamicColor = color;\r
+ wrapRenderer();\r
+ }\r
+\r
+ @PropertySetter("width")\r
+ @SyncField("dynamicStroke")\r
+ public void setDynamicStroke(Stroke stroke) {\r
+ this.dynamicStroke = stroke;\r
+ wrapRenderer();\r
+ createSelectionStroke();\r
+ }\r
+ \r
+ @SyncField(value = {"dynamicStyles"})\r
+ public void setDynamicLineEnd(RouteTerminal terminal, ILineEndStyle style) {\r
+ if (dynamicStyles == null)\r
+ dynamicStyles = new THashMap<Object, ILineEndStyle>();\r
+ terminal.setDynamicStyle(style);\r
+ if (terminal.getData() != null) {\r
+ if (style != null)\r
+ dynamicStyles.put(terminal.getData(),style);\r
+ else\r
+ dynamicStyles.remove(terminal.getData());\r
+ }\r
+ }\r
+ \r
+ private void updateLineEnds() {\r
+ if (dynamicStyles == null)\r
+ return;\r
+ for (RouteTerminal t : rg.getTerminals()) {\r
+ if (t.getData() == null)\r
+ continue;\r
+ ILineEndStyle dynamicStyle = dynamicStyles.get(t.getData());\r
+ if (dynamicStyle != null)\r
+ t.setDynamicStyle(dynamicStyle);\r
+ }\r
+ }\r
+\r
+ @SyncField(value = {"rg"})\r
+ public void setRouteGraph(RouteGraph graph) {\r
+ this.rg = graph;\r
+ updateLineEnds();\r
+ updateBounds();\r
+ }\r
+\r
+ @SyncField(value = {"rgDelta"})\r
+ public void setRouteGraphDelta(RouteGraphDelta delta) {\r
+ this.rgDelta = delta;\r
+ }\r
+\r
+ @SyncField(value = {"renderer"})\r
+ public void setRenderer(IRouteGraphRenderer renderer) {\r
+\r
+ this.baseRenderer = renderer;\r
+ wrapRenderer();\r
+\r
+ createSelectionStroke(); \r
+ }\r
+ \r
+ private void createSelectionStroke() {\r
+ BasicConnectionStyle style = tryGetStyle();\r
+ selectionStroke = null;\r
+ if (style != null) {\r
+ BasicStroke stroke = (BasicStroke) style.getLineStroke();\r
+ if (stroke != null) {\r
+ float width = Math.max(stroke.getLineWidth() + 0.75f, stroke.getLineWidth()*1.3f);\r
+ selectionStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, stroke.getLineJoin());\r
+ }\r
+ } else {\r
+ selectionStroke = SELECTION_STROKE;\r
+ }\r
+ }\r
+ \r
+ private void wrapRenderer() {\r
+ \r
+ if(baseRenderer == null) {\r
+ renderer = null;\r
+ return;\r
+ }\r
+ \r
+ if(dynamicColor != null || dynamicStroke != null) {\r
+ BasicConnectionStyle baseStyle = (BasicConnectionStyle)tryGetStyle(baseRenderer);\r
+ try {\r
+ Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class);\r
+ renderer = new StyledRouteGraphRenderer(c.newInstance(\r
+ dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),\r
+ baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),\r
+ dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), \r
+ dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),\r
+ baseStyle.getDegeneratedLineLength()));\r
+ } catch (Exception e) {\r
+ renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(\r
+ dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),\r
+ baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),\r
+ dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), \r
+ dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),\r
+ baseStyle.getDegeneratedLineLength()));\r
+ }\r
+ \r
+ \r
+ } else {\r
+ renderer = baseRenderer;\r
+ }\r
+ \r
+ }\r
+\r
+ @SyncField(value = {"pickTolerance"})\r
+ public void setPickTolerance(double tolerance) {\r
+ this.pickTolerance = tolerance;\r
+ }\r
+\r
+ @SyncField(value = {"editable"})\r
+ public void setEditable(boolean editable) {\r
+ this.editable = editable;\r
+ }\r
+\r
+ @SyncField(value = {"branchable"})\r
+ public void setBranchable(boolean branchable) {\r
+ this.branchable = branchable;\r
+ }\r
+\r
+ public RouteGraph getRouteGraph() {\r
+ return rg;\r
+ }\r
+\r
+ public RouteGraphDelta getRouteGraphDelta() {\r
+ return rgDelta;\r
+ }\r
+\r
+ public IRouteGraphRenderer getRenderer() {\r
+ return renderer;\r
+ }\r
+\r
+ public boolean isEditable() {\r
+ return editable;\r
+ }\r
+\r
+ public boolean isBranchable() {\r
+ return branchable;\r
+ }\r
+ \r
+ public double getPickTolerance() {\r
+ return pickTolerance;\r
+ }\r
+\r
+ /**\r
+ * When in client-server mode, listener is only set on the server side and\r
+ * fireRouteGraphChanged will tell it when rg has changed.\r
+ * \r
+ * @param listener\r
+ */\r
+ public void setRouteGraphListener(IRouteGraphListener listener) {\r
+ this.rgListener = listener;\r
+ }\r
+\r
+ /**\r
+ * @param before\r
+ * @param after\r
+ * @return <code>true</code> if changes were fired\r
+ */\r
+ private boolean setRouteGraphAndFireChanges(RouteGraph before, RouteGraph after) {\r
+ RouteGraphDelta delta = new RouteGraphDelta(before, after);\r
+ if (!delta.isEmpty()) {\r
+ setRouteGraph(after);\r
+ setRouteGraphDelta(delta);\r
+ fireRouteGraphChanged(before, after, delta);\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @ServerSide\r
+ protected void fireRouteGraphChanged(RouteGraph before, RouteGraph after, RouteGraphDelta delta) {\r
+ if (rgListener != null) {\r
+ RouteGraphChangeEvent event = new RouteGraphChangeEvent(this, before, after, delta);\r
+ rgListener.routeGraphChanged(event);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void init() {\r
+ super.init();\r
+ addEventHandler(this);\r
+ }\r
+\r
+ @Override\r
+ public void cleanup() {\r
+ rgListener = null;\r
+ removeEventHandler(this);\r
+ super.cleanup();\r
+ }\r
+\r
+ protected boolean isSelected() {\r
+ return NodeUtil.isSelected(this, 1);\r
+ }\r
+\r
+ @Override\r
+ public void render(Graphics2D g) {\r
+ if (renderer == null)\r
+ return;\r
+\r
+ AffineTransform ot = null;\r
+ AffineTransform t = getTransform();\r
+ if (t != null && !t.isIdentity()) {\r
+ ot = g.getTransform();\r
+ g.transform(getTransform());\r
+ }\r
+\r
+ Object aaHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);\r
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);\r
+\r
+ boolean selected = NodeUtil.isSelected(this, 1);\r
+\r
+ if (currentAction != null) {\r
+ currentAction.render(g, renderer, mouseX, mouseY);\r
+ } else {\r
+ if (selected && selectionStroke != null) {\r
+ selectionPath.reset();\r
+ rg.getPath2D(selectionPath);\r
+ Shape selectionShape = selectionStroke.createStrokedShape(selectionPath);\r
+ g.setColor(SELECTION_COLOR);\r
+ g.fill(selectionShape);\r
+ }\r
+\r
+ renderer.render(g, rg);\r
+ if(selected)\r
+ renderer.renderGuides(g, rg);\r
+\r
+ if (selected && highlightActionsEnabled) {\r
+ // Needed for performing actions in #mouseClicked\r
+ this.lastViewTransform = g.getTransform();\r
+ highlightActions.setRouteGraph(rg);\r
+ highlightActions.render(g, renderer, mouseX, mouseY);\r
+ highlightActions.setRouteGraph(null);\r
+ }\r
+ }\r
+\r
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);\r
+\r
+ if (editable && branchable && newBranchPointPosition != null && !Double.isNaN(newBranchPointPosition.getX())) {\r
+ ConnectionStyle style = tryGetStyle();\r
+ if(style != null)\r
+ style.drawBranchPoint(g, newBranchPointPosition.getX(), newBranchPointPosition.getY());\r
+ }\r
+\r
+ if (ot != null)\r
+ g.setTransform(ot);\r
+ }\r
+ \r
+ private BasicConnectionStyle tryGetStyle() {\r
+ return tryGetStyle(renderer);\r
+ }\r
+\r
+ private BasicConnectionStyle tryGetStyle(IRouteGraphRenderer renderer) {\r
+ if (renderer instanceof StyledRouteGraphRenderer) {\r
+ ConnectionStyle cs = ((StyledRouteGraphRenderer) renderer).getStyle();\r
+ if (cs instanceof BasicConnectionStyle)\r
+ return (BasicConnectionStyle) cs;\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private double getSelectionStrokeWidth() {\r
+ if (selectionStroke instanceof BasicStroke) {\r
+ BasicStroke bs = (BasicStroke) selectionStroke;\r
+ return bs.getLineWidth();\r
+ }\r
+ return 1.0;\r
+ }\r
+\r
+ @Override\r
+ public Rectangle2D getBoundsInLocal() {\r
+ return bounds;\r
+ }\r
+\r
+ protected void updateBounds() {\r
+ Rectangle2D r = this.bounds;\r
+ if (r == null)\r
+ r = new Rectangle2D.Double();\r
+ this.bounds = calculateBounds(r);\r
+\r
+ // Need to expand to take stroke width into account.\r
+ double sw = getSelectionStrokeWidth() / 2;\r
+ GeometryUtils.expandRectangle(this.bounds, sw, sw);\r
+ }\r
+\r
+ protected Rectangle2D calculateBounds(Rectangle2D rect) {\r
+ RouteGraph rg = this.rg;\r
+ if (currentAction instanceof MoveAction)\r
+ rg = ((MoveAction) currentAction).getRouteGraph();\r
+ rg.getBounds(rect);\r
+ return rect;\r
+ }\r
+\r
+ protected void getMouseLocalPos(MouseEvent e) {\r
+ //System.out.println("m: " + e.controlPosition);\r
+ pt.setLocation(e.controlPosition);\r
+ //System.out.println("parent: " + pt);\r
+ pt = NodeUtil.worldToLocal(this, pt, pt);\r
+ //System.out.println("local: " + pt);\r
+ mouseX = pt.getX();\r
+ mouseY = pt.getY();\r
+ }\r
+\r
+ @Override\r
+ protected boolean mouseDragged(MouseDragBegin e) {\r
+ if (dragAction != null && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.button == MouseEvent.LEFT_BUTTON) {\r
+ currentAction = dragAction;\r
+ dragAction = null;\r
+ }\r
+ return updateCurrentAction(e, true);\r
+ }\r
+\r
+ @Override\r
+ protected boolean mouseMoved(MouseMovedEvent e) {\r
+ //System.out.println("mouse moved: " + e);\r
+\r
+ // Handle connection branching visualization.\r
+ getMouseLocalPos(e);\r
+ if (newBranchPointPosition == null && e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {\r
+ if (bounds.contains(mouseX, mouseY)) {\r
+ newBranchPointPosition = new Point2D.Double(Double.NaN, Double.NaN);\r
+ }\r
+ }\r
+ if (newBranchPointPosition != null) {\r
+ RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);\r
+ if (line != null) {\r
+ newBranchPointPosition.setLocation(mouseX, mouseY);\r
+ SplittedRouteGraph.snapToLine(newBranchPointPosition, line);\r
+ repaint();\r
+ } else if (!Double.isNaN(newBranchPointPosition.getX())) {\r
+ newBranchPointPosition.setLocation(Double.NaN, Double.NaN);\r
+ repaint();\r
+ }\r
+ }\r
+\r
+ // Make sure that highlight action rendering is according to mouse hover.\r
+ if (highlightActionsEnabled) {\r
+ if (NodeUtil.isSelected(this, 1)) {\r
+ repaint();\r
+ }\r
+ }\r
+\r
+ return updateCurrentAction(e, false);\r
+ }\r
+\r
+ protected boolean updateCurrentAction(MouseEvent e, boolean updateMousePos) {\r
+ boolean oldHighlight = highlightActionsEnabled;\r
+ highlightActionsEnabled = e.hasAllModifiers(MouseEvent.CTRL_MASK);\r
+ if (oldHighlight != highlightActionsEnabled)\r
+ repaint();\r
+\r
+ if (currentAction != null) {\r
+ if (updateMousePos)\r
+ getMouseLocalPos(e);\r
+ updateBounds();\r
+\r
+ // Repaint, but only if absolutely necessary.\r
+ if (currentAction instanceof MoveAction || bounds.contains(mouseX, mouseY))\r
+ repaint();\r
+\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ protected boolean mouseClicked(MouseClickEvent e) {\r
+ if (!editable)\r
+ return false;\r
+\r
+ if (e.button == MouseEvent.LEFT_BUTTON) {\r
+ if (isSelected() && highlightActionsEnabled) {\r
+ // Reconnection / segment deletion only available for branched connections. \r
+ if (rg.getTerminals().size() > 2) {\r
+ Pick pick = highlightActions.pickAction(rg, lastViewTransform, mouseX, mouseY);\r
+ if (pick.hasAction(Action.REMOVE)) {\r
+ RemoveLineAction remove = RemoveLineAction.perform(rg, pick.line.getLine(), mouseX, mouseY);\r
+ if (remove != null) {\r
+ setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());\r
+ repaint();\r
+ return true;\r
+ }\r
+ }\r
+ if (pick.hasAction(Action.RECONNECT)) {\r
+ currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);\r
+ if (currentAction != null) {\r
+ repaint();\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {\r
+ if (!editable)\r
+ return false;\r
+\r
+ if (e.button == MouseEvent.LEFT_BUTTON) {\r
+ // Visualize new branch point no longer.\r
+ newBranchPointPosition = null;\r
+\r
+ getMouseLocalPos(e);\r
+ dragAction = null;\r
+// if(currentAction instanceof HighlightActionPointsAction) {\r
+// RemoveLineAction remove = RemoveLineAction.perform(rg, mouseX, mouseY);\r
+// if (remove != null) {\r
+// setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());\r
+// repaint();\r
+// } else {\r
+// currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);\r
+// if (currentAction != null)\r
+// repaint();\r
+// }\r
+// }\r
+// else\r
+ if(currentAction instanceof IReconnectAction) {\r
+ RouteGraph originalRg = rg.copy();\r
+ ((IReconnectAction)currentAction).finish(mouseX, mouseY);\r
+ currentAction = null;\r
+\r
+ setRouteGraphAndFireChanges(originalRg, rg);\r
+\r
+ currentAction = null;\r
+ repaint();\r
+ return true;\r
+ }\r
+ else {\r
+ if (!allowConnectionRerouting()) {\r
+ return false;\r
+ }\r
+ //System.out.println("move action");\r
+ dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, pickTolerance, moveFilter, getSnapAdvisor());\r
+ //System.out.println("DRAG ACTION: " + dragAction);\r
+ }\r
+\r
+ //System.out.println(this + " NEW action: " + currentAction);\r
+ if (currentAction != null)\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Checks the selections data node in the scene graph for any links \r
+ * @return\r
+ */\r
+ private boolean allowConnectionRerouting() {\r
+ final int maxOtherNodesSelected = 1;\r
+\r
+ INode selections = NodeUtil.tryLookup(this, "selections");\r
+ if (!(selections instanceof G2DParentNode))\r
+ return true;\r
+ G2DParentNode p = (G2DParentNode) selections;\r
+ for (IG2DNode selection : p.getNodes()) {\r
+ if (!(selection instanceof G2DParentNode))\r
+ continue;\r
+\r
+ G2DParentNode sp = (G2DParentNode) selection;\r
+ Collection<IG2DNode> links = sp.getNodes();\r
+ if (links.isEmpty())\r
+ return true;\r
+ int othersSelected = 0;\r
+ for (IG2DNode link : links) {\r
+ if (link instanceof LinkNode) {\r
+ INode node = ((LinkNode) link).getDelegate();\r
+ if (!NodeUtil.isParentOf(node, this)) {\r
+ othersSelected++;\r
+ if (othersSelected > maxOtherNodesSelected)\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ if (othersSelected > maxOtherNodesSelected)\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ protected ISnapAdvisor getSnapAdvisor() {\r
+ GridNode grid = lookupNode(GridNode.GRID_NODE_ID, GridNode.class);\r
+ return grid != null ? grid.getSnapAdvisor() : null;\r
+ }\r
+\r
+ MoveAction.TargetFilter moveFilter = new MoveAction.TargetFilter() {\r
+ @Override\r
+ public boolean accept(Object target) {\r
+ return (target instanceof RouteLine) || (target instanceof RouteLink);\r
+ }\r
+ };\r
+\r
+ @Override\r
+ protected boolean handleCommand(CommandEvent e) {\r
+ /*if (Commands.DELETE.equals(e.command)) {\r
+ Object target = rg.pick(mouseX, mouseY, pickTolerance);\r
+ return deleteTarget(target);\r
+ } else if (Commands.SPLIT_CONNECTION.equals(e.command)) {\r
+ Object target = rg.pick(mouseX, mouseY, pickTolerance);\r
+ return splitTarget(target);\r
+ } else */\r
+ if (Commands.CANCEL.equals(e.command)) {\r
+ return cancelCurrentAction();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
+ if (currentAction instanceof MoveAction) {\r
+ MoveAction move = (MoveAction) currentAction;\r
+ RouteGraph originalRg = rg.copy();\r
+ move.finish(mouseX, mouseY);\r
+\r
+ setRouteGraphAndFireChanges(originalRg, rg);\r
+\r
+ currentAction = null;\r
+ repaint();\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ protected boolean keyPressed(KeyPressedEvent e) {\r
+ if (!editable)\r
+ return false;\r
+\r
+ if (!e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.keyCode == KeyEvent.VK_S) {\r
+ Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES | RouteGraph.PICK_TRANSIENT_LINES);\r
+ return splitTarget(target);\r
+ }\r
+ else if (!e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK | MouseEvent.CTRL_MASK) && (e.keyCode == KeyEvent.VK_R || e.keyCode == KeyEvent.VK_D)) {\r
+ Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES);\r
+ return deleteTarget(target);\r
+ }\r
+ else if (e.keyCode == KeyEvent.VK_ESCAPE) {\r
+ return cancelCurrentAction();\r
+ }\r
+// else if (e.keyCode == KeyEvent.VK_D) {\r
+// if (target instanceof RouteTerminal) {\r
+// RouteTerminal terminal = (RouteTerminal) target;\r
+// RouteGraph before = rg.copy();\r
+// rg.toggleDirectLines(terminal);\r
+// setRouteGraphAndFireChanges(before, rg);\r
+// repaint();\r
+// }\r
+// }\r
+// else if (target != null && e.getKeyCode() == KeyEvent.VK_P) {\r
+// rg.print();\r
+// }\r
+ else if (e.keyCode == KeyEvent.VK_CONTROL) {\r
+ highlightActionsEnabled = true;\r
+ repaint();\r
+ }\r
+ else if (e.keyCode == KeyEvent.VK_ALT) {\r
+ // Begin connection branching visualization.\r
+ RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);\r
+ if (branchable && line != null) {\r
+ newBranchPointPosition = new Point2D.Double(mouseX, mouseY);\r
+ SplittedRouteGraph.snapToLine(newBranchPointPosition, line);\r
+ repaint();\r
+ }\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ protected boolean keyReleased(KeyReleasedEvent e) {\r
+ if (e.keyCode == KeyEvent.VK_ALT) {\r
+ // End connection branching visualization.\r
+ if (newBranchPointPosition != null) {\r
+ newBranchPointPosition = null;\r
+ repaint();\r
+ }\r
+ }\r
+ if (e.keyCode == KeyEvent.VK_CONTROL) {\r
+ highlightActionsEnabled = false;\r
+ repaint();\r
+ }\r
+ return false;\r
+ }\r
+\r
+\r
+ private boolean cancelCurrentAction() {\r
+ if (currentAction != null) {\r
+ currentAction = null;\r
+ repaint();\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private boolean splitTarget(Object target) {\r
+ if (target instanceof RouteLine) {\r
+ RouteLine rLine = (RouteLine)target;\r
+ RouteGraph before = rg.copy();\r
+ rg.split(rLine, rLine.isHorizontal() ? mouseX : mouseY);\r
+ setRouteGraphAndFireChanges(before, rg);\r
+ repaint();\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private boolean deleteTarget(Object target) {\r
+ boolean changed = false;\r
+ if (target instanceof RouteLine) {\r
+ RouteLine line = (RouteLine) target;\r
+ RouteGraph before = rg.copy();\r
+ rg.merge(line);\r
+ changed = setRouteGraphAndFireChanges(before, rg);\r
+ }\r
+ else if (target instanceof RouteLink) {\r
+ RouteGraph before = rg.copy();\r
+ rg.deleteCorner((RouteLink) target);\r
+ changed = setRouteGraphAndFireChanges(before, rg);\r
+ }\r
+// else if (target instanceof RouteTerminal) {\r
+// RouteGraph before = rg.copy();\r
+// rg.remove((RouteTerminal) target);\r
+// changed = setRouteGraphAndFireChanges(before, rg);\r
+// }\r
+ if (changed)\r
+ repaint();\r
+ return changed;\r
+ }\r
+\r
+ /**\r
+ * A version of MoveAction that snaps movements using the specified\r
+ * ISnapAdvisor.\r
+ */\r
+ static class SnappingMoveAction extends MoveAction {\r
+\r
+ private ISnapAdvisor snapAdvisor;\r
+ private Point2D point = new Point2D.Double();\r
+\r
+ public SnappingMoveAction(RouteGraph rg, Object target, ISnapAdvisor snapAdvisor) {\r
+ super(rg, target);\r
+ this.snapAdvisor = snapAdvisor;\r
+ }\r
+\r
+ protected void move(RouteGraph rg, Object target, double x, double y) {\r
+ point.setLocation(x, y);\r
+ snapAdvisor.snap(point);\r
+ super.move(rg, target, point.getX(), point.getY());\r
+ }\r
+\r
+ public static MoveAction create(RouteGraph rg, double x, double y, double tolerance, TargetFilter filter, ISnapAdvisor snapAdvisor) {\r
+ Object target = rg.pick(x, y, tolerance, RouteGraph.PICK_LINES | RouteGraph.PICK_INTERIOR_POINTS);\r
+ if (target != null && (filter == null || filter.accept(target))) {\r
+ if (snapAdvisor != null)\r
+ return new SnappingMoveAction(rg, target, snapAdvisor);\r
+ return new MoveAction(rg, target);\r
+ }\r
+ return null;\r
+ }\r
+\r
+ }\r
+\r
+ @Override\r
+ public int getEventMask() {\r
+ return EventTypes.CommandMask | EventTypes.KeyMask | EventTypes.MouseMask;\r
+ }\r
+\r
+}\r