1 /*******************************************************************************
2 * Copyright (c) 2011 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes.connection;
14 import java.awt.BasicStroke;
15 import java.awt.Color;
16 import java.awt.Graphics2D;
17 import java.awt.RenderingHints;
18 import java.awt.Shape;
19 import java.awt.Stroke;
20 import java.awt.event.KeyEvent;
21 import java.awt.geom.AffineTransform;
22 import java.awt.geom.Path2D;
23 import java.awt.geom.Point2D;
24 import java.awt.geom.Rectangle2D;
25 import java.lang.reflect.Constructor;
26 import java.util.Collection;
27 import java.util.List;
30 import org.simantics.diagram.connection.RouteGraph;
31 import org.simantics.diagram.connection.RouteLine;
32 import org.simantics.diagram.connection.RouteLink;
33 import org.simantics.diagram.connection.RouteTerminal;
34 import org.simantics.diagram.connection.actions.IAction;
35 import org.simantics.diagram.connection.actions.IReconnectAction;
36 import org.simantics.diagram.connection.actions.MoveAction;
37 import org.simantics.diagram.connection.actions.ReconnectLineAction;
38 import org.simantics.diagram.connection.delta.RouteGraphDelta;
39 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
40 import org.simantics.diagram.connection.rendering.ConnectionStyle;
41 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
42 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;
43 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
44 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
45 import org.simantics.scenegraph.INode;
46 import org.simantics.scenegraph.ISelectionPainterNode;
47 import org.simantics.scenegraph.g2d.G2DNode;
48 import org.simantics.scenegraph.g2d.G2DParentNode;
49 import org.simantics.scenegraph.g2d.G2DSceneGraph;
50 import org.simantics.scenegraph.g2d.IG2DNode;
51 import org.simantics.scenegraph.g2d.events.EventTypes;
52 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
53 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
54 import org.simantics.scenegraph.g2d.events.MouseEvent;
55 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
56 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
57 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
58 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
59 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
60 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
61 import org.simantics.scenegraph.g2d.events.command.Commands;
62 import org.simantics.scenegraph.g2d.nodes.GridNode;
63 import org.simantics.scenegraph.g2d.nodes.LinkNode;
64 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
65 import org.simantics.scenegraph.g2d.nodes.SVGNodeAssignment;
66 import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Action;
67 import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Pick;
68 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
69 import org.simantics.scenegraph.utils.ColorUtil;
70 import org.simantics.scenegraph.utils.GeometryUtils;
71 import org.simantics.scenegraph.utils.InitValueSupport;
72 import org.simantics.scenegraph.utils.NodeUtil;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
76 import gnu.trove.map.hash.THashMap;
79 * @author Tuukka Lehtonen
81 public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, InitValueSupport {
83 private static final Logger LOGGER = LoggerFactory.getLogger(RouteGraphNode.class);
85 private static final long serialVersionUID = -917194130412280965L;
87 private static final double TOLERANCE = IAction.TOLERANCE;
88 private static final Stroke SELECTION_STROKE = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
89 private static final Color SELECTION_COLOR = new Color(255, 0, 255, 96);
91 private static final HighlightActionPointsAction highlightActions = new HighlightActionPointsAction(null);
93 protected RouteGraph rg;
94 protected IRouteGraphRenderer baseRenderer;
95 protected IRouteGraphRenderer renderer;
96 protected double pickTolerance = TOLERANCE;
97 protected boolean editable = true;
98 private boolean branchable = true;
100 protected IRouteGraphListener rgListener;
101 protected RouteGraphDelta rgDelta;
103 protected transient double mouseX;
104 protected transient double mouseY;
105 protected transient Point2D pt = new Point2D.Double();
107 protected transient MoveAction dragAction;
108 protected transient IAction currentAction;
109 protected transient Rectangle2D bounds;
112 * Dynamic color for connection rendering.
114 protected transient Color dynamicColor;
117 * Dynamic stroke for connection rendering.
119 protected transient Stroke dynamicStroke;
121 protected transient Path2D selectionPath = new Path2D.Double();
122 protected transient Stroke selectionStroke = null;
124 protected transient boolean highlightActionsEnabled = false;
125 protected transient AffineTransform lastViewTransform = null;
128 * x = NaN is used to indicate that possible branch point should not be
129 * rendered but interaction has not ended yet.
131 protected transient Point2D newBranchPointPosition = null;
134 protected transient Map<Object,ILineEndStyle> dynamicStyles = null;
136 private transient boolean ignoreSelection = false;
139 public void initValues() {
144 private static float tryParseFloat(String s, float def) {
146 return Float.parseFloat(s);
147 } catch (NumberFormatException e) {
148 LOGGER.error("Could not parse '" + s + "' into float.");
154 * 1.0 BUTT MITER 1.0 0.0
156 private static Stroke parseStroke(String definition) {
159 int cap = BasicStroke.CAP_BUTT;
160 int join = BasicStroke.JOIN_MITER;
161 float miterLimit = 1.0f;
162 float[] dash = { 1, 0};
163 float dash_phase = 0;
165 String[] parts = definition.split(" ");
167 if(parts.length > 0) {
168 width = tryParseFloat(parts[0], width);
170 if(parts.length > 1) {
171 if("BUTT".equals(parts[1])) cap = BasicStroke.CAP_BUTT;
172 else if("ROUND".equals(parts[1])) cap = BasicStroke.CAP_ROUND;
173 else if("SQUARE".equals(parts[1])) cap = BasicStroke.CAP_SQUARE;
175 if(parts.length > 2) {
176 if("BEVEL".equals(parts[2])) cap = BasicStroke.JOIN_BEVEL;
177 else if("MITER".equals(parts[2])) cap = BasicStroke.JOIN_MITER;
178 else if("ROUND".equals(parts[2])) cap = BasicStroke.JOIN_ROUND;
180 if(parts.length > 3) {
181 miterLimit = tryParseFloat(parts[3], miterLimit);
183 if(parts.length > 4) {
184 dash_phase = tryParseFloat(parts[4], dash_phase);
186 if(parts.length > 6) {
187 dash = new float[parts.length - 5];
188 for(int i=5;i<parts.length;i++) {
189 dash[i-5] = tryParseFloat(parts[i], 1.0f);
195 return new BasicStroke(width, cap, join, miterLimit, dash, dash_phase);
196 } catch (IllegalArgumentException e) {
197 return new BasicStroke();
202 public void setAssignments(List<SVGNodeAssignment> assignments) {
203 for(SVGNodeAssignment ass : assignments) {
204 if("dynamicColor".equals(ass.elementId)) {
205 setDynamicColor(ColorUtil.hexColor(ass.value));
206 } else if("dynamicStroke".equals(ass.elementId)) {
207 setDynamicStroke(parseStroke(ass.value));
212 public void setIgnoreSelection(boolean value) {
213 ignoreSelection = value;
216 public boolean getIgnoreSelection() {
217 return ignoreSelection;
220 @PropertySetter("color")
221 @SyncField(value = {"dynamicColor"})
222 public void setDynamicColor(Color color) {
223 this.dynamicColor = color;
227 @PropertySetter("width")
228 @SyncField("dynamicStroke")
229 public void setDynamicStroke(Stroke stroke) {
230 this.dynamicStroke = stroke;
232 createSelectionStroke();
235 @SyncField(value = {"dynamicStyles"})
236 public void setDynamicLineEnd(RouteTerminal terminal, ILineEndStyle style) {
237 if (dynamicStyles == null)
238 dynamicStyles = new THashMap<Object, ILineEndStyle>();
239 terminal.setDynamicStyle(style);
240 if (terminal.getData() != null) {
242 dynamicStyles.put(terminal.getData(),style);
244 dynamicStyles.remove(terminal.getData());
248 private void updateLineEnds() {
249 if (dynamicStyles == null)
251 for (RouteTerminal t : rg.getTerminals()) {
252 if (t.getData() == null)
254 ILineEndStyle dynamicStyle = dynamicStyles.get(t.getData());
255 if (dynamicStyle != null)
256 t.setDynamicStyle(dynamicStyle);
260 @SyncField(value = {"rg"})
261 public void setRouteGraph(RouteGraph graph) {
267 @SyncField(value = {"rgDelta"})
268 public void setRouteGraphDelta(RouteGraphDelta delta) {
269 this.rgDelta = delta;
272 @SyncField(value = {"renderer"})
273 public void setRenderer(IRouteGraphRenderer renderer) {
275 this.baseRenderer = renderer;
278 createSelectionStroke();
281 private void createSelectionStroke() {
282 BasicConnectionStyle style = tryGetStyle();
283 selectionStroke = null;
285 BasicStroke stroke = (BasicStroke) style.getLineStroke();
286 if (stroke != null) {
287 float width = Math.max(stroke.getLineWidth() + 0.75f, stroke.getLineWidth()*1.3f);
288 selectionStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, stroke.getLineJoin());
291 // TODO: support AggregateConnectionStyle
292 selectionStroke = SELECTION_STROKE;
296 private void wrapRenderer() {
298 if(baseRenderer == null) {
303 if(dynamicColor != null || dynamicStroke != null) {
304 BasicConnectionStyle baseStyle = (BasicConnectionStyle)tryGetStyle(baseRenderer);
305 if (baseStyle != null) {
307 Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class, double.class, double.class);
308 renderer = new StyledRouteGraphRenderer(c.newInstance(
309 dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
310 baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
311 dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
312 dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
313 baseStyle.getDegeneratedLineLength(), baseStyle.getRounding(), baseStyle.getOffset()));
314 } catch (Exception e) {
315 renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(
316 dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
317 baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
318 dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
319 dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
320 baseStyle.getDegeneratedLineLength(), baseStyle.getRounding(), baseStyle.getOffset()));
323 // TODO: support AggregateConnectionStyle
324 renderer = baseRenderer;
328 renderer = baseRenderer;
333 @SyncField(value = {"pickTolerance"})
334 public void setPickTolerance(double tolerance) {
335 this.pickTolerance = tolerance;
338 @SyncField(value = {"editable"})
339 public void setEditable(boolean editable) {
340 this.editable = editable;
343 @SyncField(value = {"branchable"})
344 public void setBranchable(boolean branchable) {
345 this.branchable = branchable;
348 public RouteGraph getRouteGraph() {
352 public RouteGraphDelta getRouteGraphDelta() {
356 public IRouteGraphRenderer getRenderer() {
360 public boolean isEditable() {
364 public boolean isBranchable() {
368 public double getPickTolerance() {
369 return pickTolerance;
373 * When in client-server mode, listener is only set on the server side and
374 * fireRouteGraphChanged will tell it when rg has changed.
378 public void setRouteGraphListener(IRouteGraphListener listener) {
379 this.rgListener = listener;
385 * @return <code>true</code> if changes were fired
387 public boolean setRouteGraphAndFireChanges(RouteGraph before, RouteGraph after) {
388 RouteGraphDelta delta = new RouteGraphDelta(before, after);
389 if (!delta.isEmpty()) {
390 setRouteGraph(after);
391 setRouteGraphDelta(delta);
392 fireRouteGraphChanged(before, after, delta);
399 protected void fireRouteGraphChanged(RouteGraph before, RouteGraph after, RouteGraphDelta delta) {
400 if (rgListener != null) {
401 RouteGraphChangeEvent event = new RouteGraphChangeEvent(this, before, after, delta);
402 rgListener.routeGraphChanged(event);
406 public void showBranchPoint(Point2D p) {
407 newBranchPointPosition = p;
413 addEventHandler(this);
417 public void cleanup() {
419 removeEventHandler(this);
423 protected boolean isSelected() {
424 return NodeUtil.isSelected(this, 1);
428 public void render(Graphics2D g) {
429 if (renderer == null)
432 AffineTransform ot = null;
433 AffineTransform t = getTransform();
434 if (t != null && !t.isIdentity()) {
435 ot = g.getTransform();
436 g.transform(getTransform());
439 Object aaHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
440 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
442 boolean selected = ignoreSelection ? false : NodeUtil.isSelected(this, 1);
444 rg.updateTerminals();
446 if (currentAction != null) {
447 currentAction.render(g, renderer, mouseX, mouseY);
449 if (selected && selectionStroke != null) {
450 selectionPath.reset();
451 rg.getPath2D(selectionPath);
452 Shape selectionShape = selectionStroke.createStrokedShape(selectionPath);
453 g.setColor(SELECTION_COLOR);
454 g.fill(selectionShape);
457 renderer.render(g, rg);
459 renderer.renderGuides(g, rg);
461 if (selected && highlightActionsEnabled) {
462 // Needed for performing actions in #mouseClicked
463 this.lastViewTransform = g.getTransform();
464 highlightActions.setRouteGraph(rg);
465 highlightActions.render(g, renderer, mouseX, mouseY);
466 highlightActions.setRouteGraph(null);
470 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);
472 if (editable && branchable && newBranchPointPosition != null && !Double.isNaN(newBranchPointPosition.getX())) {
473 ConnectionStyle style = tryGetStyle();
475 style.drawBranchPoint(g, newBranchPointPosition.getX(), newBranchPointPosition.getY());
482 private BasicConnectionStyle tryGetStyle() {
483 return tryGetStyle(renderer);
486 private BasicConnectionStyle tryGetStyle(IRouteGraphRenderer renderer) {
487 if (renderer instanceof StyledRouteGraphRenderer) {
488 ConnectionStyle cs = ((StyledRouteGraphRenderer) renderer).getStyle();
489 if (cs instanceof BasicConnectionStyle)
490 return (BasicConnectionStyle) cs;
495 public double getSelectionStrokeWidth() {
496 if (selectionStroke instanceof BasicStroke) {
497 BasicStroke bs = (BasicStroke) selectionStroke;
498 return bs.getLineWidth();
504 public Rectangle2D getBoundsInLocal() {
508 protected void updateBounds() {
509 Rectangle2D r = this.bounds;
511 r = new Rectangle2D.Double();
512 this.bounds = calculateBounds(r);
514 // Need to expand to take stroke width into account.
515 double sw = getSelectionStrokeWidth() / 2;
516 GeometryUtils.expandRectangle(this.bounds, sw, sw);
519 protected Rectangle2D calculateBounds(Rectangle2D rect) {
520 RouteGraph rg = this.rg;
521 if (currentAction instanceof MoveAction)
522 rg = ((MoveAction) currentAction).getRouteGraph();
527 protected void getMouseLocalPos(MouseEvent e) {
528 //System.out.println("m: " + e.controlPosition);
529 pt.setLocation(e.controlPosition);
530 //System.out.println("parent: " + pt);
531 pt = NodeUtil.worldToLocal(this, pt, pt);
532 //System.out.println("local: " + pt);
538 protected boolean mouseDragged(MouseDragBegin e) {
539 // Consume event if drag is possible.
540 // PointerInteractor will call handleDrag with the MouseDragBegin event for the route line that is closest to the cursor.
541 return currentAction != null;
544 public boolean handleDrag(MouseDragBegin e) {
545 if (dragAction != null && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.button == MouseEvent.LEFT_BUTTON) {
546 currentAction = dragAction;
549 return updateCurrentAction(e, true);
553 protected boolean mouseMoved(MouseMovedEvent e) {
554 //System.out.println("mouse moved: " + e);
556 // Handle connection branching visualization.
558 if (newBranchPointPosition == null && e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {
559 if (bounds.contains(mouseX, mouseY)) {
560 newBranchPointPosition = new Point2D.Double(Double.NaN, Double.NaN);
563 if (newBranchPointPosition != null) {
564 RouteLine line = rg.pickLine(mouseX, mouseY, scaledPickTolerance());
566 newBranchPointPosition.setLocation(mouseX, mouseY);
567 SplittedRouteGraph.snapToLine(newBranchPointPosition, line);
569 } else if (!Double.isNaN(newBranchPointPosition.getX())) {
570 newBranchPointPosition.setLocation(Double.NaN, Double.NaN);
575 // Make sure that highlight action rendering is according to mouse hover.
576 if (highlightActionsEnabled) {
577 if (NodeUtil.isSelected(this, 1)) {
582 return updateCurrentAction(e, false);
585 protected boolean updateCurrentAction(MouseEvent e, boolean updateMousePos) {
586 boolean oldHighlight = highlightActionsEnabled;
587 highlightActionsEnabled = e.hasAllModifiers(MouseEvent.CTRL_MASK);
588 if (oldHighlight != highlightActionsEnabled)
591 if (currentAction != null) {
596 // Repaint, but only if absolutely necessary.
597 if (currentAction instanceof MoveAction || bounds.contains(mouseX, mouseY))
606 protected boolean mouseClicked(MouseClickEvent e) {
610 if (e.button == MouseEvent.LEFT_BUTTON) {
611 if (isSelected() && highlightActionsEnabled) {
612 // Reconnection / segment deletion only available for branched connections.
613 if (rg.getTerminals().size() > 2) {
614 Pick pick = highlightActions.pickAction(rg, lastViewTransform, mouseX, mouseY);
615 if (pick.hasAction(Action.REMOVE)) {
616 RemoveLineAction remove = RemoveLineAction.perform(rg, pick.line.getLine(), mouseX, mouseY);
617 if (remove != null) {
618 setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());
623 if (pick.hasAction(Action.RECONNECT)) {
624 currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);
625 if (currentAction != null) {
637 protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
641 if (e.button == MouseEvent.LEFT_BUTTON) {
642 // Visualize new branch point no longer.
643 newBranchPointPosition = null;
647 // if(currentAction instanceof HighlightActionPointsAction) {
648 // RemoveLineAction remove = RemoveLineAction.perform(rg, mouseX, mouseY);
649 // if (remove != null) {
650 // setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());
653 // currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);
654 // if (currentAction != null)
659 if(currentAction instanceof IReconnectAction) {
660 RouteGraph originalRg = rg.copy();
661 ((IReconnectAction)currentAction).finish(mouseX, mouseY);
662 currentAction = null;
664 setRouteGraphAndFireChanges(originalRg, rg);
666 currentAction = null;
671 if (!allowConnectionRerouting()) {
674 //System.out.println("move action");
675 dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, scaledPickTolerance(), moveFilter, getSnapAdvisor());
676 //System.out.println("DRAG ACTION: " + dragAction);
679 //System.out.println(this + " NEW action: " + currentAction);
680 if (currentAction != null)
686 private double scaledPickTolerance() {
687 NavigationNode nn = NodeUtil.findNearestParentNode(this, NavigationNode.class);
690 scale = GeometryUtils.getScale(nn.getTransform());
692 double pickDistance = 0;
693 G2DSceneGraph sg = NodeUtil.getRootNode(nn != null ? nn : this);
695 pickDistance = sg.getGlobalProperty(G2DSceneGraph.PICK_DISTANCE, pickDistance);
697 return Math.max(getSelectionStrokeWidth() / 2.0, pickDistance / scale);
701 * Checks the selections data node in the scene graph for any links
704 private boolean allowConnectionRerouting() {
705 final int maxOtherNodesSelected = 1;
707 INode selections = NodeUtil.tryLookup(this, "selections");
708 if (!(selections instanceof G2DParentNode))
710 G2DParentNode p = (G2DParentNode) selections;
711 for (IG2DNode selection : p.getNodes()) {
712 if (!(selection instanceof G2DParentNode))
715 G2DParentNode sp = (G2DParentNode) selection;
716 Collection<IG2DNode> links = sp.getNodes();
719 int othersSelected = 0;
720 for (IG2DNode link : links) {
721 if (link instanceof LinkNode) {
722 INode node = ((LinkNode) link).getDelegate();
723 if (!NodeUtil.isParentOf(node, this)) {
725 if (othersSelected > maxOtherNodesSelected)
730 if (othersSelected > maxOtherNodesSelected)
736 protected ISnapAdvisor getSnapAdvisor() {
737 GridNode grid = lookupNode(GridNode.GRID_NODE_ID, GridNode.class);
738 return grid != null ? grid.getSnapAdvisor() : null;
741 MoveAction.TargetFilter moveFilter = new MoveAction.TargetFilter() {
743 public boolean accept(Object target) {
744 return (target instanceof RouteLine) || (target instanceof RouteLink);
749 protected boolean handleCommand(CommandEvent e) {
750 /*if (Commands.DELETE.equals(e.command)) {
751 Object target = rg.pick(mouseX, mouseY, pickTolerance);
752 return deleteTarget(target);
753 } else if (Commands.SPLIT_CONNECTION.equals(e.command)) {
754 Object target = rg.pick(mouseX, mouseY, pickTolerance);
755 return splitTarget(target);
757 if (Commands.CANCEL.equals(e.command)) {
758 return cancelCurrentAction();
763 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
764 if (currentAction instanceof MoveAction) {
765 MoveAction move = (MoveAction) currentAction;
766 RouteGraph originalRg = rg.copy();
767 move.finish(mouseX, mouseY);
769 setRouteGraphAndFireChanges(originalRg, rg);
771 currentAction = null;
779 protected boolean keyPressed(KeyPressedEvent e) {
783 if (!e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.keyCode == KeyEvent.VK_S) {
784 Object target = rg.pick(mouseX, mouseY, scaledPickTolerance(), RouteGraph.PICK_PERSISTENT_LINES | RouteGraph.PICK_TRANSIENT_LINES);
785 return splitTarget(target);
787 else if (!e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK | MouseEvent.CTRL_MASK) && (e.keyCode == KeyEvent.VK_R || e.keyCode == KeyEvent.VK_D)) {
788 Object target = rg.pick(mouseX, mouseY, scaledPickTolerance(), RouteGraph.PICK_PERSISTENT_LINES);
789 return deleteTarget(target);
791 else if (e.keyCode == KeyEvent.VK_ESCAPE) {
792 return cancelCurrentAction();
794 // else if (e.keyCode == KeyEvent.VK_D) {
795 // if (target instanceof RouteTerminal) {
796 // RouteTerminal terminal = (RouteTerminal) target;
797 // RouteGraph before = rg.copy();
798 // rg.toggleDirectLines(terminal);
799 // setRouteGraphAndFireChanges(before, rg);
803 // else if (target != null && e.getKeyCode() == KeyEvent.VK_P) {
806 else if (e.keyCode == KeyEvent.VK_CONTROL) {
807 highlightActionsEnabled = true;
810 else if (e.keyCode == KeyEvent.VK_ALT) {
811 // Begin connection branching visualization.
812 RouteLine line = rg.pickLine(mouseX, mouseY, scaledPickTolerance());
813 if (branchable && line != null) {
814 newBranchPointPosition = new Point2D.Double(mouseX, mouseY);
815 SplittedRouteGraph.snapToLine(newBranchPointPosition, line);
824 protected boolean keyReleased(KeyReleasedEvent e) {
825 if (e.keyCode == KeyEvent.VK_ALT) {
826 // End connection branching visualization.
827 if (newBranchPointPosition != null) {
828 newBranchPointPosition = null;
832 if (e.keyCode == KeyEvent.VK_CONTROL) {
833 highlightActionsEnabled = false;
840 private boolean cancelCurrentAction() {
841 if (currentAction != null) {
842 currentAction = null;
849 private boolean splitTarget(Object target) {
850 if (target instanceof RouteLine) {
851 RouteLine rLine = (RouteLine)target;
852 RouteGraph before = rg.copy();
853 rg.split(rLine, rLine.isHorizontal() ? mouseX : mouseY);
854 setRouteGraphAndFireChanges(before, rg);
861 private boolean deleteTarget(Object target) {
862 boolean changed = false;
863 if (target instanceof RouteLine) {
864 RouteLine line = (RouteLine) target;
865 RouteGraph before = rg.copy();
867 changed = setRouteGraphAndFireChanges(before, rg);
869 else if (target instanceof RouteLink) {
870 RouteGraph before = rg.copy();
871 rg.deleteCorner((RouteLink) target);
872 changed = setRouteGraphAndFireChanges(before, rg);
874 // else if (target instanceof RouteTerminal) {
875 // RouteGraph before = rg.copy();
876 // rg.remove((RouteTerminal) target);
877 // changed = setRouteGraphAndFireChanges(before, rg);
885 * A version of MoveAction that snaps movements using the specified
888 static class SnappingMoveAction extends MoveAction {
890 private ISnapAdvisor snapAdvisor;
891 private Point2D point = new Point2D.Double();
893 public SnappingMoveAction(RouteGraph rg, Object target, ISnapAdvisor snapAdvisor) {
895 this.snapAdvisor = snapAdvisor;
898 protected void move(RouteGraph rg, Object target, double x, double y) {
899 point.setLocation(x, y);
900 snapAdvisor.snap(point);
901 super.move(rg, target, point.getX(), point.getY());
904 public static MoveAction create(RouteGraph rg, double x, double y, double tolerance, TargetFilter filter, ISnapAdvisor snapAdvisor) {
905 Object target = rg.pick(x, y, tolerance, RouteGraph.PICK_LINES | RouteGraph.PICK_INTERIOR_POINTS);
906 if (target != null && (filter == null || filter.accept(target))) {
907 if (snapAdvisor != null)
908 return new SnappingMoveAction(rg, target, snapAdvisor);
909 return new MoveAction(rg, target);
917 public int getEventMask() {
918 return EventTypes.CommandMask | EventTypes.KeyMask | EventTypes.MouseMask;