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