1 /*******************************************************************************
\r
2 * Copyright (c) 2011 Association for Decentralized Information Management in
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.g2d.nodes.connection;
\r
14 import gnu.trove.map.hash.THashMap;
\r
16 import java.awt.BasicStroke;
\r
17 import java.awt.Color;
\r
18 import java.awt.Graphics2D;
\r
19 import java.awt.RenderingHints;
\r
20 import java.awt.Shape;
\r
21 import java.awt.Stroke;
\r
22 import java.awt.event.KeyEvent;
\r
23 import java.awt.geom.AffineTransform;
\r
24 import java.awt.geom.Path2D;
\r
25 import java.awt.geom.Point2D;
\r
26 import java.awt.geom.Rectangle2D;
\r
27 import java.lang.reflect.Constructor;
\r
28 import java.util.Collection;
\r
29 import java.util.Map;
\r
31 import org.simantics.diagram.connection.RouteGraph;
\r
32 import org.simantics.diagram.connection.RouteLine;
\r
33 import org.simantics.diagram.connection.RouteLink;
\r
34 import org.simantics.diagram.connection.RoutePoint;
\r
35 import org.simantics.diagram.connection.RouteTerminal;
\r
36 import org.simantics.diagram.connection.actions.IAction;
\r
37 import org.simantics.diagram.connection.actions.IReconnectAction;
\r
38 import org.simantics.diagram.connection.actions.MoveAction;
\r
39 import org.simantics.diagram.connection.actions.ReconnectLineAction;
\r
40 import org.simantics.diagram.connection.delta.RouteGraphDelta;
\r
41 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
\r
42 import org.simantics.diagram.connection.rendering.ConnectionStyle;
\r
43 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
\r
44 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;
\r
45 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
\r
46 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
\r
47 import org.simantics.scenegraph.INode;
\r
48 import org.simantics.scenegraph.ISelectionPainterNode;
\r
49 import org.simantics.scenegraph.g2d.G2DNode;
\r
50 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
51 import org.simantics.scenegraph.g2d.IG2DNode;
\r
52 import org.simantics.scenegraph.g2d.events.EventTypes;
\r
53 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
\r
54 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
\r
55 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
56 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
57 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
\r
58 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
\r
59 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
60 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
61 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
\r
62 import org.simantics.scenegraph.g2d.events.command.Commands;
\r
63 import org.simantics.scenegraph.g2d.nodes.GridNode;
\r
64 import org.simantics.scenegraph.g2d.nodes.LinkNode;
\r
65 import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Action;
\r
66 import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Pick;
\r
67 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
\r
68 import org.simantics.scenegraph.utils.GeometryUtils;
\r
69 import org.simantics.scenegraph.utils.InitValueSupport;
\r
70 import org.simantics.scenegraph.utils.NodeUtil;
\r
73 * @author Tuukka Lehtonen
\r
75 public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, InitValueSupport {
\r
77 private static final long serialVersionUID = -917194130412280965L;
\r
79 private static final double TOLERANCE = IAction.TOLERANCE;
\r
80 private static final Stroke SELECTION_STROKE = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
\r
81 private static final Color SELECTION_COLOR = new Color(255, 0, 255, 96);
\r
83 private static final HighlightActionPointsAction highlightActions = new HighlightActionPointsAction(null);
\r
85 protected RouteGraph rg;
\r
86 protected IRouteGraphRenderer baseRenderer;
\r
87 protected IRouteGraphRenderer renderer;
\r
88 protected double pickTolerance = TOLERANCE;
\r
89 protected boolean editable = true;
\r
90 private boolean branchable = true;
\r
92 protected IRouteGraphListener rgListener;
\r
93 protected RouteGraphDelta rgDelta;
\r
95 protected transient double mouseX;
\r
96 protected transient double mouseY;
\r
97 protected transient Point2D pt = new Point2D.Double();
\r
99 protected transient MoveAction dragAction;
\r
100 protected transient IAction currentAction;
\r
101 protected transient Rectangle2D bounds;
\r
104 * Dynamic color for connection rendering.
\r
106 protected transient Color dynamicColor;
\r
109 * Dynamic stroke for connection rendering.
\r
111 protected transient Stroke dynamicStroke;
\r
113 protected transient Path2D selectionPath = new Path2D.Double();
\r
114 protected transient Stroke selectionStroke = null;
\r
116 protected transient boolean highlightActionsEnabled = false;
\r
117 protected transient AffineTransform lastViewTransform = null;
\r
120 * x = NaN is used to indicate that possible branch point should not be
\r
121 * rendered but interaction has not ended yet.
\r
123 protected transient Point2D newBranchPointPosition = null;
\r
126 protected transient Map<Object,ILineEndStyle> dynamicStyles = null;
\r
129 public void initValues() {
\r
130 dynamicColor = null;
\r
134 @PropertySetter("color")
\r
135 @SyncField(value = {"dynamicColor"})
\r
136 public void setDynamicColor(Color color) {
\r
137 this.dynamicColor = color;
\r
141 @PropertySetter("width")
\r
142 @SyncField("dynamicStroke")
\r
143 public void setDynamicStroke(Stroke stroke) {
\r
144 this.dynamicStroke = stroke;
\r
146 createSelectionStroke();
\r
149 @SyncField(value = {"dynamicStyles"})
\r
150 public void setDynamicLineEnd(RouteTerminal terminal, ILineEndStyle style) {
\r
151 if (dynamicStyles == null)
\r
152 dynamicStyles = new THashMap<Object, ILineEndStyle>();
\r
153 terminal.setDynamicStyle(style);
\r
154 if (terminal.getData() != null) {
\r
156 dynamicStyles.put(terminal.getData(),style);
\r
158 dynamicStyles.remove(terminal.getData());
\r
162 private void updateLineEnds() {
\r
163 if (dynamicStyles == null)
\r
165 for (RouteTerminal t : rg.getTerminals()) {
\r
166 if (t.getData() == null)
\r
168 ILineEndStyle dynamicStyle = dynamicStyles.get(t.getData());
\r
169 if (dynamicStyle != null)
\r
170 t.setDynamicStyle(dynamicStyle);
\r
174 @SyncField(value = {"rg"})
\r
175 public void setRouteGraph(RouteGraph graph) {
\r
181 @SyncField(value = {"rgDelta"})
\r
182 public void setRouteGraphDelta(RouteGraphDelta delta) {
\r
183 this.rgDelta = delta;
\r
186 @SyncField(value = {"renderer"})
\r
187 public void setRenderer(IRouteGraphRenderer renderer) {
\r
189 this.baseRenderer = renderer;
\r
192 createSelectionStroke();
\r
195 private void createSelectionStroke() {
\r
196 BasicConnectionStyle style = tryGetStyle();
\r
197 selectionStroke = null;
\r
198 if (style != null) {
\r
199 BasicStroke stroke = (BasicStroke) style.getLineStroke();
\r
200 if (stroke != null) {
\r
201 float width = Math.max(stroke.getLineWidth() + 0.75f, stroke.getLineWidth()*1.3f);
\r
202 selectionStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, stroke.getLineJoin());
\r
205 selectionStroke = SELECTION_STROKE;
\r
209 private void wrapRenderer() {
\r
211 if(baseRenderer == null) {
\r
216 if(dynamicColor != null || dynamicStroke != null) {
\r
217 BasicConnectionStyle baseStyle = (BasicConnectionStyle)tryGetStyle(baseRenderer);
\r
219 Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class);
\r
220 renderer = new StyledRouteGraphRenderer(c.newInstance(
\r
221 dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
\r
222 baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
\r
223 dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
\r
224 dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
\r
225 baseStyle.getDegeneratedLineLength()));
\r
226 } catch (Exception e) {
\r
227 renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(
\r
228 dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
\r
229 baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
\r
230 dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
\r
231 dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
\r
232 baseStyle.getDegeneratedLineLength()));
\r
237 renderer = baseRenderer;
\r
242 @SyncField(value = {"pickTolerance"})
\r
243 public void setPickTolerance(double tolerance) {
\r
244 this.pickTolerance = tolerance;
\r
247 @SyncField(value = {"editable"})
\r
248 public void setEditable(boolean editable) {
\r
249 this.editable = editable;
\r
252 @SyncField(value = {"branchable"})
\r
253 public void setBranchable(boolean branchable) {
\r
254 this.branchable = branchable;
\r
257 public RouteGraph getRouteGraph() {
\r
261 public RouteGraphDelta getRouteGraphDelta() {
\r
265 public IRouteGraphRenderer getRenderer() {
\r
269 public boolean isEditable() {
\r
273 public boolean isBranchable() {
\r
277 public double getPickTolerance() {
\r
278 return pickTolerance;
\r
282 * When in client-server mode, listener is only set on the server side and
\r
283 * fireRouteGraphChanged will tell it when rg has changed.
\r
287 public void setRouteGraphListener(IRouteGraphListener listener) {
\r
288 this.rgListener = listener;
\r
294 * @return <code>true</code> if changes were fired
\r
296 private boolean setRouteGraphAndFireChanges(RouteGraph before, RouteGraph after) {
\r
297 RouteGraphDelta delta = new RouteGraphDelta(before, after);
\r
298 if (!delta.isEmpty()) {
\r
299 setRouteGraph(after);
\r
300 setRouteGraphDelta(delta);
\r
301 fireRouteGraphChanged(before, after, delta);
\r
308 protected void fireRouteGraphChanged(RouteGraph before, RouteGraph after, RouteGraphDelta delta) {
\r
309 if (rgListener != null) {
\r
310 RouteGraphChangeEvent event = new RouteGraphChangeEvent(this, before, after, delta);
\r
311 rgListener.routeGraphChanged(event);
\r
316 public void init() {
\r
318 addEventHandler(this);
\r
322 public void cleanup() {
\r
324 removeEventHandler(this);
\r
328 protected boolean isSelected() {
\r
329 return NodeUtil.isSelected(this, 1);
\r
333 public void render(Graphics2D g) {
\r
334 if (renderer == null)
\r
337 AffineTransform ot = null;
\r
338 AffineTransform t = getTransform();
\r
339 if (t != null && !t.isIdentity()) {
\r
340 ot = g.getTransform();
\r
341 g.transform(getTransform());
\r
344 Object aaHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
\r
345 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
\r
347 boolean selected = NodeUtil.isSelected(this, 1);
\r
349 if (currentAction != null) {
\r
350 currentAction.render(g, renderer, mouseX, mouseY);
\r
352 if (selected && selectionStroke != null) {
\r
353 selectionPath.reset();
\r
354 rg.getPath2D(selectionPath);
\r
355 Shape selectionShape = selectionStroke.createStrokedShape(selectionPath);
\r
356 g.setColor(SELECTION_COLOR);
\r
357 g.fill(selectionShape);
\r
360 renderer.render(g, rg);
\r
362 renderer.renderGuides(g, rg);
\r
364 if (selected && highlightActionsEnabled) {
\r
365 // Needed for performing actions in #mouseClicked
\r
366 this.lastViewTransform = g.getTransform();
\r
367 highlightActions.setRouteGraph(rg);
\r
368 highlightActions.render(g, renderer, mouseX, mouseY);
\r
369 highlightActions.setRouteGraph(null);
\r
373 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);
\r
375 if (editable && branchable && newBranchPointPosition != null && !Double.isNaN(newBranchPointPosition.getX())) {
\r
376 ConnectionStyle style = tryGetStyle();
\r
378 style.drawBranchPoint(g, newBranchPointPosition.getX(), newBranchPointPosition.getY());
\r
382 g.setTransform(ot);
\r
385 private BasicConnectionStyle tryGetStyle() {
\r
386 return tryGetStyle(renderer);
\r
389 private BasicConnectionStyle tryGetStyle(IRouteGraphRenderer renderer) {
\r
390 if (renderer instanceof StyledRouteGraphRenderer) {
\r
391 ConnectionStyle cs = ((StyledRouteGraphRenderer) renderer).getStyle();
\r
392 if (cs instanceof BasicConnectionStyle)
\r
393 return (BasicConnectionStyle) cs;
\r
398 private double getSelectionStrokeWidth() {
\r
399 if (selectionStroke instanceof BasicStroke) {
\r
400 BasicStroke bs = (BasicStroke) selectionStroke;
\r
401 return bs.getLineWidth();
\r
407 public Rectangle2D getBoundsInLocal() {
\r
411 protected void updateBounds() {
\r
412 Rectangle2D r = this.bounds;
\r
414 r = new Rectangle2D.Double();
\r
415 this.bounds = calculateBounds(r);
\r
417 // Need to expand to take stroke width into account.
\r
418 double sw = getSelectionStrokeWidth() / 2;
\r
419 GeometryUtils.expandRectangle(this.bounds, sw, sw);
\r
422 protected Rectangle2D calculateBounds(Rectangle2D rect) {
\r
423 RouteGraph rg = this.rg;
\r
424 if (currentAction instanceof MoveAction)
\r
425 rg = ((MoveAction) currentAction).getRouteGraph();
\r
426 rg.getBounds(rect);
\r
430 protected void getMouseLocalPos(MouseEvent e) {
\r
431 //System.out.println("m: " + e.controlPosition);
\r
432 pt.setLocation(e.controlPosition);
\r
433 //System.out.println("parent: " + pt);
\r
434 pt = NodeUtil.worldToLocal(this, pt, pt);
\r
435 //System.out.println("local: " + pt);
\r
436 mouseX = pt.getX();
\r
437 mouseY = pt.getY();
\r
441 protected boolean mouseDragged(MouseDragBegin e) {
\r
442 if (dragAction != null && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.button == MouseEvent.LEFT_BUTTON) {
\r
443 currentAction = dragAction;
\r
446 return updateCurrentAction(e, true);
\r
450 protected boolean mouseMoved(MouseMovedEvent e) {
\r
451 //System.out.println("mouse moved: " + e);
\r
453 // Handle connection branching visualization.
\r
454 getMouseLocalPos(e);
\r
455 if (newBranchPointPosition == null && e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {
\r
456 if (bounds.contains(mouseX, mouseY)) {
\r
457 newBranchPointPosition = new Point2D.Double(Double.NaN, Double.NaN);
\r
460 if (newBranchPointPosition != null) {
\r
461 RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);
\r
462 if (line != null) {
\r
463 newBranchPointPosition.setLocation(mouseX, mouseY);
\r
464 SplittedRouteGraph.snapToLine(newBranchPointPosition, line);
\r
466 } else if (!Double.isNaN(newBranchPointPosition.getX())) {
\r
467 newBranchPointPosition.setLocation(Double.NaN, Double.NaN);
\r
472 // Make sure that highlight action rendering is according to mouse hover.
\r
473 if (highlightActionsEnabled) {
\r
474 if (NodeUtil.isSelected(this, 1)) {
\r
479 return updateCurrentAction(e, false);
\r
482 protected boolean updateCurrentAction(MouseEvent e, boolean updateMousePos) {
\r
483 boolean oldHighlight = highlightActionsEnabled;
\r
484 highlightActionsEnabled = e.hasAllModifiers(MouseEvent.CTRL_MASK);
\r
485 if (oldHighlight != highlightActionsEnabled)
\r
488 if (currentAction != null) {
\r
489 if (updateMousePos)
\r
490 getMouseLocalPos(e);
\r
493 // Repaint, but only if absolutely necessary.
\r
494 if (currentAction instanceof MoveAction || bounds.contains(mouseX, mouseY))
\r
503 protected boolean mouseClicked(MouseClickEvent e) {
\r
507 if (e.button == MouseEvent.LEFT_BUTTON) {
\r
508 if (isSelected() && highlightActionsEnabled) {
\r
509 // Reconnection / segment deletion only available for branched connections.
\r
510 if (rg.getTerminals().size() > 2) {
\r
511 Pick pick = highlightActions.pickAction(rg, lastViewTransform, mouseX, mouseY);
\r
512 if (pick.hasAction(Action.REMOVE)) {
\r
513 RemoveLineAction remove = RemoveLineAction.perform(rg, pick.line.getLine(), mouseX, mouseY);
\r
514 if (remove != null) {
\r
515 setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());
\r
520 if (pick.hasAction(Action.RECONNECT)) {
\r
521 currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);
\r
522 if (currentAction != null) {
\r
534 protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
\r
538 if (e.button == MouseEvent.LEFT_BUTTON) {
\r
539 // Visualize new branch point no longer.
\r
540 newBranchPointPosition = null;
\r
542 getMouseLocalPos(e);
\r
544 // if(currentAction instanceof HighlightActionPointsAction) {
\r
545 // RemoveLineAction remove = RemoveLineAction.perform(rg, mouseX, mouseY);
\r
546 // if (remove != null) {
\r
547 // setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());
\r
550 // currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);
\r
551 // if (currentAction != null)
\r
556 if(currentAction instanceof IReconnectAction) {
\r
557 RouteGraph originalRg = rg.copy();
\r
558 ((IReconnectAction)currentAction).finish(mouseX, mouseY);
\r
559 currentAction = null;
\r
561 setRouteGraphAndFireChanges(originalRg, rg);
\r
563 currentAction = null;
\r
568 if (!allowConnectionRerouting()) {
\r
571 //System.out.println("move action");
\r
572 dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, pickTolerance, moveFilter, getSnapAdvisor());
\r
573 //System.out.println("DRAG ACTION: " + dragAction);
\r
576 //System.out.println(this + " NEW action: " + currentAction);
\r
577 if (currentAction != null)
\r
584 * Checks the selections data node in the scene graph for any links
\r
587 private boolean allowConnectionRerouting() {
\r
588 final int maxOtherNodesSelected = 1;
\r
590 INode selections = NodeUtil.tryLookup(this, "selections");
\r
591 if (!(selections instanceof G2DParentNode))
\r
593 G2DParentNode p = (G2DParentNode) selections;
\r
594 for (IG2DNode selection : p.getNodes()) {
\r
595 if (!(selection instanceof G2DParentNode))
\r
598 G2DParentNode sp = (G2DParentNode) selection;
\r
599 Collection<IG2DNode> links = sp.getNodes();
\r
600 if (links.isEmpty())
\r
602 int othersSelected = 0;
\r
603 for (IG2DNode link : links) {
\r
604 if (link instanceof LinkNode) {
\r
605 INode node = ((LinkNode) link).getDelegate();
\r
606 if (!NodeUtil.isParentOf(node, this)) {
\r
608 if (othersSelected > maxOtherNodesSelected)
\r
613 if (othersSelected > maxOtherNodesSelected)
\r
619 protected ISnapAdvisor getSnapAdvisor() {
\r
620 GridNode grid = lookupNode(GridNode.GRID_NODE_ID, GridNode.class);
\r
621 return grid != null ? grid.getSnapAdvisor() : null;
\r
624 MoveAction.TargetFilter moveFilter = new MoveAction.TargetFilter() {
\r
626 public boolean accept(Object target) {
\r
627 return (target instanceof RouteLine) || (target instanceof RouteLink);
\r
632 protected boolean handleCommand(CommandEvent e) {
\r
633 /*if (Commands.DELETE.equals(e.command)) {
\r
634 Object target = rg.pick(mouseX, mouseY, pickTolerance);
\r
635 return deleteTarget(target);
\r
636 } else if (Commands.SPLIT_CONNECTION.equals(e.command)) {
\r
637 Object target = rg.pick(mouseX, mouseY, pickTolerance);
\r
638 return splitTarget(target);
\r
640 if (Commands.CANCEL.equals(e.command)) {
\r
641 return cancelCurrentAction();
\r
646 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
\r
647 if (currentAction instanceof MoveAction) {
\r
648 MoveAction move = (MoveAction) currentAction;
\r
649 RouteGraph originalRg = rg.copy();
\r
650 move.finish(mouseX, mouseY);
\r
652 setRouteGraphAndFireChanges(originalRg, rg);
\r
654 currentAction = null;
\r
662 protected boolean keyPressed(KeyPressedEvent e) {
\r
666 if (!e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.keyCode == KeyEvent.VK_S) {
\r
667 Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES | RouteGraph.PICK_TRANSIENT_LINES);
\r
668 return splitTarget(target);
\r
670 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
671 Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES);
\r
672 return deleteTarget(target);
\r
674 else if (e.keyCode == KeyEvent.VK_ESCAPE) {
\r
675 return cancelCurrentAction();
\r
677 // else if (e.keyCode == KeyEvent.VK_D) {
\r
678 // if (target instanceof RouteTerminal) {
\r
679 // RouteTerminal terminal = (RouteTerminal) target;
\r
680 // RouteGraph before = rg.copy();
\r
681 // rg.toggleDirectLines(terminal);
\r
682 // setRouteGraphAndFireChanges(before, rg);
\r
686 // else if (target != null && e.getKeyCode() == KeyEvent.VK_P) {
\r
689 else if (e.keyCode == KeyEvent.VK_CONTROL) {
\r
690 highlightActionsEnabled = true;
\r
693 else if (e.keyCode == KeyEvent.VK_ALT) {
\r
694 // Begin connection branching visualization.
\r
695 RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);
\r
696 if (branchable && line != null) {
\r
697 newBranchPointPosition = new Point2D.Double(mouseX, mouseY);
\r
698 SplittedRouteGraph.snapToLine(newBranchPointPosition, line);
\r
707 protected boolean keyReleased(KeyReleasedEvent e) {
\r
708 if (e.keyCode == KeyEvent.VK_ALT) {
\r
709 // End connection branching visualization.
\r
710 if (newBranchPointPosition != null) {
\r
711 newBranchPointPosition = null;
\r
715 if (e.keyCode == KeyEvent.VK_CONTROL) {
\r
716 highlightActionsEnabled = false;
\r
723 private boolean cancelCurrentAction() {
\r
724 if (currentAction != null) {
\r
725 currentAction = null;
\r
732 private boolean splitTarget(Object target) {
\r
733 if (target instanceof RouteLine) {
\r
734 RouteLine rLine = (RouteLine)target;
\r
735 RouteGraph before = rg.copy();
\r
736 rg.split(rLine, rLine.isHorizontal() ? mouseX : mouseY);
\r
737 setRouteGraphAndFireChanges(before, rg);
\r
744 private boolean deleteTarget(Object target) {
\r
745 boolean changed = false;
\r
746 if (target instanceof RouteLine) {
\r
747 RouteLine line = (RouteLine) target;
\r
748 RouteGraph before = rg.copy();
\r
750 changed = setRouteGraphAndFireChanges(before, rg);
\r
752 else if (target instanceof RouteLink) {
\r
753 RouteGraph before = rg.copy();
\r
754 rg.deleteCorner((RouteLink) target);
\r
755 changed = setRouteGraphAndFireChanges(before, rg);
\r
757 // else if (target instanceof RouteTerminal) {
\r
758 // RouteGraph before = rg.copy();
\r
759 // rg.remove((RouteTerminal) target);
\r
760 // changed = setRouteGraphAndFireChanges(before, rg);
\r
768 * A version of MoveAction that snaps movements using the specified
\r
771 static class SnappingMoveAction extends MoveAction {
\r
773 private ISnapAdvisor snapAdvisor;
\r
774 private Point2D point = new Point2D.Double();
\r
776 public SnappingMoveAction(RouteGraph rg, Object target, ISnapAdvisor snapAdvisor) {
\r
778 this.snapAdvisor = snapAdvisor;
\r
781 protected void move(RouteGraph rg, Object target, double x, double y) {
\r
782 point.setLocation(x, y);
\r
783 snapAdvisor.snap(point);
\r
784 super.move(rg, target, point.getX(), point.getY());
\r
787 public static MoveAction create(RouteGraph rg, double x, double y, double tolerance, TargetFilter filter, ISnapAdvisor snapAdvisor) {
\r
788 Object target = rg.pick(x, y, tolerance, RouteGraph.PICK_LINES | RouteGraph.PICK_INTERIOR_POINTS);
\r
789 if (target != null && (filter == null || filter.accept(target))) {
\r
790 if (snapAdvisor != null)
\r
791 return new SnappingMoveAction(rg, target, snapAdvisor);
\r
792 return new MoveAction(rg, target);
\r
800 public int getEventMask() {
\r
801 return EventTypes.CommandMask | EventTypes.KeyMask | EventTypes.MouseMask;
\r