/******************************************************************************* * Copyright (c) 2007, 2020 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 * Semantum Oy - gitlab #454 *******************************************************************************/ 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; import org.simantics.g2d.element.handler.BendsHandler; 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; /** * * @See {@link GeometryUtils} for intersect and contains tests * @See {@link TransformedRectangle} * @See {@link Area} intersects operations for complex shapes * @author Toni Kalajainen */ public class PickRequest { public static enum PickPolicy { PICK_INTERSECTING_OBJECTS, PICK_CONTAINED_OBJECTS } public Shape pickArea; public PickPolicy pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS; /** Pick filter (null == everything is accepted) */ public PickFilter pickFilter = null; public PickSorter pickSorter = null; /** * Used to optimize picking if provided via R-tree traversal to find * intersecting elements, not everything. */ public ICanvasContext pickContext; public PickRequest(double x, double y) { pickArea = new Rectangle2D.Double(x, y, 1, 1); } public PickRequest(Point2D p) { pickArea = new Rectangle2D.Double(p.getX(), p.getY(), 0.0001, 0.0001); } public PickRequest(Shape shape) { pickArea = shape; } public PickRequest(Shape shape, AffineTransform transform) { pickArea = GeometryUtils.transformShape(shape, transform); } public PickRequest context(ICanvasContext ctx) { this.pickContext = ctx; return this; } public static interface PickFilter { boolean accept(IElement e); public static final PickFilter FILTER_ALL = new PickFilter() { @Override public boolean accept(IElement e) { return true; } }; // Connections public static final PickFilter FILTER_CONNECTIONS = new PickFilter() { @Override public boolean accept(IElement e) { return e.getElementClass().containsClass(ConnectionHandler.class); } @Override public String toString() { return "FILTER_CONNECTIONS"; } }; // Connection edges public static final PickFilter FILTER_CONNECTION_EDGES = new PickFilter() { @Override public boolean accept(IElement e) { return e.getElementClass().containsClass(BendsHandler.class) || e.getElementClass().containsClass(ConnectionSelectionOutline.class); } @Override public String toString() { return "FILTER_CONNECTION_EDGES"; } }; // Connections public static final PickFilter FILTER_BRANCH_POINT = new PickFilter() { @Override public boolean accept(IElement e) { return e.getElementClass().containsClass(BranchPoint.class); } @Override public String toString() { return "FILTER_BRANCH_POINTS"; } }; // Anything that has terminals public static final PickFilter FILTER_NODES = new PickFilter() { @Override public boolean accept(IElement e) { return e.getElementClass().containsClass(TerminalTopology.class); } @Override public String toString() { return "FILTER_NODES"; } }; public static final PickFilter FILTER_MONITORS = new PickFilter() { @Override public boolean accept(IElement e) { return e.getElementClass().containsClass(MonitorHandler.class); } @Override public String toString() { return "FILTER_MONITORS"; } }; } public static interface PickSorter { /** * Sorts the specified element list. * * @param elements the element list to sort */ void sort(List elements); /** * Extended interface-method that receives the pick request in addition to the * picked elements to be sorted. Allows e.g. looking at the pick area in the * sorter. * *

* The default implementation just invokes {@link #sort(List)} ignoring the pick * request. The default implementation also keeps PickSorter API/ABI-compatible. * * @param request the original pick request that produced the hits listed in * elements * @param elements the element list to sort * * @author Tuukka Lehtonen * @since 1.43.0, 1.35.3 */ default void sort(PickRequest request, List elements) { sort(elements); } // public static final PickSorter CONNECTIONS_LAST = new PickSorter() { @Override public void sort(List elements) { Collections.sort(elements, new Comparator() { @Override public int compare(IElement e1, IElement e2) { boolean isConn1 = PickFilter.FILTER_CONNECTION_EDGES.accept(e1); boolean isConn2 = PickFilter.FILTER_CONNECTION_EDGES.accept(e2); if (!isConn1 && isConn2) return -1; if (isConn1 && !isConn2) return 1; return 0; } }); } }; public static final PickSorter CONNECTIONS_FIRST = new PickSorter() { @Override public void sort(List elements) { Collections.sort(elements, new Comparator() { @Override public int compare(IElement e1, IElement e2) { boolean isConn1 = PickFilter.FILTER_CONNECTION_EDGES.accept(e1); boolean isConn2 = PickFilter.FILTER_CONNECTION_EDGES.accept(e2); if (!isConn1 && isConn2) return 1; if (isConn1 && !isConn2) return -1; return 0; } }); } }; /* * 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; } private void sortConnections(List 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(Pair.make(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; } } } @Override public void sort(PickRequest request, List elements) { if (sorter != null) sorter.sort(request, elements); sortConnections(elements); } @Override public void sort(List elements) { if (sorter != null) sorter.sort(elements); sortConnections(elements); } }; } } }