From 72542227673047d71c3c3f281e478c1bea82eb81 Mon Sep 17 00:00:00 2001 From: Jussi Koskela Date: Tue, 29 Oct 2019 14:04:28 +0200 Subject: [PATCH] Take zoom level into account when picking connections Also fixed RouteGraphConnectTool.pickNearestRouteGraphConnection which gave flawed results. Visual hints are fixed to match the performed actions. This versions always picks the closest connection if there are multiple hits. gitlab #396 Change-Id: Ib5d8be3b2c7301dae5545166c07e11c03f92a50d --- .../diagram/connection/RouteLine.java | 8 +- .../participant/RouteGraphConnectTool.java | 85 +++++++++---------- .../g2d/diagram/handler/PickRequest.java | 66 ++++++++++++++ .../pointertool/PointerInteractor.java | 55 +++++++----- .../RouteGraphConnectionClass.java | 6 +- .../scenegraph/g2d/G2DSceneGraph.java | 1 + .../g2d/nodes/connection/RouteGraphNode.java | 37 ++++++-- 7 files changed, 179 insertions(+), 79 deletions(-) diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteLine.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteLine.java index c32b0ee24..8a73ead7a 100644 --- a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteLine.java +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteLine.java @@ -97,11 +97,11 @@ public class RouteLine implements RouteNode, Serializable { public boolean isNear(double x2, double y2, double tolerance) { return isHorizontal ? Math.abs(y2-position) <= tolerance - && points.get(0).x <= x2 - && x2 <= points.get(points.size()-1).x + && points.get(0).x <= x2 - tolerance + && x2 <= points.get(points.size()-1).x + tolerance : Math.abs(x2-position) <= tolerance - && points.get(0).y <= y2 - && y2 <= points.get(points.size()-1).y; + && points.get(0).y <= y2 - tolerance + && y2 <= points.get(points.size()-1).y + tolerance; } public void print(PrintStream out) { diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java index f4add6387..7454a2f39 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java @@ -16,6 +16,7 @@ import java.awt.AlphaComposite; import java.awt.Composite; import java.awt.Shape; import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayDeque; @@ -41,6 +42,7 @@ import org.simantics.diagram.connection.rendering.IRouteGraphRenderer; import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle; import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle; import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle; +import org.simantics.diagram.connection.segments.Segment; import org.simantics.diagram.synchronization.ISynchronizationContext; import org.simantics.diagram.synchronization.SynchronizationHints; import org.simantics.diagram.synchronization.graph.RouteGraphConnection; @@ -863,56 +865,53 @@ public class RouteGraphConnectTool extends AbstractMode { } private static RouteGraphTarget pickNearestRouteGraphConnection(ArrayList elements, double x, double y, double pd) { - // Find the nearest distance at which we get hits. - double hi = pd + 1; - double lo = hi * .01; - double limit = 0.5; - while (true) { - double delta = (hi - lo); - if (delta <= limit) - break; - - pd = (lo + hi) * .5; + Segment nearestSegment = null; + RouteLine nearestLine = null; + IElement nearestConnection = null; - boolean hit = false; - for (IElement connection : elements) { - RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE); - RouteLine line = rgn.getRouteGraph().pickLine(x, y, pd); - if (line != null) { - hit = true; - break; + double minDistanceSq = Double.MAX_VALUE; + + for (IElement connection : elements) { + RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE); + for (RouteLine line : rgn.getRouteGraph().getAllLines()) { + ArrayList segments = new ArrayList(); + line.collectSegments(segments); + for (Segment segment : segments) { + RoutePoint p1 = segment.p1; + RoutePoint p2 = segment.p2; + + double distanceSq = Line2D.ptSegDistSq(p1.getX(), p1.getY(), p2.getX(), p2.getY(), x, y); + if (distanceSq < minDistanceSq && distanceSq < Math.pow(pd + rgn.getSelectionStrokeWidth() / 2, 2)) { + minDistanceSq = distanceSq; + nearestSegment = segment; + nearestLine = line; + nearestConnection = connection; + } } } - - if (hit) - hi = pd; - else - lo = pd; } - // Now that the nearest hitting distance is found, find the nearest intersection. - RouteGraphTarget nearestTarget = null; - double nearest = Double.MAX_VALUE; - for (IElement connection : elements) { - RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE); - RouteLine line = rgn.getRouteGraph().pickLine(x, y, pd); - if (line == null) - continue; - - Point2D intersection = intersectionPoint(x, y, line); - if (intersection == null) - continue; - - double dx = intersection.getX() - x; - double dy = intersection.getY() - y; - double dist = Math.sqrt(dx*dx + dy*dy); - if (dist < nearest) { - nearest = dist; - nearestTarget = new RouteGraphTarget(connection, rgn, line, new Point2D.Double(x, y), intersection); + if (nearestSegment != null) { + RoutePoint p1 = nearestSegment.p1; + RoutePoint p2 = nearestSegment.p2; + double d = Math.pow(p2.getX() - p1.getX(), 2.0) + Math.pow(p2.getY() - p1.getY(), 2.0); + Point2D p; + if (d == 0) { + p = new Point2D.Double(p1.getX(), p1.getY()); + } else { + double u = ((x - p1.getX()) * (p2.getX() - p1.getX()) + (y - p1.getY()) * (p2.getY() - p1.getY())) / d; + if (u > 1.0) { + p = new Point2D.Double(p2.getX(), p2.getY()); + } else if (u <= 0.0) { + p = new Point2D.Double(p1.getX(), p1.getY()); + } else { + p = new Point2D.Double(p2.getX() * u + p1.getX() * (1.0-u), (p2.getY() * u + p1.getY() * (1.0- u))); + } } + return new RouteGraphTarget(nearestConnection, nearestConnection.getHint(RouteGraphConnectionClass.KEY_RG_NODE), nearestLine, new Point2D.Double(x, y), p); + } else { + return null; } - - return nearestTarget; } static Point2D intersectionPoint(double x, double y, RouteLine line) { diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java index 265caa805..912e79cd9 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java @@ -14,12 +14,17 @@ package org.simantics.g2d.diagram.handler; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Area; +import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import org.simantics.diagram.connection.RouteLine; +import org.simantics.diagram.connection.RoutePoint; +import org.simantics.diagram.connection.segments.Segment; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.connection.handler.ConnectionHandler; import org.simantics.g2d.element.IElement; @@ -28,8 +33,11 @@ import org.simantics.g2d.element.handler.TerminalTopology; import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline; import org.simantics.g2d.elementclass.BranchPoint; import org.simantics.g2d.elementclass.MonitorHandler; +import org.simantics.g2d.elementclass.RouteGraphConnectionClass; import org.simantics.g2d.utils.GeometryUtils; +import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode; import org.simantics.scenegraph.utils.TransformedRectangle; +import org.simantics.utils.datastructures.Pair; /** * @@ -172,6 +180,64 @@ public class PickRequest { }); } }; + + /* + * First use the default sorting if available, then sort successive connections by their distance to cursor. + */ + public static PickSorter connectionSorter(PickSorter sorter, double x, double y) { + return new PickSorter() { + + private double getDistanceSqToRouteGraphConnection(RouteGraphNode rgn, double x, double y) { + double minDistanceSq = Double.MAX_VALUE; + for (RouteLine line : rgn.getRouteGraph().getAllLines()) { + ArrayList segments = new ArrayList(); + line.collectSegments(segments); + for (Segment segment : segments) { + RoutePoint p1 = segment.p1; + RoutePoint p2 = segment.p2; + + double distanceSq = Line2D.ptSegDistSq(p1.getX(), p1.getY(), p2.getX(), p2.getY(), x, y); + if (distanceSq < minDistanceSq) { + minDistanceSq = distanceSq; + } + } + } + return minDistanceSq; + } + + @Override + public void sort(List elements) { + if (sorter != null) + sorter.sort(elements); + + List> connections = new ArrayList<>(elements.size()); + int tail = 0; + for (int i = 0; i < elements.size(); i++) { + IElement element = elements.get(i); + RouteGraphNode rgn = element.getHint(RouteGraphConnectionClass.KEY_RG_NODE); + if (rgn != null) { + double distanceSq = getDistanceSqToRouteGraphConnection(rgn, x, y); + connections.add(new Pair(distanceSq, element)); + } + + if (rgn == null || i == elements.size() - 1) { + Collections.sort(connections, new Comparator>() { + @Override + public int compare(Pair connection1, Pair connection2) { + return Double.compare(connection2.first, connection1.first); + } + }); + for (Pair connection : connections) { + elements.set(tail, connection.second); + tail++; + } + connections.clear(); + tail = i + 1; + } + } + } + }; + } } } diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java index 4f9df238f..940579ebe 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java @@ -45,12 +45,14 @@ import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalIn import org.simantics.g2d.element.ElementClassProviders; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.IElementClassProvider; +import org.simantics.g2d.elementclass.RouteGraphConnectionClass; import org.simantics.g2d.participant.KeyUtil; import org.simantics.g2d.participant.MouseUtil; import org.simantics.g2d.participant.TransformUtil; import org.simantics.g2d.scenegraph.SceneGraphConstants; import org.simantics.g2d.utils.CanvasUtils; import org.simantics.g2d.utils.GeometryUtils; +import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.g2d.events.Event; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.KeyEvent; @@ -60,12 +62,15 @@ import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin; import org.simantics.scenegraph.g2d.events.command.Commands; +import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode; import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; import org.simantics.utils.ObjectUtils; import org.simantics.utils.datastructures.context.IContext; import org.simantics.utils.datastructures.context.IContextListener; +import org.simantics.utils.datastructures.hints.HintListenerAdapter; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; +import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.threads.ThreadUtils; /** @@ -203,6 +208,14 @@ public class PointerInteractor extends AbstractDiagramParticipant { super.addedToContext(ctx); hoverStrategy = new DefaultHoverStrategy((TerminalHoverStrategy) getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY)); setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, hoverStrategy); + + getContext().getSceneGraph().setGlobalProperty(G2DSceneGraph.PICK_DISTANCE, getPickDistance()); + getHintStack().addKeyHintListener(KEY_PICK_DISTANCE, new HintListenerAdapter() { + @Override + public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { + getContext().getSceneGraph().setGlobalProperty(G2DSceneGraph.PICK_DISTANCE, getPickDistance()); + } + }); } @EventHandler(priority = 0) @@ -297,7 +310,7 @@ public class PointerInteractor extends AbstractDiagramParticipant { return null; double pd = getPickDistance(); - Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2+1, pd*2+1); + Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2, pd*2); Shape canvasShape = GeometryUtils.transformShape(controlPickRect, inverse); return canvasShape; } @@ -415,15 +428,13 @@ public class PointerInteractor extends AbstractDiagramParticipant { assertDependencies(); - // Pick Terminal - double pickDist = getPickDistance(); - Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1); - Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); + Shape canvasPickRect = getCanvasPickShape(me.controlPosition); int selectionId = me.mouseId; PickRequest req = new PickRequest(canvasPickRect).context(getContext()); req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS; - req.pickSorter = pickSorter; + req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY()); + //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST; List pickables = new ArrayList(); pickContext.pick(diagram, req, pickables); @@ -586,14 +597,13 @@ public class PointerInteractor extends AbstractDiagramParticipant { if (getToolMode() != Hints.POINTERTOOL) return false; if (me.clickCount < 2) return false; - // Pick Terminal - double pickDist = getPickDistance(); - Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1); - Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); + Shape canvasPickRect = getCanvasPickShape(me.controlPosition); int selectionId = me.mouseId; PickRequest req = new PickRequest(canvasPickRect).context(getContext()); req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS; + + req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY()); List pick = new ArrayList(); pickContext.pick(diagram, req, pick); @@ -641,23 +651,13 @@ public class PointerInteractor extends AbstractDiagramParticipant { assertDependencies(); Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, curCanvasDragPos); - PickRequest req = new PickRequest(me.startCanvasPos).context(getContext()); + Shape canvasPickRect = getCanvasPickShape(me.controlPosition); + PickRequest req = new PickRequest(canvasPickRect).context(getContext()); req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS; + req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY()); List picks = new ArrayList(); pickContext.pick(diagram, req, picks); - //System.out.println(picks); - if (picks.isEmpty()) { - // Widen the area of searching if nothing is found with point picking - double pickDist = getPickDistance(); - Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1); - Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); - req = new PickRequest(canvasPickRect).context(getContext()); - req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS; - pickContext.pick(diagram, req, picks); - //System.out.println("2nd try: " + picks); - } - Set sel = selection.getSelection(me.mouseId); IElement topMostPick = picks.isEmpty() ? null : picks.get(picks.size() - 1); Set elementsToDrag = new HashSet(); @@ -686,6 +686,15 @@ public class PointerInteractor extends AbstractDiagramParticipant { getContext().add(tm); return !onlyConnections; } + } else { + // forward MouseDragBegin to closest RouteGraphNode + for (int i = picks.size() - 1; i >= 0; i--) { + RouteGraphNode rgn = picks.get(i).getHint(RouteGraphConnectionClass.KEY_RG_NODE); + if (rgn != null) { + rgn.handleDrag(me); + break; + } + } } } } diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/RouteGraphConnectionClass.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/RouteGraphConnectionClass.java index a9015fcfb..bf4577b51 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/RouteGraphConnectionClass.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/RouteGraphConnectionClass.java @@ -171,6 +171,10 @@ public class RouteGraphConnectionClass { @Override public boolean pickTest(IElement e, Shape s, PickPolicy policy) { + RouteGraphNode rgn = e.getHint(KEY_RG_NODE); + if (rgn == null) { + return false; + } RouteGraph rg = getRouteGraph(e); if (rg == null) return false; @@ -185,7 +189,7 @@ public class RouteGraphConnectionClass { if (e.containsHint(KEY_USE_TOLERANCE_IN_SELECTION)) tolerance = getTolerance(e); else - tolerance = (bounds.getHeight()+bounds.getHeight()) * 0.25; + tolerance = Math.max((bounds.getHeight()+bounds.getHeight()) * 0.25, rgn.getSelectionStrokeWidth() / 2); Object node = rg.pickLine(bounds.getCenterX(), bounds.getCenterY(), tolerance); return node != null; } diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DSceneGraph.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DSceneGraph.java index 9055a8efe..a013dc041 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DSceneGraph.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DSceneGraph.java @@ -48,6 +48,7 @@ public class G2DSceneGraph extends G2DParentNode implements ILookupService, INod private static final long serialVersionUID = -7066146333849901429L; public static final String IGNORE_FOCUS = "ignoreFocus"; + public static final String PICK_DISTANCE = "pickDistance"; protected transient Container rootPane = null; // TODO: swing dependency in here might not be a good idea 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 0a2583fc6..048514f3a 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 @@ -23,7 +23,6 @@ import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -47,6 +46,7 @@ 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.G2DSceneGraph; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.g2d.events.EventTypes; import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; @@ -61,6 +61,7 @@ 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.NavigationNode; import org.simantics.scenegraph.g2d.nodes.SVGNodeAssignment; import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Action; import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Pick; @@ -69,8 +70,8 @@ import org.simantics.scenegraph.utils.ColorUtil; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.scenegraph.utils.InitValueSupport; import org.simantics.scenegraph.utils.NodeUtil; -import org.slf4j.LoggerFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import gnu.trove.map.hash.THashMap; @@ -486,7 +487,7 @@ public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, In return null; } - private double getSelectionStrokeWidth() { + public double getSelectionStrokeWidth() { if (selectionStroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) selectionStroke; return bs.getLineWidth(); @@ -530,6 +531,12 @@ public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, In @Override protected boolean mouseDragged(MouseDragBegin e) { + // Consume event if drag is possible. + // PointerInteractor will call handleDrag with the MouseDragBegin event for the route line that is closest to the cursor. + return currentAction != null; + } + + public boolean handleDrag(MouseDragBegin e) { if (dragAction != null && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.button == MouseEvent.LEFT_BUTTON) { currentAction = dragAction; dragAction = null; @@ -549,7 +556,7 @@ public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, In } } if (newBranchPointPosition != null) { - RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance); + RouteLine line = rg.pickLine(mouseX, mouseY, scaledPickTolerance()); if (line != null) { newBranchPointPosition.setLocation(mouseX, mouseY); SplittedRouteGraph.snapToLine(newBranchPointPosition, line); @@ -660,7 +667,7 @@ public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, In return false; } //System.out.println("move action"); - dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, pickTolerance, moveFilter, getSnapAdvisor()); + dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, scaledPickTolerance(), moveFilter, getSnapAdvisor()); //System.out.println("DRAG ACTION: " + dragAction); } @@ -671,6 +678,20 @@ public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, In return false; } + private double scaledPickTolerance() { + NavigationNode nn = NodeUtil.findNearestParentNode(this, NavigationNode.class); + double scale = 1.0; + if (nn != null) { + scale = GeometryUtils.getScale(nn.getTransform()); + } + double pickDistance = 0; + G2DSceneGraph sg = NodeUtil.getRootNode(nn != null ? nn : this); + if (sg != null) { + pickDistance = sg.getGlobalProperty(G2DSceneGraph.PICK_DISTANCE, pickDistance); + } + return Math.max(getSelectionStrokeWidth() / 2.0, pickDistance / scale); + } + /** * Checks the selections data node in the scene graph for any links * @return @@ -755,11 +776,11 @@ public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, In 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); + Object target = rg.pick(mouseX, mouseY, scaledPickTolerance(), 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); + Object target = rg.pick(mouseX, mouseY, scaledPickTolerance(), RouteGraph.PICK_PERSISTENT_LINES); return deleteTarget(target); } else if (e.keyCode == KeyEvent.VK_ESCAPE) { @@ -783,7 +804,7 @@ public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, In } else if (e.keyCode == KeyEvent.VK_ALT) { // Begin connection branching visualization. - RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance); + RouteLine line = rg.pickLine(mouseX, mouseY, scaledPickTolerance()); if (branchable && line != null) { newBranchPointPosition = new Point2D.Double(mouseX, mouseY); SplittedRouteGraph.snapToLine(newBranchPointPosition, line); -- 2.43.2