--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.handler;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Line2D;\r
+import java.awt.geom.Point2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashSet;\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.content.ConnectionUtil;\r
+import org.simantics.diagram.content.EdgeResource;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.diagram.synchronization.graph.RemoveBranchpoint;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
+import org.simantics.g2d.connection.ConnectionEntity;\r
+import org.simantics.g2d.diagram.DiagramHints;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.PickRequest;\r
+import org.simantics.g2d.diagram.handler.Topology;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
+import org.simantics.g2d.diagram.participant.Selection;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.elementclass.BranchPoint;\r
+import org.simantics.g2d.elementclass.BranchPoint.Direction;\r
+import org.simantics.g2d.participant.MouseUtil;\r
+import org.simantics.g2d.participant.MouseUtil.MouseInfo;\r
+import org.simantics.g2d.participant.WorkbenchStatusLine;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
+import org.simantics.scenegraph.g2d.events.command.Commands;\r
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
+import org.simantics.ui.SimanticsUI;\r
+import org.simantics.utils.ui.ErrorLogger;\r
+\r
+/**\r
+ * A participant for handling:\r
+ * <ul>\r
+ * <li>DELETE for deleting route points from DIA.Connection type connections\r
+ * <li>SPLIT_CONNECTION for creating new route points into DIA.Connection type connections\r
+ * <li>ROTATE_ELEMENT_CW & ROTATE_ELEMENT_CCW for rotating route points orientations.\r
+ * </ul>\r
+ * \r
+ * NOTE: this participant does nothing useful with DIA.RouteGraphConnection type connections.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ * \r
+ * TODO: start using {@link WorkbenchStatusLine}\r
+ */\r
+public class ConnectionCommandHandler extends AbstractDiagramParticipant {\r
+\r
+ @Dependency MouseUtil mouseUtil;\r
+ @Dependency Selection selection;\r
+ //@Dependency WorkbenchStatusLine statusLine;\r
+\r
+ @EventHandler(priority = 100)\r
+ public boolean handleCommand(CommandEvent event) {\r
+ if (Commands.DELETE.equals(event.command)) {\r
+ try {\r
+ return joinConnection();\r
+ } catch (DatabaseException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ return false;\r
+ }\r
+ } else if (Commands.SPLIT_CONNECTION.equals(event.command) && routePointsEnabled()) {\r
+ return splitConnection();\r
+ } else if (Commands.ROTATE_ELEMENT_CW.equals(event.command) || Commands.ROTATE_ELEMENT_CCW.equals(event.command)) {\r
+ boolean clockWise = Commands.ROTATE_ELEMENT_CW.equals(event.command);\r
+ try {\r
+ return rotateBranchPoint(clockWise);\r
+ } catch (DatabaseException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ return false;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private boolean routePointsEnabled() {\r
+ return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));\r
+ }\r
+\r
+ boolean joinConnection() throws DatabaseException {\r
+\r
+ final Set<IElement> routePoints = getBranchPoints(2);\r
+\r
+ if (routePoints.isEmpty())\r
+ return false;\r
+\r
+ selection.clear(0);\r
+\r
+ Simantics.getSession().sync(new WriteRequest() {\r
+ @Override\r
+ public void perform(WriteGraph graph) throws DatabaseException {\r
+ for (IElement routePoint : routePoints) {\r
+ Object node = ElementUtils.getObject(routePoint);\r
+ if (node instanceof Resource) {\r
+ new RemoveBranchpoint(routePoint).perform(graph);\r
+ }\r
+ }\r
+ }\r
+ });\r
+ setDirty();\r
+\r
+ //statusLine.message("Deleted " + routePoints.size() + " route points");\r
+ return true;\r
+ }\r
+\r
+ private boolean rotateBranchPoint(final boolean clockWise) throws DatabaseException {\r
+ final Set<IElement> routePoints = getBranchPoints(Integer.MAX_VALUE);\r
+ if (routePoints.isEmpty())\r
+ return false;\r
+\r
+ // Rotate branch points.\r
+ try {\r
+ SimanticsUI.getSession().syncRequest(new WriteRequest() {\r
+ @Override\r
+ public void perform(WriteGraph graph) throws DatabaseException {\r
+ DiagramResource DIA = DiagramResource.getInstance(graph);\r
+ for (IElement bp : routePoints) {\r
+ Resource bpr = (Resource) ElementUtils.getObject(bp);\r
+ boolean vertical = graph.hasStatement(bpr, DIA.Vertical, bpr);\r
+ boolean horizontal = graph.hasStatement(bpr, DIA.Horizontal, bpr);\r
+ Direction dir = Direction.toDirection(horizontal, vertical);\r
+ Direction newDir = clockWise ? dir.cycleNext() : dir.cyclePrevious();\r
+ switch (newDir) {\r
+ case Any:\r
+ graph.deny(bpr, DIA.Vertical);\r
+ graph.deny(bpr, DIA.Horizontal);\r
+ break;\r
+ case Horizontal:\r
+ graph.deny(bpr, DIA.Vertical);\r
+ graph.claim(bpr, DIA.Horizontal, bpr);\r
+ break;\r
+ case Vertical:\r
+ graph.deny(bpr, DIA.Horizontal);\r
+ graph.claim(bpr, DIA.Vertical, bpr);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ });\r
+ } catch (DatabaseException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ }\r
+\r
+ return true;\r
+ }\r
+\r
+ boolean splitConnection() {\r
+\r
+ final IElement edge = getSingleConnectionSegment();\r
+ if (edge == null)\r
+ return false;\r
+ final IDiagram diagram = ElementUtils.peekDiagram(edge);\r
+ if (diagram == null)\r
+ return false;\r
+\r
+ MouseInfo mi = mouseUtil.getMouseInfo(0);\r
+ final Point2D mousePos = mi.canvasPosition;\r
+\r
+ ISnapAdvisor snap = getHint(DiagramHints.SNAP_ADVISOR);\r
+ if (snap != null)\r
+ snap.snap(mousePos);\r
+\r
+ final AffineTransform splitPos = AffineTransform.getTranslateInstance(mousePos.getX(), mousePos.getY());\r
+\r
+ try {\r
+ SimanticsUI.getSession().syncRequest(new WriteRequest() {\r
+ @Override\r
+ public void perform(WriteGraph graph) throws DatabaseException {\r
+ DiagramResource DIA = DiagramResource.getInstance(graph);\r
+\r
+ // Split the edge with a new branch point\r
+ ConnectionUtil cu = new ConnectionUtil(graph);\r
+ EdgeResource segment = (EdgeResource) ElementUtils.getObject(edge);\r
+ Resource bp = cu.split(segment, splitPos);\r
+\r
+ Line2D nearestLine = ConnectionUtil.resolveNearestEdgeLineSegment(mousePos, edge);\r
+ if (nearestLine != null) {\r
+ double angle = Math.atan2(\r
+ Math.abs(nearestLine.getY2() - nearestLine.getY1()),\r
+ Math.abs(nearestLine.getX2() - nearestLine.getX1())\r
+ );\r
+\r
+ if (angle >= 0 && angle < Math.PI / 4) {\r
+ graph.claim(bp, DIA.Horizontal, bp);\r
+ } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {\r
+ graph.claim(bp, DIA.Vertical, bp);\r
+ }\r
+ }\r
+ }\r
+ });\r
+\r
+ selection.clear(0);\r
+\r
+ } catch (DatabaseException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * @return all route points in the current diagram selection if and only if\r
+ * only route points are selected. If anything else is selected,\r
+ * even branch points, an empty set will be returned.\r
+ */\r
+ Set<IElement> getBranchPoints(int maxDegree) throws DatabaseException {\r
+\r
+ Set<IElement> ss = selection.getSelection(0);\r
+ if (ss.isEmpty())\r
+ return Collections.emptySet();\r
+\r
+ final Set<IElement> result = new HashSet<IElement>();\r
+ Collection<Connection> connections = new ArrayList<Connection>();\r
+ for (IElement e : ss) {\r
+ if (!e.getElementClass().containsClass(BranchPoint.class))\r
+ return Collections.emptySet();\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return Collections.emptySet();\r
+ IDiagram diagram = ElementUtils.peekDiagram(e);\r
+ if (diagram == null)\r
+ return Collections.emptySet();\r
+ for (Topology topology : diagram.getDiagramClass().getItemsByClass(Topology.class)) {\r
+ connections.clear();\r
+ topology.getConnections(e, ElementUtils.getSingleTerminal(e), connections);\r
+ int degree = connections.size();\r
+ if (degree < 2 || degree > maxDegree)\r
+ return Collections.emptySet();\r
+ result.add(e);\r
+ }\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * @return if a single edge is selected, return the edge. If a single\r
+ * connection is selected and it contains only one edge segment,\r
+ * return it. Otherwise return <code>null</code>.\r
+ */\r
+ IElement getSingleConnectionSegment() {\r
+ Set<IElement> s = selection.getSelection(0);\r
+ if (s.size() != 1)\r
+ return null;\r
+ IElement e = s.iterator().next();\r
+ if (PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e)) {\r
+ // Does this connection have only one edge and no branch points?\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ Collection<IElement> coll = ce.getBranchPoints(null);\r
+ if (!coll.isEmpty())\r
+ return null;\r
+ ce.getSegments(coll);\r
+ if (coll.size() != 1)\r
+ return null;\r
+ // Return the only edge element of the whole connection.\r
+ return coll.iterator().next();\r
+ }\r
+ if (PickRequest.PickFilter.FILTER_CONNECTION_EDGES.accept(e))\r
+ return e;\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * @return the selected top-level connection element if and only if the\r
+ * selection contains the connection or parts of the same connection\r
+ * (edge segments or branch/route points) and nothing else.\r
+ */\r
+ IElement getSingleConnection() {\r
+\r
+ Set<IElement> ss = selection.getSelection(0);\r
+ if (ss.isEmpty())\r
+ return null;\r
+\r
+ IElement result = null;\r
+\r
+ for (IElement e : ss) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ continue;\r
+ if (result != null && !ce.getConnection().equals(result))\r
+ return null;\r
+ result = ce.getConnection();\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+}\r