+\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