1 /*******************************************************************************
2 * Copyright (c) 2007, 2020 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 * Semantum Oy - gitlab #454
12 *******************************************************************************/
13 package org.simantics.g2d.diagram.handler;
15 import java.awt.Shape;
16 import java.awt.geom.AffineTransform;
17 import java.awt.geom.Area;
18 import java.awt.geom.Line2D;
19 import java.awt.geom.Point2D;
20 import java.awt.geom.Rectangle2D;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.List;
26 import org.simantics.diagram.connection.RouteLine;
27 import org.simantics.diagram.connection.RoutePoint;
28 import org.simantics.diagram.connection.segments.Segment;
29 import org.simantics.g2d.canvas.ICanvasContext;
30 import org.simantics.g2d.connection.handler.ConnectionHandler;
31 import org.simantics.g2d.element.IElement;
32 import org.simantics.g2d.element.handler.BendsHandler;
33 import org.simantics.g2d.element.handler.TerminalTopology;
34 import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;
35 import org.simantics.g2d.elementclass.BranchPoint;
36 import org.simantics.g2d.elementclass.MonitorHandler;
37 import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
38 import org.simantics.g2d.utils.GeometryUtils;
39 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
40 import org.simantics.scenegraph.utils.TransformedRectangle;
41 import org.simantics.utils.datastructures.Pair;
45 * @See {@link GeometryUtils} for intersect and contains tests
46 * @See {@link TransformedRectangle}
47 * @See {@link Area} intersects operations for complex shapes
48 * @author Toni Kalajainen
50 public class PickRequest {
52 public static enum PickPolicy {
53 PICK_INTERSECTING_OBJECTS,
54 PICK_CONTAINED_OBJECTS
57 public Shape pickArea;
58 public PickPolicy pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
59 /** Pick filter (null == everything is accepted) */
60 public PickFilter pickFilter = null;
61 public PickSorter pickSorter = null;
64 * Used to optimize picking if provided via R-tree traversal to find
65 * intersecting elements, not everything.
67 public ICanvasContext pickContext;
69 public PickRequest(double x, double y)
71 pickArea = new Rectangle2D.Double(x, y, 1, 1);
73 public PickRequest(Point2D p)
75 pickArea = new Rectangle2D.Double(p.getX(), p.getY(), 0.0001, 0.0001);
77 public PickRequest(Shape shape)
81 public PickRequest(Shape shape, AffineTransform transform)
83 pickArea = GeometryUtils.transformShape(shape, transform);
86 public PickRequest context(ICanvasContext ctx) {
87 this.pickContext = ctx;
91 public static interface PickFilter {
92 boolean accept(IElement e);
94 public static final PickFilter FILTER_ALL = new PickFilter() {
96 public boolean accept(IElement e) {
101 public static final PickFilter FILTER_CONNECTIONS = new PickFilter() {
103 public boolean accept(IElement e) {
104 return e.getElementClass().containsClass(ConnectionHandler.class);
107 public String toString() { return "FILTER_CONNECTIONS"; }
110 public static final PickFilter FILTER_CONNECTION_EDGES = new PickFilter() {
112 public boolean accept(IElement e) {
113 return e.getElementClass().containsClass(BendsHandler.class) || e.getElementClass().containsClass(ConnectionSelectionOutline.class);
116 public String toString() { return "FILTER_CONNECTION_EDGES"; }
119 public static final PickFilter FILTER_BRANCH_POINT = new PickFilter() {
121 public boolean accept(IElement e) {
122 return e.getElementClass().containsClass(BranchPoint.class);
125 public String toString() { return "FILTER_BRANCH_POINTS"; }
127 // Anything that has terminals
128 public static final PickFilter FILTER_NODES = new PickFilter() {
130 public boolean accept(IElement e) {
131 return e.getElementClass().containsClass(TerminalTopology.class);
134 public String toString() { return "FILTER_NODES"; }
136 public static final PickFilter FILTER_MONITORS = new PickFilter() {
138 public boolean accept(IElement e) {
139 return e.getElementClass().containsClass(MonitorHandler.class);
142 public String toString() { return "FILTER_MONITORS"; }
146 public static interface PickSorter {
148 * Sorts the specified element list.
150 * @param elements the element list to sort
152 void sort(List<IElement> elements);
155 * Extended interface-method that receives the pick request in addition to the
156 * picked elements to be sorted. Allows e.g. looking at the pick area in the
160 * The default implementation just invokes {@link #sort(List)} ignoring the pick
161 * request. The default implementation also keeps PickSorter API/ABI-compatible.
163 * @param request the original pick request that produced the hits listed in
164 * <code>elements</code>
165 * @param elements the element list to sort
167 * @author Tuukka Lehtonen
168 * @since 1.43.0, 1.35.3
170 default void sort(PickRequest request, List<IElement> elements) {
175 public static final PickSorter CONNECTIONS_LAST = new PickSorter() {
177 public void sort(List<IElement> elements) {
178 Collections.sort(elements, new Comparator<IElement>() {
180 public int compare(IElement e1, IElement e2) {
181 boolean isConn1 = PickFilter.FILTER_CONNECTION_EDGES.accept(e1);
182 boolean isConn2 = PickFilter.FILTER_CONNECTION_EDGES.accept(e2);
183 if (!isConn1 && isConn2)
185 if (isConn1 && !isConn2)
192 public static final PickSorter CONNECTIONS_FIRST = new PickSorter() {
194 public void sort(List<IElement> elements) {
195 Collections.sort(elements, new Comparator<IElement>() {
197 public int compare(IElement e1, IElement e2) {
198 boolean isConn1 = PickFilter.FILTER_CONNECTION_EDGES.accept(e1);
199 boolean isConn2 = PickFilter.FILTER_CONNECTION_EDGES.accept(e2);
200 if (!isConn1 && isConn2)
202 if (isConn1 && !isConn2)
211 * First use the default sorting if available, then sort successive connections by their distance to cursor.
213 public static PickSorter connectionSorter(PickSorter sorter, double x, double y) {
214 return new PickSorter() {
216 private double getDistanceSqToRouteGraphConnection(RouteGraphNode rgn, double x, double y) {
217 double minDistanceSq = Double.MAX_VALUE;
218 for (RouteLine line : rgn.getRouteGraph().getAllLines()) {
219 ArrayList<Segment> segments = new ArrayList<Segment>();
220 line.collectSegments(segments);
221 for (Segment segment : segments) {
222 RoutePoint p1 = segment.p1;
223 RoutePoint p2 = segment.p2;
225 double distanceSq = Line2D.ptSegDistSq(p1.getX(), p1.getY(), p2.getX(), p2.getY(), x, y);
226 if (distanceSq < minDistanceSq) {
227 minDistanceSq = distanceSq;
231 return minDistanceSq;
234 private void sortConnections(List<IElement> elements) {
235 List<Pair<Double, IElement>> connections = new ArrayList<>(elements.size());
237 for (int i = 0; i < elements.size(); i++) {
238 IElement element = elements.get(i);
239 RouteGraphNode rgn = element.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
241 double distanceSq = getDistanceSqToRouteGraphConnection(rgn, x, y);
242 connections.add(Pair.make(distanceSq, element));
245 if (rgn == null || i == elements.size() - 1) {
246 Collections.sort(connections, new Comparator<Pair<Double, IElement>>() {
248 public int compare(Pair<Double, IElement> connection1, Pair<Double, IElement> connection2) {
249 return Double.compare(connection2.first, connection1.first);
252 for (Pair<Double, IElement> connection : connections) {
253 elements.set(tail, connection.second);
263 public void sort(PickRequest request, List<IElement> elements) {
265 sorter.sort(request, elements);
266 sortConnections(elements);
270 public void sort(List<IElement> elements) {
272 sorter.sort(elements);
273 sortConnections(elements);