-\r
-package org.simantics.district.network.ui;\r
-\r
-import java.awt.Color;\r
-import java.awt.Graphics2D;\r
-import java.awt.Shape;\r
-import java.awt.geom.Path2D;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Set;\r
-\r
-import org.simantics.Simantics;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.diagram.ui.DiagramModelHints;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.handler.PickContext;\r
-import org.simantics.g2d.diagram.handler.PickRequest;\r
-import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;\r
-import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
-import org.simantics.g2d.diagram.participant.Selection;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.g2d.utils.GeometryUtils;\r
-import org.simantics.scenegraph.g2d.G2DNode;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.events.EventTypes;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent;\r
-import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
-import org.simantics.scenegraph.utils.NodeUtil;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
-\r
-public class NetworkDrawingParticipant extends AbstractDiagramParticipant {\r
-\r
- /**\r
- * A hint key for terminal pick distance in control pixels.\r
- * @see #PICK_DIST\r
- */\r
- public static final Key KEY_PICK_DISTANCE = new KeyOf(Double.class, "PICK_DISTANCE");\r
-\r
- /**\r
- * Default terminal pick distance in control pixels.\r
- * @see #DEFAULT_PICK_DISTANCE\r
- */\r
- public static final double PICK_DIST = 10;\r
- \r
- private NetworkDrawingNode node;\r
- \r
- @Dependency Selection selection;\r
- @Dependency TransformUtil util;\r
- @Dependency PickContext pickContext;\r
- \r
- @SGInit\r
- public void initSG(G2DParentNode parent) {\r
- node = parent.addNode("networkDrawingNode", NetworkDrawingNode.class);\r
- }\r
- \r
- @Override\r
- protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {\r
- node.setDiagram(newDiagram);\r
- }\r
- \r
- @EventHandler(priority = 1 << 21)\r
- public boolean handleClick(MouseClickEvent me) {\r
- \r
- boolean isLeft = me.button == MouseEvent.LEFT_BUTTON;\r
- boolean isRight = me.button == MouseEvent.RIGHT_BUTTON;\r
- if (!isLeft && !isRight)\r
- return false;\r
- boolean isShiftPressed = me.hasAllModifiers(MouseEvent.SHIFT_MASK);\r
- \r
- int selectionId = me.mouseId;\r
- \r
- double pickDist = getPickDistance();\r
- Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);\r
- Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());\r
- \r
- PickRequest req = new PickRequest(canvasPickRect);\r
- req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;\r
- //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;\r
- List<IElement> pickables = new ArrayList<IElement>();\r
- pickContext.pick(diagram, req, pickables);\r
- \r
- Set<IElement> currentSelection = selection.getSelection(selectionId);\r
- \r
- if (!pickables.isEmpty()) {\r
- /*\r
- * Select the one object the mouse points to. If multiple object\r
- * are picked, select the one that is after the earliest by\r
- * index of the current selection when shift is pressed. Otherwise\r
- * always pick the topmost element.\r
- */\r
- IElement selectedPick = isShiftPressed\r
- ? rotatingPick(currentSelection, pickables)\r
- : pickables.get(pickables.size() - 1);\r
- \r
- // Only select when\r
- // 1. the selection would actually change\r
- // AND\r
- // 2.1. left button was pressed\r
- // OR\r
- // 2.2. right button was pressed and the element to-be-selected\r
- // is NOT a part of the current selection\r
- if (!Collections.singleton(selectedPick).equals(currentSelection)\r
- && (isLeft || (isRight && !currentSelection.contains(selectedPick)))) {\r
- selection.setSelection(selectionId, selectedPick);\r
- }\r
- }\r
- \r
- return false;\r
- }\r
- \r
- private double getPickDistance() {\r
- Double pickDistance = getHint(KEY_PICK_DISTANCE);\r
- return pickDistance == null ? PICK_DIST : Math.max(pickDistance, 0);\r
- }\r
- \r
- private IElement rotatingPick(int selectionId, List<IElement> pickables) {\r
- Set<IElement> sel = selection.getSelection(selectionId);\r
- return rotatingPick(sel, pickables);\r
- }\r
-\r
- private IElement rotatingPick(Set<IElement> sel, List<IElement> pickables) {\r
- int earliestIndex = pickables.size();\r
- for (int i = pickables.size() - 1; i >= 0; --i) {\r
- if (sel.contains(pickables.get(i))) {\r
- earliestIndex = i;\r
- break;\r
- }\r
- }\r
- if (earliestIndex == 0)\r
- earliestIndex = pickables.size();\r
- IElement selectedPick = pickables.get(earliestIndex - 1);\r
- return selectedPick;\r
- }\r
-\r
- public static class NetworkDrawingNode extends G2DNode {\r
-\r
- private static final long serialVersionUID = -3475301184009620573L;\r
- \r
- private List<Point2D> nodes = new ArrayList<>();\r
-\r
- private Rectangle2D rect = new Rectangle2D.Double(10, 10, 10, 10);\r
-\r
- private Set<Path2D> paths = new HashSet<>();\r
-\r
- private Resource diagramResource;\r
-\r
- private boolean committed;\r
- \r
- @Override\r
- public void init() {\r
- super.init();\r
- addEventHandler(this);\r
- }\r
- \r
- public void setDiagram(IDiagram diagram) {\r
- if (diagram != null)\r
- this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
- }\r
-\r
- @Override\r
- public void render(Graphics2D g2d) {\r
- Color old = g2d.getColor();\r
- g2d.setColor(Color.BLUE);\r
- \r
- paths.forEach(p -> {\r
- g2d.draw(p);\r
- });\r
- \r
- g2d.setColor(old);\r
- }\r
-\r
- @Override\r
- public Rectangle2D getBoundsInLocal() {\r
- return rect.getBounds2D();\r
- }\r
- \r
- @Override\r
- public int getEventMask() {\r
- return EventTypes.MouseMask;\r
- }\r
- \r
- @Override\r
- protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {\r
- // nodes to path2d\r
- Point2D start = null;\r
- Point2D end = null;\r
- Iterator<Point2D> nodeIter = nodes.iterator();\r
- while (nodeIter.hasNext()) {\r
- if (end == null) {\r
- start = nodeIter.next();\r
- } else {\r
- start = end;\r
- }\r
- end = nodeIter.next();\r
- \r
- createEdge(start, end);\r
- }\r
- \r
- nodes.clear();\r
- committed = true;\r
- \r
- repaint();\r
- \r
- return true;\r
- }\r
- \r
- private void createEdge(Point2D start, Point2D end) {\r
- double[] startCoords = new double[] { start.getX(), start.getY() };\r
- double[] endCoords = new double[] { end.getX(), end.getY() };\r
- \r
- DNEdgeBuilder builder = new DNEdgeBuilder(diagramResource);\r
- Simantics.getSession().asyncRequest(new WriteRequest() {\r
- \r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- builder.create(graph, startCoords, endCoords);\r
- }\r
- });\r
- \r
- }\r
- \r
- @Override\r
- protected boolean mouseClicked(MouseClickEvent e) {\r
- if (committed) {\r
- committed = false;\r
- return false;\r
- }\r
- Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double());\r
- nodes.add(new Point2D.Double(localPos.getX(), localPos.getY()));\r
- return super.mouseClicked(e);\r
- }\r
- \r
- @Override\r
- protected boolean mouseMoved(MouseMovedEvent e) {\r
- return super.mouseMoved(e);\r
- }\r
- }\r
-}\r
+
+package org.simantics.district.network.ui;
+
+import java.awt.geom.AffineTransform;
+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.db.Resource;
+import org.simantics.diagram.ui.DiagramModelHints;
+import org.simantics.district.network.ui.adapters.DistrictNetworkEdgeElement;
+import org.simantics.district.network.ui.adapters.DistrictNetworkVertexElement;
+import org.simantics.district.network.ui.nodes.DistrictNetworkVertexNode;
+import org.simantics.district.network.ui.nodes.NetworkDrawingNode;
+import org.simantics.district.network.ui.participants.DynamicVisualisationContributionsParticipant;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.PickContext;
+import org.simantics.g2d.diagram.handler.PickRequest;
+import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.IElement;
+import org.simantics.maps.MapScalingTransform;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.Node;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.utils.GeometryUtils;
+
+public class NetworkDrawingParticipant extends AbstractDiagramParticipant {
+
+ public static final String NETWORK_DRAWING_NODE = "networkDrawingNode";
+
+ @Dependency
+ PickContext pick;
+
+ private NetworkDrawingNode node;
+ private DynamicVisualisationContributionsParticipant dynamicVisualisationContributionsParticipant;
+ private AffineTransform transform;
+
+ /**
+ * Holds the current element for which hover information is shown.
+ * This is just to optimize the
+ */
+ private IElement currentHoverElement;
+
+ public NetworkDrawingParticipant(DynamicVisualisationContributionsParticipant dynamicVisualisationContributionsParticipant, AffineTransform transform) {
+ this.dynamicVisualisationContributionsParticipant = dynamicVisualisationContributionsParticipant;
+ this.transform = transform;
+ }
+
+ @SGInit
+ public void initSG(G2DParentNode parent) {
+ node = parent.addNode(NETWORK_DRAWING_NODE, NetworkDrawingNode.class);
+ node.setTransform(transform);
+ node.setNetworkDrawingParticipant(this);
+ }
+
+ @Override
+ protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
+ node.setDiagram(newDiagram);
+ }
+
+ public boolean pickHoveredElement(Point2D canvasPos, boolean isConnectionTool, AffineTransform viewTransform) {
+ PickRequest req = new PickRequest(getPickRect(canvasPos, viewTransform)).context(getContext());
+ List<IElement> pickables = new ArrayList<>();
+ pick.pick(diagram, req, pickables);
+
+
+ Comparator<IElement> nearestVerticesFirst = (IElement e1, IElement e2) -> {
+ // If there are any vertices in the elements, prefer those primarily.
+ DistrictNetworkVertexNode v1 = e1.getHint(DistrictNetworkVertexElement.KEY_DN_VERTEX_NODE);
+ DistrictNetworkVertexNode v2 = e2.getHint(DistrictNetworkVertexElement.KEY_DN_VERTEX_NODE);
+
+ // Don't reorder edges
+ if ((v1 == null && v2 == null))
+ return 0;
+
+ // Sort vertices in nearest first order
+ if (v1 != null && v2 != null) {
+ Rectangle2D b1 = v1.getBounds();
+ Rectangle2D b2 = v2.getBounds();
+ double dist1 = canvasPos.distanceSq(b1.getCenterX(), b1.getCenterY());
+ double dist2 = canvasPos.distanceSq(b2.getCenterX(), b2.getCenterY());
+ return dist1 < dist2 ? -1 : dist1 > dist2 ? 1 : 0;
+ }
+
+ // Always vertices before edges
+ return v1 != null && v2 == null ? -1 : 1;
+ };
+
+ Collections.sort(pickables, nearestVerticesFirst);
+
+ updateHoveredElement(pickables, true, isConnectionTool, viewTransform);
+ // Will repaint once the async hover info calculation is ready, no need to do it here
+ return false;
+ }
+
+ private boolean updateHoveredElement(List<IElement> elements, boolean hover, boolean isConnectionTool, AffineTransform viewTransform) {
+ if (elements == null || elements.isEmpty()) {
+ currentHoverElement = null;
+ return dynamicVisualisationContributionsParticipant.doHover(false, isConnectionTool);
+ } else {
+ dynamicVisualisationContributionsParticipant.doHover(true, isConnectionTool);
+
+ // we prefer the first picked element only
+ IElement elem = elements.get(0);
+ if (elem.equals(currentHoverElement))
+ return false;
+
+ INode node = elem.getHint(DistrictNetworkVertexElement.KEY_DN_VERTEX_NODE);
+ if (node == null)
+ node = elem.getHint(DistrictNetworkEdgeElement.KEY_DN_EDGE_NODE);
+ if (node == null)
+ return false;
+
+ Resource mapElement = elem.getHint(ElementHints.KEY_OBJECT);
+ Resource runtimeDiagram = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);
+ currentHoverElement = elem;
+ dynamicVisualisationContributionsParticipant.hoverNode(runtimeDiagram, mapElement, node, MapScalingTransform.zoomLevel(viewTransform));
+ return true;
+ }
+ }
+
+ public boolean isHoveringOverNode(Point2D canvasPos, AffineTransform viewTransform) {
+ PickRequest req = new PickRequest(getPickRect(canvasPos, viewTransform)).context(getContext());
+ List<IElement> pickables = new ArrayList<>();
+ pick.pick(diagram, req, pickables);
+ for (IElement elem : pickables) {
+ Node node = elem.getHint(DistrictNetworkVertexElement.KEY_DN_VERTEX_NODE);
+ if (node instanceof DistrictNetworkVertexNode) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Rectangle2D getPickRect(Point2D canvasPos, AffineTransform viewTransform) {
+ double pixelScale = 1.0 / GeometryUtils.getScale(viewTransform);
+ if (Double.isInfinite(pixelScale))
+ pixelScale = 1e-8;
+
+ Rectangle2D pickRect = GeometryUtils.expandRectangle(
+ new Rectangle2D.Double(canvasPos.getX(), canvasPos.getY(), 0, 0),
+ pixelScale * 4);
+
+ return pickRect;
+ }
+
+ public AffineTransform getTransform() {
+ return transform;
+ }
+
+}