package org.simantics.district.network.ui; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.simantics.Simantics; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.diagram.ui.DiagramModelHints; 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.handler.PickRequest.PickPolicy; import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.element.IElement; import org.simantics.g2d.participant.TransformUtil; import org.simantics.g2d.utils.GeometryUtils; import org.simantics.scenegraph.g2d.G2DNode; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.events.EventTypes; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; public class NetworkDrawingParticipant extends AbstractDiagramParticipant { /** * A hint key for terminal pick distance in control pixels. * @see #PICK_DIST */ public static final Key KEY_PICK_DISTANCE = new KeyOf(Double.class, "PICK_DISTANCE"); /** * Default terminal pick distance in control pixels. * @see #DEFAULT_PICK_DISTANCE */ public static final double PICK_DIST = 10; private NetworkDrawingNode node; @Dependency Selection selection; @Dependency TransformUtil util; @Dependency PickContext pickContext; @SGInit public void initSG(G2DParentNode parent) { node = parent.addNode("networkDrawingNode", NetworkDrawingNode.class); } @Override protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) { node.setDiagram(newDiagram); } @EventHandler(priority = 1 << 21) public boolean handleClick(MouseClickEvent me) { boolean isLeft = me.button == MouseEvent.LEFT_BUTTON; boolean isRight = me.button == MouseEvent.RIGHT_BUTTON; if (!isLeft && !isRight) return false; boolean isShiftPressed = me.hasAllModifiers(MouseEvent.SHIFT_MASK); int selectionId = me.mouseId; 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()); PickRequest req = new PickRequest(canvasPickRect); req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS; //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST; List pickables = new ArrayList(); pickContext.pick(diagram, req, pickables); Set currentSelection = selection.getSelection(selectionId); if (!pickables.isEmpty()) { /* * Select the one object the mouse points to. If multiple object * are picked, select the one that is after the earliest by * index of the current selection when shift is pressed. Otherwise * always pick the topmost element. */ IElement selectedPick = isShiftPressed ? rotatingPick(currentSelection, pickables) : pickables.get(pickables.size() - 1); // Only select when // 1. the selection would actually change // AND // 2.1. left button was pressed // OR // 2.2. right button was pressed and the element to-be-selected // is NOT a part of the current selection if (!Collections.singleton(selectedPick).equals(currentSelection) && (isLeft || (isRight && !currentSelection.contains(selectedPick)))) { selection.setSelection(selectionId, selectedPick); } } return false; } private double getPickDistance() { Double pickDistance = getHint(KEY_PICK_DISTANCE); return pickDistance == null ? PICK_DIST : Math.max(pickDistance, 0); } private IElement rotatingPick(int selectionId, List pickables) { Set sel = selection.getSelection(selectionId); return rotatingPick(sel, pickables); } private IElement rotatingPick(Set sel, List pickables) { int earliestIndex = pickables.size(); for (int i = pickables.size() - 1; i >= 0; --i) { if (sel.contains(pickables.get(i))) { earliestIndex = i; break; } } if (earliestIndex == 0) earliestIndex = pickables.size(); IElement selectedPick = pickables.get(earliestIndex - 1); return selectedPick; } public static class NetworkDrawingNode extends G2DNode { private static final long serialVersionUID = -3475301184009620573L; private List nodes = new ArrayList<>(); private Rectangle2D rect = new Rectangle2D.Double(10, 10, 10, 10); private Set paths = new HashSet<>(); private Resource diagramResource; private boolean committed; @Override public void init() { super.init(); addEventHandler(this); } public void setDiagram(IDiagram diagram) { if (diagram != null) this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); } @Override public void render(Graphics2D g2d) { Color old = g2d.getColor(); g2d.setColor(Color.BLUE); paths.forEach(p -> { g2d.draw(p); }); g2d.setColor(old); } @Override public Rectangle2D getBoundsInLocal() { return rect.getBounds2D(); } @Override public int getEventMask() { return EventTypes.MouseMask; } @Override protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) { // nodes to path2d Point2D start = null; Point2D end = null; Iterator nodeIter = nodes.iterator(); while (nodeIter.hasNext()) { if (end == null) { start = nodeIter.next(); } else { start = end; } end = nodeIter.next(); createEdge(start, end); } nodes.clear(); committed = true; repaint(); return true; } private void createEdge(Point2D start, Point2D end) { double[] startCoords = new double[] { start.getX(), start.getY() }; double[] endCoords = new double[] { end.getX(), end.getY() }; DNEdgeBuilder builder = new DNEdgeBuilder(diagramResource); Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { builder.create(graph, startCoords, endCoords); } }); } @Override protected boolean mouseClicked(MouseClickEvent e) { if (committed) { committed = false; return false; } Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double()); nodes.add(new Point2D.Double(localPos.getX(), localPos.getY())); return super.mouseClicked(e); } @Override protected boolean mouseMoved(MouseMovedEvent e) { return super.mouseMoved(e); } } }