]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/ConnectionCommandHandler.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / ConnectionCommandHandler.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.diagram.handler;\r
13 \r
14 import java.awt.geom.AffineTransform;\r
15 import java.awt.geom.Line2D;\r
16 import java.awt.geom.Point2D;\r
17 import java.util.ArrayList;\r
18 import java.util.Collection;\r
19 import java.util.Collections;\r
20 import java.util.HashSet;\r
21 import java.util.Set;\r
22 \r
23 import org.simantics.Simantics;\r
24 import org.simantics.db.Resource;\r
25 import org.simantics.db.WriteGraph;\r
26 import org.simantics.db.common.request.WriteRequest;\r
27 import org.simantics.db.exception.DatabaseException;\r
28 import org.simantics.diagram.content.ConnectionUtil;\r
29 import org.simantics.diagram.content.EdgeResource;\r
30 import org.simantics.diagram.stubs.DiagramResource;\r
31 import org.simantics.diagram.synchronization.graph.RemoveBranchpoint;\r
32 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
33 import org.simantics.g2d.connection.ConnectionEntity;\r
34 import org.simantics.g2d.diagram.DiagramHints;\r
35 import org.simantics.g2d.diagram.IDiagram;\r
36 import org.simantics.g2d.diagram.handler.PickRequest;\r
37 import org.simantics.g2d.diagram.handler.Topology;\r
38 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
39 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
40 import org.simantics.g2d.diagram.participant.Selection;\r
41 import org.simantics.g2d.element.ElementHints;\r
42 import org.simantics.g2d.element.ElementUtils;\r
43 import org.simantics.g2d.element.IElement;\r
44 import org.simantics.g2d.elementclass.BranchPoint;\r
45 import org.simantics.g2d.elementclass.BranchPoint.Direction;\r
46 import org.simantics.g2d.participant.MouseUtil;\r
47 import org.simantics.g2d.participant.MouseUtil.MouseInfo;\r
48 import org.simantics.g2d.participant.WorkbenchStatusLine;\r
49 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
50 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
51 import org.simantics.scenegraph.g2d.events.command.Commands;\r
52 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
53 import org.simantics.ui.SimanticsUI;\r
54 import org.simantics.utils.ui.ErrorLogger;\r
55 \r
56 /**\r
57  * A participant for handling:\r
58  * <ul>\r
59  * <li>DELETE for deleting route points from DIA.Connection type connections\r
60  * <li>SPLIT_CONNECTION for creating new route points into DIA.Connection type connections\r
61  * <li>ROTATE_ELEMENT_CW & ROTATE_ELEMENT_CCW for rotating route points orientations.\r
62  * </ul>\r
63  * \r
64  *  NOTE: this participant does nothing useful with DIA.RouteGraphConnection type connections.\r
65  * \r
66  * @author Tuukka Lehtonen\r
67  * \r
68  * TODO: start using {@link WorkbenchStatusLine}\r
69  */\r
70 public class ConnectionCommandHandler extends AbstractDiagramParticipant {\r
71 \r
72     @Dependency MouseUtil mouseUtil;\r
73     @Dependency Selection selection;\r
74     //@Dependency WorkbenchStatusLine statusLine;\r
75 \r
76     @EventHandler(priority = 100)\r
77     public boolean handleCommand(CommandEvent event) {\r
78         if (Commands.DELETE.equals(event.command)) {\r
79             try {\r
80                 return joinConnection();\r
81             } catch (DatabaseException e) {\r
82                 ErrorLogger.defaultLogError(e);\r
83                 return false;\r
84             }\r
85         } else if (Commands.SPLIT_CONNECTION.equals(event.command) && routePointsEnabled()) {\r
86             return splitConnection();\r
87         } else if (Commands.ROTATE_ELEMENT_CW.equals(event.command) || Commands.ROTATE_ELEMENT_CCW.equals(event.command)) {\r
88             boolean clockWise = Commands.ROTATE_ELEMENT_CW.equals(event.command);\r
89             try {\r
90                 return rotateBranchPoint(clockWise);\r
91             } catch (DatabaseException e) {\r
92                 ErrorLogger.defaultLogError(e);\r
93                 return false;\r
94             }\r
95         }\r
96         return false;\r
97     }\r
98 \r
99     private boolean routePointsEnabled() {\r
100         return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));\r
101     }\r
102 \r
103     boolean joinConnection() throws DatabaseException {\r
104 \r
105         final Set<IElement> routePoints = getBranchPoints(2);\r
106 \r
107         if (routePoints.isEmpty())\r
108             return false;\r
109 \r
110         selection.clear(0);\r
111 \r
112         Simantics.getSession().sync(new WriteRequest() {\r
113             @Override\r
114             public void perform(WriteGraph graph) throws DatabaseException {\r
115                 for (IElement routePoint : routePoints) {\r
116                     Object node = ElementUtils.getObject(routePoint);\r
117                     if (node instanceof Resource) {\r
118                         new RemoveBranchpoint(routePoint).perform(graph);\r
119                     }\r
120                 }\r
121             }\r
122         });\r
123         setDirty();\r
124 \r
125         //statusLine.message("Deleted " + routePoints.size() + " route points");\r
126         return true;\r
127     }\r
128 \r
129     private boolean rotateBranchPoint(final boolean clockWise) throws DatabaseException {\r
130         final Set<IElement> routePoints = getBranchPoints(Integer.MAX_VALUE);\r
131         if (routePoints.isEmpty())\r
132             return false;\r
133 \r
134         // Rotate branch points.\r
135         try {\r
136             SimanticsUI.getSession().syncRequest(new WriteRequest() {\r
137                 @Override\r
138                 public void perform(WriteGraph graph) throws DatabaseException {\r
139                     DiagramResource DIA = DiagramResource.getInstance(graph);\r
140                     for (IElement bp : routePoints) {\r
141                         Resource bpr = (Resource) ElementUtils.getObject(bp);\r
142                         boolean vertical = graph.hasStatement(bpr, DIA.Vertical, bpr);\r
143                         boolean horizontal = graph.hasStatement(bpr, DIA.Horizontal, bpr);\r
144                         Direction dir = Direction.toDirection(horizontal, vertical);\r
145                         Direction newDir = clockWise ? dir.cycleNext() : dir.cyclePrevious();\r
146                         switch (newDir) {\r
147                             case Any:\r
148                                 graph.deny(bpr, DIA.Vertical);\r
149                                 graph.deny(bpr, DIA.Horizontal);\r
150                                 break;\r
151                             case Horizontal:\r
152                                 graph.deny(bpr, DIA.Vertical);\r
153                                 graph.claim(bpr, DIA.Horizontal, bpr);\r
154                                 break;\r
155                             case Vertical:\r
156                                 graph.deny(bpr, DIA.Horizontal);\r
157                                 graph.claim(bpr, DIA.Vertical, bpr);\r
158                                 break;\r
159                         }\r
160                     }\r
161                 }\r
162             });\r
163         } catch (DatabaseException e) {\r
164             ErrorLogger.defaultLogError(e);\r
165         }\r
166 \r
167         return true;\r
168     }\r
169 \r
170     boolean splitConnection() {\r
171 \r
172         final IElement edge = getSingleConnectionSegment();\r
173         if (edge == null)\r
174             return false;\r
175         final IDiagram diagram = ElementUtils.peekDiagram(edge);\r
176         if (diagram == null)\r
177             return false;\r
178 \r
179         MouseInfo mi = mouseUtil.getMouseInfo(0);\r
180         final Point2D mousePos = mi.canvasPosition;\r
181 \r
182         ISnapAdvisor snap = getHint(DiagramHints.SNAP_ADVISOR);\r
183         if (snap != null)\r
184             snap.snap(mousePos);\r
185 \r
186         final AffineTransform splitPos = AffineTransform.getTranslateInstance(mousePos.getX(), mousePos.getY());\r
187 \r
188         try {\r
189             SimanticsUI.getSession().syncRequest(new WriteRequest() {\r
190                 @Override\r
191                 public void perform(WriteGraph graph) throws DatabaseException {\r
192                     DiagramResource DIA = DiagramResource.getInstance(graph);\r
193 \r
194                     // Split the edge with a new branch point\r
195                     ConnectionUtil cu = new ConnectionUtil(graph);\r
196                     EdgeResource segment = (EdgeResource) ElementUtils.getObject(edge);\r
197                     Resource bp = cu.split(segment, splitPos);\r
198 \r
199                     Line2D nearestLine = ConnectionUtil.resolveNearestEdgeLineSegment(mousePos, edge);\r
200                     if (nearestLine != null) {\r
201                         double angle = Math.atan2(\r
202                                 Math.abs(nearestLine.getY2() - nearestLine.getY1()),\r
203                                 Math.abs(nearestLine.getX2() - nearestLine.getX1())\r
204                         );\r
205 \r
206                         if (angle >= 0 && angle < Math.PI / 4) {\r
207                             graph.claim(bp, DIA.Horizontal, bp);\r
208                         } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {\r
209                             graph.claim(bp, DIA.Vertical, bp);\r
210                         }\r
211                     }\r
212                 }\r
213             });\r
214 \r
215             selection.clear(0);\r
216 \r
217         } catch (DatabaseException e) {\r
218             ErrorLogger.defaultLogError(e);\r
219         }\r
220 \r
221         return false;\r
222     }\r
223 \r
224     /**\r
225      * @return all route points in the current diagram selection if and only if\r
226      *         only route points are selected. If anything else is selected,\r
227      *         even branch points, an empty set will be returned.\r
228      */\r
229     Set<IElement> getBranchPoints(int maxDegree) throws DatabaseException {\r
230 \r
231         Set<IElement> ss = selection.getSelection(0);\r
232         if (ss.isEmpty())\r
233             return Collections.emptySet();\r
234 \r
235         final Set<IElement> result = new HashSet<IElement>();\r
236         Collection<Connection> connections = new ArrayList<Connection>();\r
237         for (IElement e : ss) {\r
238             if (!e.getElementClass().containsClass(BranchPoint.class))\r
239                 return Collections.emptySet();\r
240             ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
241             if (ce == null)\r
242                 return Collections.emptySet();\r
243             IDiagram diagram = ElementUtils.peekDiagram(e);\r
244             if (diagram == null)\r
245                 return Collections.emptySet();\r
246             for (Topology topology : diagram.getDiagramClass().getItemsByClass(Topology.class)) {\r
247                 connections.clear();\r
248                 topology.getConnections(e, ElementUtils.getSingleTerminal(e), connections);\r
249                 int degree = connections.size();\r
250                 if (degree < 2 || degree > maxDegree)\r
251                     return Collections.emptySet();\r
252                 result.add(e);\r
253             }\r
254         }\r
255 \r
256         return result;\r
257     }\r
258 \r
259     /**\r
260      * @return if a single edge is selected, return the edge. If a single\r
261      *         connection is selected and it contains only one edge segment,\r
262      *         return it. Otherwise return <code>null</code>.\r
263      */\r
264     IElement getSingleConnectionSegment() {\r
265         Set<IElement> s = selection.getSelection(0);\r
266         if (s.size() != 1)\r
267             return null;\r
268         IElement e = s.iterator().next();\r
269         if (PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e)) {\r
270             // Does this connection have only one edge and no branch points?\r
271             ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
272             Collection<IElement> coll = ce.getBranchPoints(null);\r
273             if (!coll.isEmpty())\r
274                 return null;\r
275             ce.getSegments(coll);\r
276             if (coll.size() != 1)\r
277                 return null;\r
278             // Return the only edge element of the whole connection.\r
279             return coll.iterator().next();\r
280         }\r
281         if (PickRequest.PickFilter.FILTER_CONNECTION_EDGES.accept(e))\r
282             return e;\r
283         return null;\r
284     }\r
285 \r
286     /**\r
287      * @return the selected top-level connection element if and only if the\r
288      *         selection contains the connection or parts of the same connection\r
289      *         (edge segments or branch/route points) and nothing else.\r
290      */\r
291     IElement getSingleConnection() {\r
292 \r
293         Set<IElement> ss = selection.getSelection(0);\r
294         if (ss.isEmpty())\r
295             return null;\r
296 \r
297         IElement result = null;\r
298 \r
299         for (IElement e : ss) {\r
300             ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
301             if (ce == null)\r
302                 continue;\r
303             if (result != null && !ce.getConnection().equals(result))\r
304                 return null;\r
305             result = ce.getConnection();\r
306         }\r
307 \r
308         return result;\r
309     }\r
310 \r
311 }\r