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