]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java
Sync git svn branch with SVN repository r33324.
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / pointertool / TranslateMode.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.g2d.diagram.participant.pointertool;\r
13 \r
14 import java.awt.AlphaComposite;\r
15 import java.awt.Cursor;\r
16 import java.awt.geom.AffineTransform;\r
17 import java.awt.geom.Point2D;\r
18 import java.util.ArrayDeque;\r
19 import java.util.ArrayList;\r
20 import java.util.Collection;\r
21 import java.util.Collections;\r
22 import java.util.HashSet;\r
23 import java.util.Queue;\r
24 import java.util.Set;\r
25 import java.util.UUID;\r
26 \r
27 import org.simantics.g2d.canvas.Hints;\r
28 import org.simantics.g2d.canvas.ICanvasContext;\r
29 import org.simantics.g2d.canvas.IMouseCursorContext;\r
30 import org.simantics.g2d.canvas.IMouseCursorHandle;\r
31 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
32 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;\r
33 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
34 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
35 import org.simantics.g2d.connection.ConnectionEntity;\r
36 import org.simantics.g2d.connection.handler.ConnectionHandler;\r
37 import org.simantics.g2d.diagram.DiagramHints;\r
38 import org.simantics.g2d.diagram.DiagramUtils;\r
39 import org.simantics.g2d.diagram.IDiagram;\r
40 import org.simantics.g2d.diagram.handler.Relationship;\r
41 import org.simantics.g2d.diagram.handler.RelationshipHandler;\r
42 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;\r
43 import org.simantics.g2d.diagram.handler.Topology;\r
44 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
45 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
46 import org.simantics.g2d.element.ElementClass;\r
47 import org.simantics.g2d.element.ElementHints;\r
48 import org.simantics.g2d.element.ElementUtils;\r
49 import org.simantics.g2d.element.IElement;\r
50 import org.simantics.g2d.element.handler.Move;\r
51 import org.simantics.g2d.element.handler.TerminalTopology;\r
52 import org.simantics.g2d.participant.RenderingQualityInteractor;\r
53 import org.simantics.g2d.participant.TransformUtil;\r
54 import org.simantics.scenegraph.ILookupService;\r
55 import org.simantics.scenegraph.Node;\r
56 import org.simantics.scenegraph.g2d.G2DParentNode;\r
57 import org.simantics.scenegraph.g2d.events.Event;\r
58 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
59 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
60 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;\r
61 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
62 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
63 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
64 import org.simantics.scenegraph.g2d.events.command.Commands;\r
65 import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
66 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
67 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
68 import org.simantics.scenegraph.utils.NodeUtil;\r
69 import org.simantics.scenegraph.utils.Quality;\r
70 import org.simantics.utils.ObjectUtils;\r
71 import org.simantics.utils.logging.TimeLogger;\r
72 \r
73 /**\r
74  * This participant handles the diagram in translate elements mode.\r
75  *\r
76  * @author Toni Kalajainen\r
77  * @author Tuukka Lehtonen\r
78  */\r
79 public class TranslateMode extends AbstractMode {\r
80 \r
81     @Reference\r
82     protected RenderingQualityInteractor quality;\r
83 \r
84     @Dependency\r
85     protected TransformUtil        util;\r
86 \r
87     protected Point2D              startingPoint;\r
88     protected Point2D              currentPoint;\r
89     protected IMouseCursorHandle   cursor;\r
90 \r
91     protected Collection<IElement> elementsToTranslate       = Collections.emptyList();\r
92     protected Collection<IElement> elementsToReallyTranslate = Collections.emptyList();\r
93     protected Collection<IElement> translatedConnections     = Collections.emptyList();\r
94 \r
95     /**\r
96      * A set of elements gathered during\r
97      * {@link #getElementsToReallyTranslate(IDiagram, Collection)} that is to be\r
98      * marked dirty after the translation operation has been completed.\r
99      * \r
100      * <p>\r
101      * This exists to cover cases where indirectly related (not topologically)\r
102      * elements are not properly updated after translation operations.\r
103      */\r
104     protected Collection<IElement> elementsToDirty           = new HashSet<IElement>();\r
105 \r
106     /**\r
107      * The node under which the mutated diagram is ghosted in the scene graph.\r
108      */\r
109     protected G2DParentNode        parent;\r
110 \r
111     /**\r
112      * This stays null until the translated diagram parts have been initialized\r
113      * into the scene graph. After that, only the translations of the nodes in\r
114      * the scene graph are modified.\r
115      */\r
116     protected SingleElementNode    node                      = null;\r
117 \r
118     protected double               dx;\r
119     protected double               dy;\r
120 \r
121     public TranslateMode(Point2D startingPoint, Point2D currentPoint, int mouseId, Collection<IElement> elements) {\r
122         super(mouseId);\r
123         if (startingPoint == null)\r
124             throw new NullPointerException("null startingPoint");\r
125         if (currentPoint == null)\r
126             throw new NullPointerException("null currentPoint");\r
127         if (elements == null)\r
128             throw new NullPointerException("null elements");\r
129 \r
130         this.startingPoint = startingPoint;\r
131         this.currentPoint = currentPoint;\r
132         this.elementsToTranslate = elements;\r
133     }\r
134 \r
135     @Override\r
136     protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {\r
137         if (newDiagram != null) {\r
138 //            for (IElement e : elementsToTranslate) {\r
139 //                System.out.println("element: " + e);\r
140 //            }\r
141             processTranslatedSelection(newDiagram, elementsToTranslate);\r
142 //            for (IElement e : elementsToReallyTranslate) {\r
143 //                System.out.println("REAL element: " + e);\r
144 //            }\r
145 \r
146             int i = 0;\r
147             ILookupService lookup = NodeUtil.getLookupService(node);\r
148             for (IElement e : elementsToReallyTranslate) {\r
149                 Node n = e.getHint(ElementHints.KEY_SG_NODE);\r
150                 if (n != null) {\r
151                     LinkNode link = node.addNode("" + (i), LinkNode.class);\r
152                     link.setZIndex(i);\r
153 \r
154                     String id = lookup.lookupId(n);\r
155                     if (id == null) {\r
156                         id = UUID.randomUUID().toString();\r
157                         lookup.map(id, n);\r
158                         link.setLookupIdOwner(true);\r
159                     }\r
160                     link.setDelegateId(id);\r
161 \r
162                     ++i;\r
163                 }\r
164             }\r
165         }\r
166     }\r
167 \r
168     protected void processTranslatedSelection(IDiagram diagram, Collection<IElement> elementsToTranslate) {\r
169         // Only translate elements that are not parents of another elements that\r
170         // is in the translated set also. Otherwise we would end up doing double\r
171         // translation for the parented elements.\r
172 \r
173         // Don't move "connections only" selections.\r
174         int connectionCount = 0; \r
175         for (IElement e : elementsToTranslate) {\r
176             if (e.getElementClass().containsClass(ConnectionHandler.class)) {\r
177                 ++connectionCount;\r
178             }\r
179         }\r
180         if (connectionCount == elementsToTranslate.size())\r
181             return;\r
182 \r
183         elementsToReallyTranslate = new HashSet<IElement>(elementsToTranslate);\r
184         translatedConnections = new HashSet<IElement>();\r
185 \r
186         // 1st: find out if some elements should not be translated\r
187         // because their parent elements are being translated also.\r
188         // \r
189         // Post-invariants:\r
190         // * elementsToReallyTranslate does not contain any elements whose parents are also translated\r
191         // * elementsToDirty contains all elements whose parents are also translated\r
192         RelationshipHandler erh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);\r
193         if (erh != null) {\r
194             Queue<Object> todo = new ArrayDeque<Object>(elementsToTranslate);\r
195             Set<Object> visited = new HashSet<Object>();\r
196             Collection<Relation> relations = new ArrayList<Relation>();\r
197             while (!todo.isEmpty()) {\r
198                 Object e = todo.poll();\r
199                 if (!visited.add(e))\r
200                     continue;\r
201 \r
202                 // Check PARENT_OF relationships\r
203                 relations.clear();\r
204                 erh.getRelations(diagram, e, relations);\r
205                 for (Relation r : relations) {\r
206                     if (Relationship.PARENT_OF.equals(r.getRelationship())) {\r
207                         elementsToReallyTranslate.remove(r.getObject());\r
208                         if (r.getObject() instanceof IElement)\r
209                             elementsToDirty.add((IElement) r.getObject());\r
210                         if (!visited.contains(r.getObject()))\r
211                             todo.add(r.getObject());\r
212                     }\r
213                 }\r
214 \r
215                 if (e instanceof IElement) {\r
216                     IElement el = (IElement) e;\r
217 \r
218                     // Do not try to translate non-moveable elements.\r
219                     if (!el.getElementClass().containsClass(Move.class)) {\r
220                         elementsToReallyTranslate.remove(el);\r
221                         continue;\r
222                     }\r
223 \r
224                     // Check Parent handler\r
225                     Collection<IElement> parents = ElementUtils.getParents(el);\r
226                     boolean parentIsTranslated = false;\r
227                     for (IElement parent : parents) {\r
228                         if (elementsToTranslate.contains(parent)) {\r
229                             parentIsTranslated = true;\r
230                             break;\r
231                         }\r
232                     }\r
233                     if (parentIsTranslated) {\r
234                         elementsToReallyTranslate.remove(el);\r
235                         elementsToDirty.add(el);\r
236                     }\r
237                 }\r
238             }\r
239         }\r
240 \r
241         // 2nd: Include those connections in the translation for which all\r
242         // all terminal connected elements are also included in the selection.\r
243 \r
244         Collection<Terminal> terminals = new ArrayList<Terminal>();\r
245         Collection<Connection> connections = new ArrayList<Connection>();\r
246         Collection<Connection> connections2 = new ArrayList<Connection>();\r
247 \r
248         Topology topology = diagram.getDiagramClass().getSingleItem(Topology.class);\r
249         for (IElement el : new ArrayList<IElement>(elementsToReallyTranslate)) {\r
250             // Check if the selection is a connection.\r
251             // If it is, translate all of its branchpoints too,\r
252             // but only if all of its terminal connected elements\r
253             // are about to be translated too.\r
254             ElementClass ec = el.getElementClass();\r
255             TerminalTopology tt = ec.getAtMostOneItemOfClass(TerminalTopology.class);\r
256             if (tt != null) {\r
257                 terminals.clear();\r
258                 tt.getTerminals(el, terminals);\r
259                 for (Terminal terminal : terminals) {\r
260                     topology.getConnections(el, terminal, connections);\r
261                     for (Connection conn : connections) {\r
262                         ConnectionEntity ce = conn.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
263                         if (ce == null)\r
264                             continue;\r
265                         IElement connection = ce.getConnection();\r
266                         if (connection == null)\r
267                             continue;\r
268                         ConnectionHandler ch = connection.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);\r
269                         if (ch == null)\r
270                             continue;\r
271 \r
272                         connections2.clear();\r
273                         ch.getTerminalConnections(connection, connections2);\r
274 \r
275                         boolean allConnectedNodesSelected = true;\r
276                         for (Connection conn2 : connections2) {\r
277                             if (!elementsToReallyTranslate.contains(conn2.node)) {\r
278                                 allConnectedNodesSelected = false;\r
279                                 break;\r
280                             }\r
281                         }\r
282 \r
283                         if (allConnectedNodesSelected) {\r
284                             // Finally! It seems like all nodes connected\r
285                             // with 'connection' are about to be translated.\r
286                             // We should also include the whole connection\r
287                             // in the translation.\r
288                             elementsToReallyTranslate.add(connection);\r
289                             translatedConnections.add(connection);\r
290                         }\r
291                     }\r
292                 }\r
293             }\r
294         }\r
295 \r
296         // Include all non-selected branch points in the translation that\r
297         // are either:\r
298         //   a) among connections that have been either selected\r
299         //   b) exist in a connection whose all terminal connected\r
300         //      elements are selected.\r
301 \r
302         for (IElement el : new ArrayList<IElement>(elementsToReallyTranslate)) {\r
303             // Check if the selection is a connection.\r
304             // If it is, translate all of its branchpoints too,\r
305             // but only if all of its terminal connected elements\r
306             // are about to be translated too.\r
307             ConnectionHandler ch = el.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);\r
308             if (ch != null) {\r
309                 boolean anyTerminalConnectionsSelected = false;\r
310                 Collection<Connection> terminalConnections = new ArrayList<Connection>();\r
311                 ch.getTerminalConnections(el, terminalConnections);\r
312                 for (Connection conn : terminalConnections) {\r
313                     if (elementsToTranslate.contains(conn.node)) {\r
314                         anyTerminalConnectionsSelected = true;\r
315                         break;\r
316                     }\r
317                 }\r
318 \r
319                 if (anyTerminalConnectionsSelected) {\r
320                     translatedConnections.add(el);\r
321                     Collection<IElement> branchPoints = new ArrayList<IElement>();\r
322                     ch.getBranchPoints(el, branchPoints);\r
323                     elementsToReallyTranslate.addAll(branchPoints);\r
324                 }\r
325             }\r
326         }\r
327     }\r
328 \r
329     @Override\r
330     public void addedToContext(ICanvasContext ctx) {\r
331         super.addedToContext(ctx);\r
332         if (quality != null)\r
333             quality.setStaticQuality(Quality.LOW);\r
334         IMouseCursorContext mcc = getContext().getMouseCursorContext();\r
335         cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.MOVE_CURSOR));\r
336     }\r
337 \r
338     @Override\r
339     public void removedFromContext(ICanvasContext ctx) {\r
340         if (cursor != null) {\r
341             cursor.remove();\r
342             cursor = null;\r
343         }\r
344         if (quality != null)\r
345             quality.setStaticQuality(null);\r
346         super.removedFromContext(ctx);\r
347     }\r
348 \r
349     public AffineTransform getTransform() {\r
350         double dx = currentPoint.getX() - startingPoint.getX();\r
351         double dy = currentPoint.getY() - startingPoint.getY();\r
352         return AffineTransform.getTranslateInstance(dx, dy);\r
353     }\r
354 \r
355     /**\r
356      * return translate in control coordinate\r
357      * @return\r
358      */\r
359     public Point2D getTranslateVector()\r
360     {\r
361         double dx = currentPoint.getX() - startingPoint.getX();\r
362         double dy = currentPoint.getY() - startingPoint.getY();\r
363         return new Point2D.Double(dx, dy);\r
364     }\r
365 \r
366     @EventHandler(priority = 30)\r
367     public boolean handleEvent(Event e) {\r
368         // Reject all mouse events not for this mouse.\r
369         if (e instanceof MouseEvent) {\r
370             MouseEvent me = (MouseEvent) e;\r
371             if (me.mouseId != mouseId)\r
372                 return false;\r
373         }\r
374 \r
375         if (e instanceof CommandEvent) {\r
376             CommandEvent event = (CommandEvent) e;\r
377             if (event.command.equals( Commands.CANCEL)) {\r
378                 setDirty();\r
379                 remove();\r
380                 return true;\r
381             } else if (event.command.equals( Commands.ROTATE_ELEMENT_CCW )\r
382                     || event.command.equals( Commands.DELETE )\r
383                     || event.command.equals( Commands.ROTATE_ELEMENT_CW )\r
384                     || event.command.equals( Commands.FLIP_ELEMENT_HORIZONTAL )\r
385                     || event.command.equals( Commands.FLIP_ELEMENT_VERTICAL) ) {\r
386                 // Just eat these commands to disable\r
387                 // rotation and scaling during translation.\r
388                 return true;\r
389             }\r
390         } else if (e instanceof MouseMovedEvent) {\r
391             return move((MouseMovedEvent) e);\r
392         } else if (e instanceof MouseButtonReleasedEvent) {\r
393             if (((MouseButtonEvent)e).button == MouseEvent.LEFT_BUTTON) {\r
394                 TimeLogger.resetTimeAndLog(getClass(), "handleEvent");\r
395                 return commit();\r
396             }\r
397         }\r
398         return false;\r
399     }\r
400 \r
401     protected boolean move(MouseMovedEvent event) {\r
402         Point2D canvasPos = util.controlToCanvas(event.controlPosition, null);\r
403         if (ObjectUtils.objectEquals(currentPoint, canvasPos)) return true;\r
404 \r
405         ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
406         if (snapAdvisor != null) {\r
407             IElement someElement = null;\r
408 \r
409             for (IElement elem : elementsToReallyTranslate) {\r
410                 someElement = elem;\r
411                 break;\r
412             }\r
413 \r
414             if (someElement != null) {\r
415                 AffineTransform at = node.getTransform();\r
416                 Move m = someElement.getElementClass().getSingleItem(Move.class);\r
417                 Point2D oldPos = m.getPosition(someElement);\r
418                 oldPos.setLocation(oldPos.getX() + at.getTranslateX(), oldPos.getY() + at.getTranslateY());\r
419 \r
420                 snapAdvisor.snap(canvasPos,\r
421                         new Point2D[] {\r
422                         new Point2D.Double(\r
423                                 -oldPos.getX() + currentPoint.getX(),\r
424                                 -oldPos.getY() + currentPoint.getY()\r
425                         )\r
426                 });\r
427             }\r
428         }\r
429 \r
430         dx = canvasPos.getX()-startingPoint.getX();\r
431         dy = canvasPos.getY()-startingPoint.getY();\r
432         if ((event.stateMask & MouseEvent.CTRL_MASK) != 0) {\r
433             // Forced horizontal/vertical -only movement\r
434             if (Math.abs(dx) >= Math.abs(dy)) {\r
435                 dy = 0;\r
436             } else {\r
437                 dx = 0;\r
438             }\r
439         }\r
440 \r
441         AffineTransform nat = AffineTransform.getTranslateInstance(dx, dy);\r
442         node.setTransform(nat);\r
443 \r
444         currentPoint = canvasPos;\r
445 \r
446         setDirty();\r
447         return true;\r
448     }\r
449 \r
450     protected boolean commit() {\r
451         TimeLogger.resetTimeAndLog(getClass(), "commit");\r
452         for (IElement el : elementsToReallyTranslate) {\r
453             Move move = el.getElementClass().getAtMostOneItemOfClass(Move.class);\r
454             if (move != null) {\r
455                 Point2D oldPos = move.getPosition(el);\r
456                 move.moveTo(el, oldPos.getX() + dx, oldPos.getY() + dy);\r
457             }\r
458         }\r
459 \r
460         // Persist all translation modifications.\r
461         DiagramUtils.mutateDiagram(diagram, m -> {\r
462             for (IElement e : elementsToReallyTranslate)\r
463                 m.modifyTransform(e);\r
464         });\r
465 \r
466         for (IElement dirty : elementsToDirty)\r
467             dirty.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
468 \r
469         setDirty();\r
470         remove();\r
471         return false;\r
472     }\r
473 \r
474     @SGInit\r
475     public void initSG(G2DParentNode parent) {\r
476         this.parent = parent;\r
477         node = parent.addNode("translate ghost", SingleElementNode.class);\r
478         node.setZIndex(1000);\r
479         node.setVisible(Boolean.TRUE);\r
480         node.setComposite(AlphaComposite.SrcOver.derive(0.4f));\r
481     }\r
482 \r
483     @SGCleanup\r
484     public void cleanupSG() {\r
485         if (node != null) {\r
486             node.remove();\r
487             node = null;\r
488         }\r
489         parent = null;\r
490     }\r
491 \r
492 }\r