1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.diagram.participant.pointertool;
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;
25 import java.util.UUID;
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;
74 * This participant handles the diagram in translate elements mode.
76 * @author Toni Kalajainen
77 * @author Tuukka Lehtonen
79 public class TranslateMode extends AbstractMode {
82 protected RenderingQualityInteractor quality;
85 protected TransformUtil util;
87 protected Point2D startingPoint;
88 protected Point2D currentPoint;
89 protected IMouseCursorHandle cursor;
91 protected Collection<IElement> elementsToTranslate = Collections.emptyList();
92 protected Collection<IElement> elementsToReallyTranslate = Collections.emptyList();
93 protected Collection<IElement> translatedConnections = Collections.emptyList();
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.
101 * This exists to cover cases where indirectly related (not topologically)
102 * elements are not properly updated after translation operations.
104 protected Collection<IElement> elementsToDirty = new HashSet<IElement>();
107 * The node under which the mutated diagram is ghosted in the scene graph.
109 protected G2DParentNode parent;
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.
116 protected SingleElementNode node = null;
121 public TranslateMode(Point2D startingPoint, Point2D currentPoint, int mouseId, Collection<IElement> elements) {
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");
130 this.startingPoint = startingPoint;
131 this.currentPoint = currentPoint;
132 this.elementsToTranslate = elements;
136 protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
137 if (newDiagram != null) {
138 // for (IElement e : elementsToTranslate) {
139 // System.out.println("element: " + e);
141 processTranslatedSelection(newDiagram, elementsToTranslate);
142 // for (IElement e : elementsToReallyTranslate) {
143 // System.out.println("REAL element: " + e);
147 ILookupService lookup = NodeUtil.getLookupService(node);
148 for (IElement e : elementsToReallyTranslate) {
149 Node n = e.getHint(ElementHints.KEY_SG_NODE);
151 LinkNode link = node.addNode("" + (i), LinkNode.class);
154 String id = lookup.lookupId(n);
156 id = UUID.randomUUID().toString();
158 link.setLookupIdOwner(true);
160 link.setDelegateId(id);
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.
173 // Don't move "connections only" selections.
174 int connectionCount = 0;
175 for (IElement e : elementsToTranslate) {
176 if (e.getElementClass().containsClass(ConnectionHandler.class)) {
180 if (connectionCount == elementsToTranslate.size())
183 elementsToReallyTranslate = new HashSet<IElement>(elementsToTranslate);
184 translatedConnections = new HashSet<IElement>();
186 // 1st: find out if some elements should not be translated
187 // because their parent elements are being translated also.
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);
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();
202 // Check PARENT_OF relationships
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());
215 if (e instanceof IElement) {
216 IElement el = (IElement) e;
218 // Do not try to translate non-moveable elements.
219 if (!el.getElementClass().containsClass(Move.class)) {
220 elementsToReallyTranslate.remove(el);
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;
233 if (parentIsTranslated) {
234 elementsToReallyTranslate.remove(el);
235 elementsToDirty.add(el);
241 // 2nd: Include those connections in the translation for which all
242 // all terminal connected elements are also included in the selection.
244 Collection<Terminal> terminals = new ArrayList<Terminal>();
245 Collection<Connection> connections = new ArrayList<Connection>();
246 Collection<Connection> connections2 = new ArrayList<Connection>();
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);
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);
265 IElement connection = ce.getConnection();
266 if (connection == null)
268 ConnectionHandler ch = connection.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
272 connections2.clear();
273 ch.getTerminalConnections(connection, connections2);
275 boolean allConnectedNodesSelected = true;
276 for (Connection conn2 : connections2) {
277 if (!elementsToReallyTranslate.contains(conn2.node)) {
278 allConnectedNodesSelected = false;
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);
296 // Include all non-selected branch points in the translation that
298 // a) among connections that have been either selected
299 // b) exist in a connection whose all terminal connected
300 // elements are selected.
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);
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;
319 if (anyTerminalConnectionsSelected) {
320 translatedConnections.add(el);
321 Collection<IElement> branchPoints = new ArrayList<IElement>();
322 ch.getBranchPoints(el, branchPoints);
323 elementsToReallyTranslate.addAll(branchPoints);
330 public void addedToContext(ICanvasContext ctx) {
331 super.addedToContext(ctx);
333 quality.setStaticQuality(Quality.LOW);
334 IMouseCursorContext mcc = getContext().getMouseCursorContext();
335 cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.MOVE_CURSOR));
339 public void removedFromContext(ICanvasContext ctx) {
340 if (cursor != null) {
345 quality.setStaticQuality(null);
346 super.removedFromContext(ctx);
349 public AffineTransform getTransform() {
350 double dx = currentPoint.getX() - startingPoint.getX();
351 double dy = currentPoint.getY() - startingPoint.getY();
352 return AffineTransform.getTranslateInstance(dx, dy);
356 * return translate in control coordinate
359 public Point2D getTranslateVector()
361 double dx = currentPoint.getX() - startingPoint.getX();
362 double dy = currentPoint.getY() - startingPoint.getY();
363 return new Point2D.Double(dx, dy);
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)
375 if (e instanceof CommandEvent) {
376 CommandEvent event = (CommandEvent) e;
377 if (event.command.equals( Commands.CANCEL)) {
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.
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");
401 protected boolean move(MouseMovedEvent event) {
402 Point2D canvasPos = util.controlToCanvas(event.controlPosition, null);
403 if (ObjectUtils.objectEquals(currentPoint, canvasPos)) return true;
405 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
406 if (snapAdvisor != null) {
407 IElement someElement = null;
409 for (IElement elem : elementsToReallyTranslate) {
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());
420 snapAdvisor.snap(canvasPos,
423 -oldPos.getX() + currentPoint.getX(),
424 -oldPos.getY() + currentPoint.getY()
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)) {
441 AffineTransform nat = AffineTransform.getTranslateInstance(dx, dy);
442 node.setTransform(nat);
444 currentPoint = canvasPos;
450 protected boolean commit() {
451 TimeLogger.resetTimeAndLog(getClass(), "commit");
452 for (IElement el : elementsToReallyTranslate) {
453 Move move = el.getElementClass().getAtMostOneItemOfClass(Move.class);
455 Point2D oldPos = move.getPosition(el);
456 move.moveTo(el, oldPos.getX() + dx, oldPos.getY() + dy);
460 // Persist all translation modifications.
461 DiagramUtils.mutateDiagram(diagram, m -> {
462 for (IElement e : elementsToReallyTranslate)
463 m.modifyTransform(e);
466 for (IElement dirty : elementsToDirty)
467 dirty.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
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));
484 public void cleanupSG() {