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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.diagram.participant.pointertool;
\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
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
74 * This participant handles the diagram in translate elements mode.
\r
76 * @author Toni Kalajainen
\r
77 * @author Tuukka Lehtonen
\r
79 public class TranslateMode extends AbstractMode {
\r
82 protected RenderingQualityInteractor quality;
\r
85 protected TransformUtil util;
\r
87 protected Point2D startingPoint;
\r
88 protected Point2D currentPoint;
\r
89 protected IMouseCursorHandle cursor;
\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
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
101 * This exists to cover cases where indirectly related (not topologically)
\r
102 * elements are not properly updated after translation operations.
\r
104 protected Collection<IElement> elementsToDirty = new HashSet<IElement>();
\r
107 * The node under which the mutated diagram is ghosted in the scene graph.
\r
109 protected G2DParentNode parent;
\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
116 protected SingleElementNode node = null;
\r
118 protected double dx;
\r
119 protected double dy;
\r
121 public TranslateMode(Point2D startingPoint, Point2D currentPoint, int mouseId, Collection<IElement> elements) {
\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
130 this.startingPoint = startingPoint;
\r
131 this.currentPoint = currentPoint;
\r
132 this.elementsToTranslate = elements;
\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
141 processTranslatedSelection(newDiagram, elementsToTranslate);
\r
142 // for (IElement e : elementsToReallyTranslate) {
\r
143 // System.out.println("REAL element: " + e);
\r
147 ILookupService lookup = NodeUtil.getLookupService(node);
\r
148 for (IElement e : elementsToReallyTranslate) {
\r
149 Node n = e.getHint(ElementHints.KEY_SG_NODE);
\r
151 LinkNode link = node.addNode("" + (i), LinkNode.class);
\r
154 String id = lookup.lookupId(n);
\r
156 id = UUID.randomUUID().toString();
\r
158 link.setLookupIdOwner(true);
\r
160 link.setDelegateId(id);
\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
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
180 if (connectionCount == elementsToTranslate.size())
\r
183 elementsToReallyTranslate = new HashSet<IElement>(elementsToTranslate);
\r
184 translatedConnections = new HashSet<IElement>();
\r
186 // 1st: find out if some elements should not be translated
\r
187 // because their parent elements are being translated also.
\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
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
202 // Check PARENT_OF relationships
\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
215 if (e instanceof IElement) {
\r
216 IElement el = (IElement) e;
\r
218 // Do not try to translate non-moveable elements.
\r
219 if (!el.getElementClass().containsClass(Move.class)) {
\r
220 elementsToReallyTranslate.remove(el);
\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
233 if (parentIsTranslated) {
\r
234 elementsToReallyTranslate.remove(el);
\r
235 elementsToDirty.add(el);
\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
244 Collection<Terminal> terminals = new ArrayList<Terminal>();
\r
245 Collection<Connection> connections = new ArrayList<Connection>();
\r
246 Collection<Connection> connections2 = new ArrayList<Connection>();
\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
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
265 IElement connection = ce.getConnection();
\r
266 if (connection == null)
\r
268 ConnectionHandler ch = connection.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
\r
272 connections2.clear();
\r
273 ch.getTerminalConnections(connection, connections2);
\r
275 boolean allConnectedNodesSelected = true;
\r
276 for (Connection conn2 : connections2) {
\r
277 if (!elementsToReallyTranslate.contains(conn2.node)) {
\r
278 allConnectedNodesSelected = false;
\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
296 // Include all non-selected branch points in the translation that
\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
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
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
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
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
339 public void removedFromContext(ICanvasContext ctx) {
\r
340 if (cursor != null) {
\r
344 if (quality != null)
\r
345 quality.setStaticQuality(null);
\r
346 super.removedFromContext(ctx);
\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
356 * return translate in control coordinate
\r
359 public Point2D getTranslateVector()
\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
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
375 if (e instanceof CommandEvent) {
\r
376 CommandEvent event = (CommandEvent) e;
\r
377 if (event.command.equals( Commands.CANCEL)) {
\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
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
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
405 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
\r
406 if (snapAdvisor != null) {
\r
407 IElement someElement = null;
\r
409 for (IElement elem : elementsToReallyTranslate) {
\r
410 someElement = elem;
\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
420 snapAdvisor.snap(canvasPos,
\r
422 new Point2D.Double(
\r
423 -oldPos.getX() + currentPoint.getX(),
\r
424 -oldPos.getY() + currentPoint.getY()
\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
441 AffineTransform nat = AffineTransform.getTranslateInstance(dx, dy);
\r
442 node.setTransform(nat);
\r
444 currentPoint = canvasPos;
\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
460 // Persist all translation modifications.
\r
461 DiagramUtils.mutateDiagram(diagram, m -> {
\r
462 for (IElement e : elementsToReallyTranslate)
\r
463 m.modifyTransform(e);
\r
466 for (IElement dirty : elementsToDirty)
\r
467 dirty.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
\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
484 public void cleanupSG() {
\r
485 if (node != null) {
\r