package org.simantics.district.network.ui.nodes; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.TimeUnit; 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.db.request.Write; import org.simantics.diagram.elements.DiagramNodeUtil; import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.district.network.DNEdgeBuilder; import org.simantics.district.network.DistrictNetworkUtil; import org.simantics.district.network.ModelledCRS; import org.simantics.district.network.ontology.DistrictNetworkResource; import org.simantics.district.network.ui.NetworkDrawingParticipant; import org.simantics.g2d.canvas.Hints; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.IToolMode; import org.simantics.g2d.diagram.IDiagram; import org.simantics.scenegraph.g2d.G2DNode; import org.simantics.scenegraph.g2d.events.EventTypes; import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent; 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.GeometryUtils; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.utils.threads.ThreadUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NetworkDrawingNode extends G2DNode { private static final Logger LOGGER = LoggerFactory.getLogger(NetworkDrawingNode.class); static class DrawingNode { private List routeNodes = new ArrayList<>(); } private static final long serialVersionUID = -3475301184009620573L; private Point2D currentMousePos = null; private List nodes = new ArrayList<>(); private DrawingNode currentRouteNode = null; private Resource diagramResource; private NetworkDrawingParticipant participant; private IDiagram diagram; private static final Stroke DASHED_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 4.0f, new float[]{4.0f}, 0.0f); private static final Color BLUE_ALPHA = new Color(0, 0, 255, 100); private static final Color RED_ALPHA = new Color(255, 0, 0, 100); private boolean scaleStroke = true; private AffineTransform lastViewTransform = new AffineTransform(); @Override public void init() { super.init(); addEventHandler(this); } public void setNetworkDrawingParticipant(NetworkDrawingParticipant participant) { this.participant = participant; } public void setDiagram(IDiagram diagram) { if (diagram != null) { this.diagram = diagram; this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); } } @Override public void render(Graphics2D g2d) { // Must store this for hover info functionality lastViewTransform = g2d.getTransform(); if (nodes.isEmpty()) return; Color old = g2d.getColor(); Stroke oldStroke = g2d.getStroke(); Iterator dnodeIterator = nodes.iterator(); while (dnodeIterator.hasNext()) { Path2D path = new Path2D.Double(); DrawingNode dnode = dnodeIterator.next(); Iterator nodeIter = dnode.routeNodes.iterator(); if (nodeIter.hasNext()) { Point2D node = nodeIter.next(); path.moveTo(node.getX(), node.getY()); } while (nodeIter.hasNext()) { Point2D node = nodeIter.next(); path.lineTo(node.getX(), node.getY()); } if (!dnodeIterator.hasNext()) { if (currentMousePos != null) path.lineTo(currentMousePos.getX(), currentMousePos.getY()); } if (DASHED_STROKE != null) { if (scaleStroke && DASHED_STROKE instanceof BasicStroke) { BasicStroke bs = GeometryUtils.scaleStroke(DASHED_STROKE, (float) (1.0 / GeometryUtils.getScale(g2d.getTransform()))); g2d.setStroke(bs); } else { g2d.setStroke(DASHED_STROKE); } } g2d.setColor(BLUE_ALPHA); g2d.draw(path); g2d.setColor(RED_ALPHA); BasicStroke stroke = GeometryUtils.scaleStroke(DASHED_STROKE, (float) (1.0 / GeometryUtils.getScale(g2d.getTransform()))); g2d.setStroke(stroke); Point2D currentPoint = path.getCurrentPoint(); g2d.draw(new Rectangle2D.Double(currentPoint.getX() - 0.0001 / 2, currentPoint.getY() - 0.0001 / 2, 0.0001, 0.0001)); } g2d.setStroke(oldStroke); g2d.setColor(old); } @Override public Rectangle2D getBoundsInLocal() { return null; } @Override public int getEventMask() { return EventTypes.AnyMask; } @Override protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) { // nodes to path2d IToolMode mode = getToolMode(); if (mode == Hints.CONNECTTOOL || e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) { // ok, new routenode starts from here Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double()); Point2D.Double pos = new Point2D.Double(localPos.getX(), localPos.getY()); if (currentRouteNode != null) { //currentRouteNode.routeNodes.add(pos); currentRouteNode = new DrawingNode(); currentRouteNode.routeNodes.add(pos); nodes.add(currentRouteNode); } else { // ok, this must be creation of dh_point double scale = getTransform().getScaleY(); double x = ModelledCRS.xToLongitude(pos.getX() / scale); double y = ModelledCRS.yToLatitude(-pos.getY() / scale); boolean leftButton = e.button == MouseEvent.LEFT_BUTTON; ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> { Simantics.getSession().asyncRequest(new Write() { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); Resource mapping = null; if (leftButton) { mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).LeftClickDefaultMapping); } else { mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).RightClickDefaultMapping); } if (mapping == null) { mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).VertexDefaultMapping); } DistrictNetworkUtil.createVertex(graph, diagramResource, new double[] { x, y }, Double.MAX_VALUE, mapping); } }); }, 100, TimeUnit.MILLISECONDS); } repaint(); return true; } return super.mouseDoubleClicked(e); } private void createEdge(DrawingNode node) { Point2D start = node.routeNodes.get(0); Point2D end = node.routeNodes.get(node.routeNodes.size() - 1); double currentPadding = DistrictNetworkVertexNode.width; AffineTransform test = getTransform(); ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(this); AffineTransform tr = ctx.getHintStack().getHint(Hints.KEY_CANVAS_TRANSFORM); AffineTransform testing = new AffineTransform(tr); testing.concatenate(test); double calculateScaleRecip = DistrictNetworkNodeUtils.calculateScaleRecip(testing); double padding = currentPadding * calculateScaleRecip; /* * To convert y-coordinates to map coordinates in ruler, use: * double val = (y-offsetY)/scaleY; * val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val)))); * String str = formatValue(val); */ // TODO: fix scale double scaleY = getTransform().getScaleY(); double scaleX = getTransform().getScaleX(); double startLat = ModelledCRS.yToLatitude(-start.getY() / scaleY); double startLon = ModelledCRS.xToLongitude(start.getX() / scaleX); double endLat = ModelledCRS.yToLatitude(-end.getY() / scaleY); double endLon = ModelledCRS.xToLongitude(end.getX() / scaleX); double[] startCoords = new double[] { startLon, startLat }; double[] endCoords = new double[] { endLon, endLat }; double[] detailedGeometryCoords = new double[node.routeNodes.size() * 2]; int i = 0; for (Point2D p : node.routeNodes) { double lat = ModelledCRS.yToLatitude(-p.getY() / scaleY); double lon = ModelledCRS.xToLongitude(p.getX() / scaleX); detailedGeometryCoords[i++] = lon; detailedGeometryCoords[i++] = lat; } DNEdgeBuilder builder = new DNEdgeBuilder(diagramResource, diagram); Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { builder.create(graph, startCoords, Double.MAX_VALUE, endCoords, Double.MAX_VALUE, detailedGeometryCoords, padding); } }); } @Override protected boolean mouseClicked(MouseClickEvent e) { // check ToolMode IToolMode mode = getToolMode(); if (mode == Hints.CONNECTTOOL || e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) { if (e.button == MouseEvent.RIGHT_BUTTON && !nodes.isEmpty()) { nodes.remove(nodes.size() - 1); } else if (e.button == MouseEvent.LEFT_BUTTON) { Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double()); if (currentRouteNode == null && canStartEdge(localPos)) { // ok, we can start from here currentRouteNode = new DrawingNode(); currentRouteNode.routeNodes.add(new Point2D.Double(localPos.getX(), localPos.getY())); nodes.add(currentRouteNode); } else if (currentRouteNode != null && canStartEdge(localPos)) { // let's commit our new routenode currentRouteNode.routeNodes.add(new Point2D.Double(localPos.getX(), localPos.getY())); Iterator nodeIter = nodes.iterator(); while (nodeIter.hasNext()) { createEdge(nodeIter.next()); } currentRouteNode = null; nodes.clear(); } else if (currentRouteNode != null) { currentRouteNode.routeNodes.add(new Point2D.Double(localPos.getX(), localPos.getY())); } } repaint(); return true; } return super.mouseClicked(e); } private boolean canStartEdge(Point2D currentPos) { return participant.isHoveringOverNode(currentPos, lastViewTransform); } private IToolMode getToolMode() { return participant.getHint(Hints.KEY_TOOL); } @Override protected boolean mouseMoved(MouseMovedEvent e) { IToolMode mode = getToolMode(); boolean repaint = false; Point2D p = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double()); boolean isConnectionTool = mode == Hints.CONNECTTOOL || e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK); // To boost pan perf hovering is only considered if no mouse button is pressed) if (e.buttons == 0 && participant.pickHoveredElement(p, isConnectionTool, lastViewTransform)) { repaint = true; } if (!nodes.isEmpty()) { currentMousePos = p; repaint(); return true; } currentMousePos = null; if (repaint == true) repaint(); return super.mouseMoved(e); } @Override protected boolean keyPressed(KeyPressedEvent e) { if (e.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) { currentRouteNode = null; nodes.clear(); repaint(); return true; } return super.keyPressed(e); } }