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) {
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;
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;
}
private static RouteGraphTarget pickNearestRouteGraphConnection(ArrayList<IElement> 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<Segment> segments = new ArrayList<Segment>();
+ 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) {
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;
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;
/**
*
});
}
};
+
+ /*
+ * 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<Segment> segments = new ArrayList<Segment>();
+ 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<IElement> elements) {
+ if (sorter != null)
+ sorter.sort(elements);
+
+ List<Pair<Double, IElement>> 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<Double, IElement>(distanceSq, element));
+ }
+
+ if (rgn == null || i == elements.size() - 1) {
+ Collections.sort(connections, new Comparator<Pair<Double, IElement>>() {
+ @Override
+ public int compare(Pair<Double, IElement> connection1, Pair<Double, IElement> connection2) {
+ return Double.compare(connection2.first, connection1.first);
+ }
+ });
+ for (Pair<Double, IElement> connection : connections) {
+ elements.set(tail, connection.second);
+ tail++;
+ }
+ connections.clear();
+ tail = i + 1;
+ }
+ }
+ }
+ };
+ }
}
}
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;
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;
/**
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)
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;
}
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<IElement> pickables = new ArrayList<IElement>();
pickContext.pick(diagram, req, pickables);
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<IElement> pick = new ArrayList<IElement>();
pickContext.pick(diagram, req, pick);
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<IElement> picks = new ArrayList<IElement>();
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<IElement> sel = selection.getSelection(me.mouseId);
IElement topMostPick = picks.isEmpty() ? null : picks.get(picks.size() - 1);
Set<IElement> elementsToDrag = new HashSet<IElement>();
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;
+ }
+ }
}
}
}
@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;
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;
}
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
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;
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;
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;
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;
return null;
}
- private double getSelectionStrokeWidth() {
+ public double getSelectionStrokeWidth() {
if (selectionStroke instanceof BasicStroke) {
BasicStroke bs = (BasicStroke) selectionStroke;
return bs.getLineWidth();
@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;
}
}
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);
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);
}
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
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) {
}
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);