}
} else {
RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(
+ getContext(),
diagram,
pi.getCanvasPickShape(me.controlPosition),
pi.getPickDistance());
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import org.simantics.Simantics;
import org.simantics.db.Resource;
import org.simantics.db.WriteGraph;
import org.simantics.db.common.request.WriteRequest;
import org.simantics.g2d.element.handler.Children;
import org.simantics.g2d.participant.TransformUtil;
import org.simantics.g2d.participant.WorkbenchStatusLine;
-import org.simantics.scenegraph.g2d.events.MouseEvent;
import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+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.MouseDragBegin;
import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
-import org.simantics.ui.SimanticsUI;
import org.simantics.utils.datastructures.hints.HintListenerAdapter;
import org.simantics.utils.datastructures.hints.IHintContext.Key;
import org.simantics.utils.datastructures.hints.IHintListener;
if (shape == null)
return false;
- PickRequest req = new PickRequest(shape);
+ PickRequest req = new PickRequest(shape).context(getContext());
req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
req.pickFilter = null;
req.pickSorter = NODES_LAST;
final AtomicReference<Resource> newBp = new AtomicReference<Resource>();
try {
- SimanticsUI.getSession().syncRequest(new WriteRequest() {
+ Simantics.getSession().syncRequest(new WriteRequest() {
@Override
public void perform(WriteGraph graph) throws DatabaseException {
DiagramResource DIA = DiagramResource.getInstance(graph);
if (tis.isEmpty()) {
// Look for potential connection to branch.
- RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(diagram,
+ RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(
+ getContext(), diagram,
getCanvasPickShape(me.controlPosition), getPickDistance());
if (cp != null) {
bsi = initiateRouteGraphConnectTool(cp, me.mouseId);
// ------------------------------------------------------------------------
- static RouteGraphTarget pickRouteGraphConnection(IDiagram diagram, Shape pickShape, double pickDistance) {
+ static RouteGraphTarget pickRouteGraphConnection(ICanvasContext ctx, IDiagram diagram, Shape pickShape, double pickDistance) {
ArrayList<IElement> elements = new ArrayList<IElement>();
- PickRequest req = new PickRequest(pickShape);
+ PickRequest req = new PickRequest(pickShape).context(ctx);
DiagramUtils.pick(diagram, req, elements);
for (Iterator<IElement> it = elements.iterator(); it.hasNext();) {
IElement e = it.next();
import org.simantics.g2d.canvas.ICanvasContext;
import org.simantics.g2d.canvas.ICanvasParticipant;
import org.simantics.g2d.connection.IConnectionAdvisor;
+import org.simantics.g2d.diagram.handler.impl.PickContextImpl;
+import org.simantics.g2d.diagram.participant.ElementPainter;
+import org.simantics.g2d.element.IElement;
import org.simantics.g2d.layers.ILayers;
import org.simantics.g2d.layers.ILayersEditor;
import org.simantics.g2d.routing.IRouter2;
+import org.simantics.scenegraph.INode;
import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
import org.simantics.utils.datastructures.hints.IHintContext.Key;
import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
*/
public static final Key SELECTION_PADDING_SCALE_FACTOR = new KeyOf(Double.class, "SELECTION_PADDING_SCALE_FACTOR");
+ /**
+ * An optional cache map that maps scene graph {@link INode}s to
+ * {@link IElement}s to speed up IElement lookups based on SG nodes.
+ *
+ * @since 1.36.0
+ * @see PickContextImpl
+ * @see ElementPainter
+ */
+ public static final Key NODE_TO_ELEMENT_MAP = new KeyOf(Map.class, "NODE_TO_ELEMENT_MAP");
+
}
}
/**
- * Execute the specified {@link Callback} within a diagram write transaction
+ * Execute the specified <code>callback</code> within a diagram write transaction
* using the {@link TransactionContext} handler available in the
* {@link DiagramClass} of the specified {@link Diagram}. The diagram must
* contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which
import java.util.Comparator;
import java.util.List;
+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;
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);
pickArea = GeometryUtils.transformShape(shape, transform);
}
+ public PickRequest context(ICanvasContext ctx) {
+ this.pickContext = ctx;
+ return this;
+ }
+
public static interface PickFilter {
boolean accept(IElement e);
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
import org.simantics.g2d.diagram.DiagramHints;
import org.simantics.g2d.diagram.IDiagram;
import org.simantics.g2d.diagram.handler.PickRequest;
import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
import org.simantics.g2d.element.ElementUtils;
import org.simantics.g2d.element.IElement;
import org.simantics.g2d.element.handler.ElementLayers;
import org.simantics.g2d.element.handler.Pick2;
import org.simantics.g2d.element.handler.Transform;
import org.simantics.g2d.layers.ILayers;
+import org.simantics.g2d.scenegraph.SceneGraphConstants;
import org.simantics.g2d.utils.GeometryUtils;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* @author Toni Kalajainen
public static final PickContextImpl INSTANCE = new PickContextImpl();
+ private static final Logger LOGGER = LoggerFactory.getLogger(PickContextImpl.class);
+
+ private static final boolean PERF = false;
+
+ private static final ThreadLocal<Rectangle2D> perThreadElementBounds = ThreadLocal.withInitial(() -> new Rectangle2D.Double());
+
@Override
public void pick(
IDiagram diagram,
assert(request!=null);
assert(finalResult!=null);
- ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
+ long startTime = PERF ? System.nanoTime() : 0L;
+
+ List<IElement> result = pickElements(diagram, request);
- Collection<IElement> result = finalResult;
- if (request.pickSorter != null) {
- // Need a temporary List<IElement> for PickSorter
- result = new ArrayList<IElement>();
+ if (PERF) {
+ long endTime = System.nanoTime();
+ LOGGER.info("[picked " + result.size() + " elements @ " + request.pickArea + "] total pick time : " + ((endTime - startTime)*1e-6));
}
- Rectangle2D elementBounds = new Rectangle2D.Double();
- nextElement:
- for (IElement e : diagram.getSnapshot())
- {
+
+ if (!result.isEmpty()) {
+ if (request.pickSorter != null) {
+ List<IElement> elems = new ArrayList<>(result);
+ request.pickSorter.sort(elems);
+ finalResult.addAll(elems);
+ } else {
+ finalResult.addAll(result);
+ }
+ }
+ }
+
+ private static class PickFilter implements Predicate<IElement> {
+
+ private final PickRequest request;
+ private final ILayers layers;
+ private final boolean checkLayers;
+
+ public PickFilter(PickRequest request, ILayers layers) {
+ this.request = request;
+ this.layers = layers;
+ this.checkLayers = layers != null && !layers.getIgnoreFocusSettings();
+ }
+
+ @Override
+ public boolean test(IElement e) {
// Ignore hidden elements.
if (ElementUtils.isHidden(e))
- continue;
+ return false;
ElementClass ec = e.getElementClass();
-
- if(layers != null && !layers.getIgnoreFocusSettings()) {
+
+ if (checkLayers) {
ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
- if(el != null) {
- if(!el.isFocusable(e, layers)) continue;
- }
- }
-
- if (request.pickFilter!=null && !request.pickFilter.accept(e)) continue;
-
- Transform t = e.getElementClass().getSingleItem(Transform.class);
- AffineTransform canvasToElement = t.getTransform(e);
- if (canvasToElement==null) continue;
- double det = canvasToElement.getDeterminant();
- if (det == 0) {
- // Singular transform, just reset the rotation/scaling part to
- // allow picking to proceed.
- // TODO: this may modify the internal transform value of an element which is not intended
- canvasToElement.setToTranslation(
- canvasToElement.getTranslateX(),
- canvasToElement.getTranslateY());
+ if (el != null && !el.isFocusable(e, layers))
+ return false;
}
- // Get bounds, ignore elements that have no bounds
- InternalSize b = e.getElementClass().getAtMostOneItemOfClass(InternalSize.class);
- if (b==null) continue;
- elementBounds.setFrame(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
- b.getBounds(e, elementBounds);
- if (Double.isNaN(elementBounds.getWidth()) || Double.isNaN(elementBounds.getHeight()))
- continue;
-
- Shape elementBoundsOnCanvas = GeometryUtils.transformShape(elementBounds, canvasToElement);
- if (elementBoundsOnCanvas instanceof Rectangle2D)
- elementBoundsOnCanvas = elementBoundsOnCanvas.getBounds2D();
- elementBoundsOnCanvas = elementBoundsOnCanvas.getBounds2D();
- org.simantics.scenegraph.utils.GeometryUtils.expandRectangle((Rectangle2D)elementBoundsOnCanvas, 1e-3, 1e-3, 1e-3, 1e-3);
-
+ if (request.pickFilter != null && !request.pickFilter.accept(e))
+ return false;
+
+ // Get InternalSize for retrieving bounds, ignore elements that have no bounds
+ InternalSize b = ec.getAtMostOneItemOfClass(InternalSize.class);
+ if (b == null)
+ return false;
+
+ // Anything that is without a transformation is not pickable
+ AffineTransform canvasToElement = getTransform(e);
+ if (canvasToElement == null)
+ return false;
+
+ Rectangle2D elementBoundsOnCanvas = getElementBounds(e, b, canvasToElement);
+ if (elementBoundsOnCanvas == null)
+ return false;
+
// Pick with pick handler(s)
- List<Pick> pickHandlers = e.getElementClass().getItemsByClass(Pick.class);
- if (!pickHandlers.isEmpty())
- {
- // Rough filtering with bounds
+ List<Pick> pickHandlers = ec.getItemsByClass(Pick.class);
+ if (!pickHandlers.isEmpty()) {
+ // Rough filtering with bounds
if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas)) {
-// System.out.println("Element bounds discards " + e.getElementClass());
- continue;
+ // System.out.println("Element bounds discards " + e.getElementClass());
+ return false;
}
-
- // Convert pick shape to element coordinates
-// AffineTransform elementToCanvas;
-// try {
-// elementToCanvas = canvasToElement.createInverse();
-// } catch (NoninvertibleTransformException e1) {
-// throw new RuntimeException(e1);
-// }
-// Shape pickShapeInElementCoords = GeometryUtils.transformShape(request.pickArea, elementToCanvas);
- for (Pick p : pickHandlers)
- {
- if (p instanceof Pick2) {
- Pick2 p2 = (Pick2) p;
- //if (p2.pick(e, pickShapeInElementCoords, request.pickPolicy, result) > 0)
- if (p2.pick(e, request.pickArea, request.pickPolicy, result) > 0)
- continue nextElement;
- } else {
- //if (p.pickTest(e, pickShapeInElementCoords, request.pickPolicy)) {
- if (p.pickTest(e, request.pickArea, request.pickPolicy)) {
- result.add(e);
- continue nextElement;
- }
+
+ // NOTE: this doesn't support Pick2 interface anymore
+ for (Pick p : pickHandlers) {
+ if (p.pickTest(e, request.pickArea, request.pickPolicy)) {
+ return true;
}
}
- continue nextElement;
+ return false;
}
-
- // Pick with shape handler(s)
- List<Outline> shapeHandlers = e.getElementClass().getItemsByClass(Outline.class);
- if (!shapeHandlers.isEmpty())
- {
- // Rough filtering with bounds
- if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas)) continue;
-
- // Convert pick shape to element coordinates
- AffineTransform elementToCanvas;
- try {
- elementToCanvas = canvasToElement.createInverse();
- } catch (NoninvertibleTransformException e1) {
- throw new RuntimeException(e1);
+
+ // Pick with Outline handler(s)
+ List<Outline> outlineHandlers = ec.getItemsByClass(Outline.class);
+ if (!outlineHandlers.isEmpty())
+ return pickByOutline(e, request, elementBoundsOnCanvas, canvasToElement, outlineHandlers);
+
+ // Finally, pick by rectangle
+ return pickByBounds(e, request, elementBoundsOnCanvas);
+ }
+
+ }
+
+ private static Rectangle2D toBoundingRectangle(Shape shape) {
+ if (shape instanceof Rectangle2D)
+ return (Rectangle2D) shape;
+ else
+ return shape.getBounds2D();
+ }
+
+ private static List<IElement> pickElements(IDiagram diagram, PickRequest request) {
+ ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
+
+ // Get the scene graph nodes that intersect the pick-requests pick shape
+ INode spatialRoot = request.pickContext != null
+ ? request.pickContext.getSceneGraph().lookupNode(SceneGraphConstants.SPATIAL_ROOT_NODE_ID)
+ : null;
+
+ if (spatialRoot instanceof RTreeNode) {
+ // Optimized picking version that no longer supports Pick2 interface
+ // and therefore doesn't support connections modelled as
+ // branchpoint/edge subelements of connection elements.
+
+ RTreeNode rtree = (RTreeNode) spatialRoot;
+ Map<INode, IElement> nodeToElement = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+ if (nodeToElement != null) {
+ // The most optimized version
+ return rtree.intersectingNodes(toBoundingRectangle(request.pickArea), new ArrayList<>())
+ .stream()
+ .parallel()
+ .map(nodeToElement::get)
+ .filter(Objects::nonNull)
+ .filter(new PickFilter(request, layers))
+ .collect(Collectors.toList());
+ } else {
+ // Slower version for when DiagramHints.NODE_TO_ELEMENT_MAP is not used
+ Set<IG2DNode> nodes =
+ new HashSet<>(
+ rtree.intersectingNodes(
+ toBoundingRectangle(request.pickArea),
+ new ArrayList<>())
+ );
+
+ return diagram.getSnapshot().stream()
+ .parallel()
+ // Choose only elements that are under the pick region bounds-wise
+ .filter(e -> {
+ INode node = e.getHint(ElementHints.KEY_SG_NODE);
+ return nodes.contains(node);
+ })
+ // Perform comprehensive picking
+ .filter(new PickFilter(request, layers))
+ .collect(Collectors.toList());
+ }
+
+ } else {
+
+ // Fall-back logic that ends up processing everything.
+ // This still supports all the old logic and the Pick2
+ // interface in element classes.
+ List<IElement> result = new ArrayList<>();
+ boolean checkLayers = layers != null && !layers.getIgnoreFocusSettings();
+
+ // Do not do this in parallel mode to keep results in proper Z order
+ diagram.getSnapshot().stream().forEachOrdered(e -> {
+ // Ignore hidden elements.
+ if (ElementUtils.isHidden(e))
+ return;
+
+ ElementClass ec = e.getElementClass();
+
+ if (checkLayers) {
+ ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
+ if (el != null && !el.isFocusable(e, layers))
+ return;
}
- Shape pickShapeInElementCoords = GeometryUtils.transformShape(request.pickArea, elementToCanvas);
-
- // Intersection with one shape is enough
- if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
- {
- for (Outline es : shapeHandlers)
- {
- Shape elementShape = es.getElementShape(e);
- if (elementShape==null) continue nextElement;
- if (GeometryUtils.intersects(pickShapeInElementCoords, elementShape))
- {
- result.add(e);
- continue nextElement;
+
+ if (request.pickFilter != null && !request.pickFilter.accept(e))
+ return;
+
+ // Get InternalSize for retrieving bounds, ignore elements that have no bounds
+ InternalSize b = ec.getAtMostOneItemOfClass(InternalSize.class);
+ if (b == null)
+ return;
+
+ // Anything that is without a transformation is not pickable
+ AffineTransform canvasToElement = getTransform(e);
+ if (canvasToElement == null)
+ return;
+
+ Rectangle2D elementBoundsOnCanvas = getElementBounds(e, b, canvasToElement);
+ if (elementBoundsOnCanvas == null)
+ return;
+
+ // Pick with pick handler(s)
+ List<Pick> pickHandlers = ec.getItemsByClass(Pick.class);
+ if (!pickHandlers.isEmpty()) {
+ // Rough filtering with bounds
+ if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas)) {
+ // System.out.println("Element bounds discards " + e.getElementClass());
+ return;
+ }
+
+ for (Pick p : pickHandlers) {
+ if (p instanceof Pick2) {
+ Pick2 p2 = (Pick2) p;
+ if (p2.pick(e, request.pickArea, request.pickPolicy, result) > 0)
+ return;
+ } else {
+ if (p.pickTest(e, request.pickArea, request.pickPolicy)) {
+ result.add(e);
+ return;
+ }
}
}
- continue nextElement;
+ return;
}
-
- // Contains of all shapes is required
- if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
- {
- for (Outline es : shapeHandlers)
- {
- Shape elementShape = es.getElementShape(e);
- if (!GeometryUtils.contains(pickShapeInElementCoords, elementShape))
- continue nextElement;
+
+ // Pick with shape handler(s)
+ List<Outline> outlineHandlers = ec.getItemsByClass(Outline.class);
+ if (!outlineHandlers.isEmpty()) {
+ if (pickByOutline(e, request, elementBoundsOnCanvas, canvasToElement, outlineHandlers)) {
+ result.add(e);
}
- result.add(e);
- continue nextElement;
+ return;
}
- continue nextElement;
- }
-
- // Pick by rectangle
- if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
- {
- if (GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas))
+
+ // Finally, pick by bounding rectangle
+ if (pickByBounds(e, request, elementBoundsOnCanvas))
result.add(e);
+ });
+
+ return result;
+
+ }
+ }
+
+ private static AffineTransform getTransform(IElement e) {
+ // Anything that is without a transformation is not pickable
+ Transform t = e.getElementClass().getSingleItem(Transform.class);
+ AffineTransform canvasToElement = t.getTransform(e);
+ if (canvasToElement == null)
+ return null;
+
+ double det = canvasToElement.getDeterminant();
+ if (det == 0) {
+ // Singular transform, only take the translation from it to move on.
+ canvasToElement = AffineTransform.getTranslateInstance(canvasToElement.getTranslateX(), canvasToElement.getTranslateY());
+ }
+
+ return canvasToElement;
+ }
+
+ private static Rectangle2D getElementBounds(IElement e, InternalSize b, AffineTransform transform) {
+ Rectangle2D elementBounds = perThreadElementBounds.get();
+ elementBounds.setFrame(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+ b.getBounds(e, elementBounds);
+ if (Double.isNaN(elementBounds.getWidth()) || Double.isNaN(elementBounds.getHeight()))
+ return null;
+
+ // Get optionally transformed axis-aligned bounds of the element,
+ // expanded by 1mm in each direction.
+ Rectangle2D transformedBounds = transform != null
+ ? toBoundingRectangle(GeometryUtils.transformShape(elementBounds, transform))
+ : elementBounds;
+ return org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(transformedBounds, 1e-3);
+ }
+
+ private static boolean pickByOutline(IElement e, PickRequest request, Rectangle2D elementBoundsOnCanvas, AffineTransform canvasToElement, List<Outline> outlineHandlers) {
+ // Rough filtering with bounds
+ if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas))
+ return false;
+
+ // Convert pick shape to element coordinates
+ AffineTransform elementToCanvas;
+ try {
+ elementToCanvas = canvasToElement.createInverse();
+ } catch (NoninvertibleTransformException ex) {
+ throw new RuntimeException(ex);
+ }
+ Shape pickShapeInElementCoords = GeometryUtils.transformShape(request.pickArea, elementToCanvas);
+
+ // Intersection with one shape is enough
+ if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) {
+ for (Outline es : outlineHandlers) {
+ Shape elementShape = es.getElementShape(e);
+ if (elementShape == null)
+ return false;
+ if (GeometryUtils.intersects(pickShapeInElementCoords, elementShape)) {
+ return true;
+ }
}
-
- else
-
- if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
- {
- if (GeometryUtils.contains(request.pickArea, elementBoundsOnCanvas))
- result.add(e);
+ return false;
+ }
+
+ // Contains of all shapes is required
+ if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) {
+ for (Outline es : outlineHandlers) {
+ Shape elementShape = es.getElementShape(e);
+ if (!GeometryUtils.contains(pickShapeInElementCoords, elementShape))
+ return false;
}
-
+ return true;
}
- if (request.pickSorter != null) {
- request.pickSorter.sort((List<IElement>) result);
- finalResult.addAll(result);
+ return false;
+ }
+
+ private static boolean pickByBounds(IElement e, PickRequest request, Rectangle2D elementBoundsOnCanvas) {
+ if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) {
+ if (GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas))
+ return true;
+ } else if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) {
+ if (GeometryUtils.contains(request.pickArea, elementBoundsOnCanvas))
+ return true;
}
+ return false;
}
}
// Pick element under the mouse
Point2D controlPos = me.controlPosition;
Point2D diagramPos = util.controlToCanvas(controlPos, null);
- PickRequest req = new PickRequest(diagramPos);
+ PickRequest req = new PickRequest(diagramPos).context(getContext());
req.pickSorter = pickSorter;
//req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;
ArrayList<IElement> result = new ArrayList<IElement>();
/*******************************************************************************
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * Copyright (c) 2007, 2018 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
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
+ * Semantum Oy - GitLab issue #66
*******************************************************************************/
package org.simantics.g2d.diagram.participant;
}
private static final boolean DEBUG = false;
+ private static final boolean NODE_TO_ELEMENT_MAPPING = true;
public static final int ELEMENT_PAINT_PRIORITY = 10;
return;
if (oldValue != null) {
+ Map<INode, IElement> nodeToElementMap = oldValue.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
for (IElement e : oldValue.getElements()) {
- removeElement(e);
+ removeElement(oldValue, e, nodeToElementMap);
}
oldValue.removeCompositionListener(this);
}
if (newValue != null) {
+ diagram.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+ Map<INode, IElement> nodeElementMap = NODE_TO_ELEMENT_MAPPING ? new HashMap<>() : null;
+ if (nodeElementMap != null)
+ diagram.setHint(DiagramHints.NODE_TO_ELEMENT_MAP, nodeElementMap);
+
for (IElement e : newValue.getElements()) {
- addElement(e, false);
+ addElement(newValue, e, false, nodeElementMap);
}
newValue.addCompositionListener(this);
public void initSG(G2DParentNode parent) {
diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);
diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);
- elementParent = diagramParent.addNode("spatialRoot", RTreeNode.class);
+ elementParent = diagramParent.addNode(SceneGraphConstants.SPATIAL_ROOT_NODE_NAME, RTreeNode.class);
+ elementParent.setLookupId(SceneGraphConstants.SPATIAL_ROOT_NODE_ID);
elementParent.setZIndex(0);
}
if (DEBUG)
System.out.println("EP.onElementAdded(" + d + ", " + e + ")");
+ Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
if (inDiagramTransaction()) {
- addElement(e, false);
+ addElement(d, e, false, nodeElementMap);
} else {
- addElement(e, true);
+ addElement(d, e, true, nodeElementMap);
}
}
@Override
if (DEBUG)
System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");
- removeElement(e);
+ removeElement(d, e, diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP));
}
@Override
if (DEBUG)
System.out.println("EP.elementChildrenChanged: " + event);
+ Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+
for (IElement removed : event.removed) {
- removeElement(removed);
+ removeElement(diagram, removed, nodeElementMap);
}
for (IElement added : event.added) {
- addElement(added, false);
+ addElement(diagram, added, false, nodeElementMap);
}
}
private final List<IElement> childrenTemp = new ArrayList<IElement>();
- public void addElement(IElement e, boolean synchronizeSceneGraphNow) {
+ public void addElement(IDiagram d, IElement e, boolean synchronizeSceneGraphNow, Map<INode, IElement> nodeElementMap) {
if (DEBUG)
System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");
holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
e.setHint(sgKey, holder);
holder.setZIndex(parentNode.getNodeCount() + 1);
+ if (nodeElementMap != null)
+ nodeElementMap.put(holder, e);
}
} else {
holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
e.setHint(sgKey, holder);
holder.setZIndex(parentNode.getNodeCount() + 1);
+ if (nodeElementMap != null)
+ nodeElementMap.put(holder, e);
}
}
children.getChildren(e, childrenTemp);
//System.out.println("children: " + childrenTemp);
for (IElement child : childrenTemp) {
- addElement(child, false);
+ addElement(d, child, false, nodeElementMap);
}
childrenTemp.clear();
}
//setTreeDirty();
}
- protected void removeElement(IElement e) {
+ protected void removeElement(IDiagram d, IElement e, Map<INode, IElement> nodeElementMap) {
if (DEBUG)
System.out.println("EP.removeElement(" + e + ")");
Node n = e.removeHint(sgKey);
if (n != null) {
n.remove();
+ if (nodeElementMap != null)
+ nodeElementMap.remove(n);
}
}
// Paint terminals normally
if (paintAreaTerminals || paintPointTerminals) {
- List<TerminalInfo> pickedTerminals = TerminalUtil.pickTerminals(diagram, null, paintPointTerminals, paintAreaTerminals);
+ List<TerminalInfo> pickedTerminals = TerminalUtil.pickTerminals(getContext(), diagram, null, paintPointTerminals, paintAreaTerminals);
paintTerminals(node, Color.BLUE, diagram, null, pickedTerminals, null);
if(pickedTerminals.size() > 0) repaint = true;
}
Rectangle2D controlPickRect = getPickRectangle(mi.controlPosition.getX(), mi.controlPosition.getY());
Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, invTx);
- List<TerminalInfo> tis = TerminalUtil.pickTerminals(diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals);
+ List<TerminalInfo> tis = TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals);
paintTerminals(node, Color.RED, diagram, canvasPickRect.getBounds2D(), tis, strategy);
if(tis.size() > 0) repaint = true;
}
@Dependency Selection sel;
@Dependency PickContext pickContext;
- private final ListenerList zOrderListeners = new ListenerList(ListenerList.IDENTITY);
+ private final ListenerList<ZOrderListener> zOrderListeners = new ListenerList<>(ListenerList.IDENTITY);
public void addOrderListener(ZOrderListener listener) {
zOrderListeners.add(listener);
Shape area = ElementUtils.getElementShapesOnDiagram(selectedElements);
if (area==null) return true;
final ArrayList<IElement> pickedElements = new ArrayList<IElement>();
- PickRequest req = new PickRequest(area);
+ PickRequest req = new PickRequest(area).context(getContext());
req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
pickContext.pick(diagram, req, pickedElements);
DiagramUtils.inDiagramTransaction(diagram, TransactionType.WRITE, new Runnable() {
Shape area = ElementUtils.getElementShapesOnDiagram(selectedElements);
if (area==null) return true;
final ArrayList<IElement> pickedElements = new ArrayList<IElement>();
- PickRequest req = new PickRequest(area);
+ PickRequest req = new PickRequest(area).context(getContext());
req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
pickContext.pick(diagram, req, pickedElements);
DiagramUtils.inDiagramTransaction(diagram, TransactionType.WRITE, new Runnable() {
boolean accumulate = (modifiers & MouseEvent.SHIFT_MASK) != 0;
Set<IElement> boxSelection = new HashSet<IElement>();
- PickRequest request = new PickRequest(rect);
+ PickRequest request = new PickRequest(rect).context(getContext());
request.pickPolicy = boxSelectMode;
pickContext.pick(diagram, request, boxSelection);
Shape canvasPickRect = getCanvasPickShape(controlPos);
if (canvasPickRect == null)
return Collections.emptyList();
- return TerminalUtil.pickTerminals(diagram, canvasPickRect, true, true);
+ return TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, true, true);
}
public TerminalInfo pickTerminal(Point2D controlPos)
Shape canvasPickRect = getCanvasPickShape(controlPos);
if (canvasPickRect == null)
return null;
- TerminalInfo ti = TerminalUtil.pickTerminal(diagram, canvasPickRect);
+ TerminalInfo ti = TerminalUtil.pickTerminal(getContext(), diagram, canvasPickRect);
return ti;
}
Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
int selectionId = me.mouseId;
- PickRequest req = new PickRequest(canvasPickRect);
+ PickRequest req = new PickRequest(canvasPickRect).context(getContext());
req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
req.pickSorter = pickSorter;
//req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;
Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
int selectionId = me.mouseId;
- PickRequest req = new PickRequest(canvasPickRect);
+ PickRequest req = new PickRequest(canvasPickRect).context(getContext());
req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
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);
+ PickRequest req = new PickRequest(me.startCanvasPos).context(getContext());
req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
List<IElement> picks = new ArrayList<IElement>();
pickContext.pick(diagram, req, picks);
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);
+ req = new PickRequest(canvasPickRect).context(getContext());
req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
pickContext.pick(diagram, req, picks);
//System.out.println("2nd try: " + picks);
import java.util.Comparator;
import java.util.List;
+import org.simantics.g2d.canvas.ICanvasContext;
import org.simantics.g2d.diagram.DiagramUtils;
import org.simantics.g2d.diagram.IDiagram;
import org.simantics.g2d.diagram.handler.PickRequest;
* @param pickAreaTerminals pick terminals that have a shape
* @return terminals in z-order (bottom to top)
*/
- public static List<TerminalInfo> pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
+ public static List<TerminalInfo> pickTerminals(ICanvasContext ctx, IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
{
boolean clearElements = false;
List<IElement> elements = null;
elements = ELEMENTS.get();
elements.clear();
clearElements = true;
- PickRequest req = new PickRequest(pickShape);
+ PickRequest req = new PickRequest(pickShape).context(ctx);
DiagramUtils.pick(d, req, elements);
} else {
// Select all terminals
* @param pickShape pick area (in diagram coordinate system)
* @return terminals in z-order (bottom to top)
*/
- public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)
+ public static TerminalInfo pickTerminal(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
{
ArrayList<IElement> elements = ELEMENTS.get();
elements.clear();
- PickRequest req = new PickRequest(pickShape);
+ PickRequest req = new PickRequest(pickShape).context(ctx);
DiagramUtils.pick(diagram, req, elements);
if (elements.isEmpty())
return null;
* @param pickShape
* @return bends or null
*/
- public BendsInfo pickBends(IDiagram diagram, Shape pickShape)
+ public BendsInfo pickBends(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
{
BendsInfo result = null;
double bestShortestDist = Double.MAX_VALUE;
ArrayList<IElement> elements = ELEMENTS.get();
elements.clear();
- PickRequest req = new PickRequest(pickShape);
+ PickRequest req = new PickRequest(pickShape).context(ctx);
DiagramUtils.pick(diagram, req, elements);
ArrayList<Bend> bends = new ArrayList<>();
public static final String[] SELECTIONS_NODE_PATH = { DATA_NODE_NAME, SELECTIONS_NODE_NAME };
+ public static final String SPATIAL_ROOT_NODE_NAME = "spatialRoot";
+
+ public static final String SPATIAL_ROOT_NODE_ID = SPATIAL_ROOT_NODE_NAME;
+
}
}
private void pickRect(Rectangle2D rect) {
- TerminalInfo terminal = TerminalUtil.pickTerminal(diagram, rect);
+ TerminalInfo terminal = TerminalUtil.pickTerminal(getContext(), diagram, rect);
if (terminal != null) {
createMessage(terminal, message -> setMessage(null, message));
} else {
*******************************************************************************/
package org.simantics.scenegraph.g2d.nodes.spatial;
-import gnu.trove.TIntObjectHashMap;
-import gnu.trove.TIntProcedure;
-
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Properties;
import java.util.Set;
import com.infomatiq.jsi.Rectangle;
import com.infomatiq.jsi.rtree.RTree;
+import gnu.trove.TIntObjectHashMap;
+import gnu.trove.TIntProcedure;
+
/**
* A G2D scene graph node that spatially decomposes all of its immediate child
* nodes into an R-Tree structure to optimize the painting of its direct child
return new Rectangle((float) rect.getMinX(), (float) rect.getMinY(), (float) rect.getMaxX(), (float) rect.getMaxY());
}
+ public List<IG2DNode> intersectingNodes(Rectangle2D rect, List<IG2DNode> result) {
+ final Tree tree = getSpatialDecomposition();
+ if (rect == null || tree.bounds == null || containedBy(tree.bounds, rect)) {
+ IG2DNode[] nodes = getSortedNodes();
+ for (IG2DNode node : nodes) {
+ if (node.validate()) {
+ result.add(node);
+ }
+ }
+ } else {
+ tree.rtree.intersects(toRectangle(rect), value -> {
+ //System.out.println("exec: " + value);
+ IG2DNode node = tree.toNodes.get(value);
+ //System.out.println(" node: " + node);
+ if (node == null || !node.validate())
+ return true;
+
+ result.add(node);
+ return true;
+ });
+ }
+ Collections.sort(result, G2DParentNode.G2DNODE_Z_COMPARATOR);
+ return result;
+ }
+
/**
* Determine whether this rectangle is contained by the passed rectangle
*