-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.diagram.participant.pointertool;\r
-\r
-import java.awt.AlphaComposite;\r
-import java.awt.Cursor;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.util.ArrayDeque;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.HashSet;\r
-import java.util.Queue;\r
-import java.util.Set;\r
-import java.util.UUID;\r
-\r
-import org.simantics.g2d.canvas.Hints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.IMouseCursorContext;\r
-import org.simantics.g2d.canvas.IMouseCursorHandle;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
-import org.simantics.g2d.connection.ConnectionEntity;\r
-import org.simantics.g2d.connection.handler.ConnectionHandler;\r
-import org.simantics.g2d.diagram.DiagramHints;\r
-import org.simantics.g2d.diagram.DiagramUtils;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.handler.Relationship;\r
-import org.simantics.g2d.diagram.handler.RelationshipHandler;\r
-import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;\r
-import org.simantics.g2d.diagram.handler.Topology;\r
-import org.simantics.g2d.diagram.handler.Topology.Connection;\r
-import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
-import org.simantics.g2d.element.ElementClass;\r
-import org.simantics.g2d.element.ElementHints;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.handler.Move;\r
-import org.simantics.g2d.element.handler.TerminalTopology;\r
-import org.simantics.g2d.participant.RenderingQualityInteractor;\r
-import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.scenegraph.ILookupService;\r
-import org.simantics.scenegraph.Node;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.events.Event;\r
-import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
-import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
-import org.simantics.scenegraph.g2d.events.command.Commands;\r
-import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
-import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
-import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
-import org.simantics.scenegraph.utils.NodeUtil;\r
-import org.simantics.scenegraph.utils.Quality;\r
-import org.simantics.utils.ObjectUtils;\r
-import org.simantics.utils.logging.TimeLogger;\r
-\r
-/**\r
- * This participant handles the diagram in translate elements mode.\r
- *\r
- * @author Toni Kalajainen\r
- * @author Tuukka Lehtonen\r
- */\r
-public class TranslateMode extends AbstractMode {\r
-\r
- @Reference\r
- protected RenderingQualityInteractor quality;\r
-\r
- @Dependency\r
- protected TransformUtil util;\r
-\r
- protected Point2D startingPoint;\r
- protected Point2D currentPoint;\r
- protected IMouseCursorHandle cursor;\r
-\r
- protected Collection<IElement> elementsToTranslate = Collections.emptyList();\r
- protected Collection<IElement> elementsToReallyTranslate = Collections.emptyList();\r
- protected Collection<IElement> translatedConnections = Collections.emptyList();\r
-\r
- /**\r
- * A set of elements gathered during\r
- * {@link #getElementsToReallyTranslate(IDiagram, Collection)} that is to be\r
- * marked dirty after the translation operation has been completed.\r
- * \r
- * <p>\r
- * This exists to cover cases where indirectly related (not topologically)\r
- * elements are not properly updated after translation operations.\r
- */\r
- protected Collection<IElement> elementsToDirty = new HashSet<IElement>();\r
-\r
- /**\r
- * The node under which the mutated diagram is ghosted in the scene graph.\r
- */\r
- protected G2DParentNode parent;\r
-\r
- /**\r
- * This stays null until the translated diagram parts have been initialized\r
- * into the scene graph. After that, only the translations of the nodes in\r
- * the scene graph are modified.\r
- */\r
- protected SingleElementNode node = null;\r
-\r
- protected double dx;\r
- protected double dy;\r
-\r
- public TranslateMode(Point2D startingPoint, Point2D currentPoint, int mouseId, Collection<IElement> elements) {\r
- super(mouseId);\r
- if (startingPoint == null)\r
- throw new NullPointerException("null startingPoint");\r
- if (currentPoint == null)\r
- throw new NullPointerException("null currentPoint");\r
- if (elements == null)\r
- throw new NullPointerException("null elements");\r
-\r
- this.startingPoint = startingPoint;\r
- this.currentPoint = currentPoint;\r
- this.elementsToTranslate = elements;\r
- }\r
-\r
- @Override\r
- protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {\r
- if (newDiagram != null) {\r
-// for (IElement e : elementsToTranslate) {\r
-// System.out.println("element: " + e);\r
-// }\r
- processTranslatedSelection(newDiagram, elementsToTranslate);\r
-// for (IElement e : elementsToReallyTranslate) {\r
-// System.out.println("REAL element: " + e);\r
-// }\r
-\r
- int i = 0;\r
- ILookupService lookup = NodeUtil.getLookupService(node);\r
- for (IElement e : elementsToReallyTranslate) {\r
- Node n = e.getHint(ElementHints.KEY_SG_NODE);\r
- if (n != null) {\r
- LinkNode link = node.addNode("" + (i), LinkNode.class);\r
- link.setZIndex(i);\r
-\r
- String id = lookup.lookupId(n);\r
- if (id == null) {\r
- id = UUID.randomUUID().toString();\r
- lookup.map(id, n);\r
- link.setLookupIdOwner(true);\r
- }\r
- link.setDelegateId(id);\r
-\r
- ++i;\r
- }\r
- }\r
- }\r
- }\r
-\r
- protected void processTranslatedSelection(IDiagram diagram, Collection<IElement> elementsToTranslate) {\r
- // Only translate elements that are not parents of another elements that\r
- // is in the translated set also. Otherwise we would end up doing double\r
- // translation for the parented elements.\r
-\r
- // Don't move "connections only" selections.\r
- int connectionCount = 0; \r
- for (IElement e : elementsToTranslate) {\r
- if (e.getElementClass().containsClass(ConnectionHandler.class)) {\r
- ++connectionCount;\r
- }\r
- }\r
- if (connectionCount == elementsToTranslate.size())\r
- return;\r
-\r
- elementsToReallyTranslate = new HashSet<IElement>(elementsToTranslate);\r
- translatedConnections = new HashSet<IElement>();\r
-\r
- // 1st: find out if some elements should not be translated\r
- // because their parent elements are being translated also.\r
- // \r
- // Post-invariants:\r
- // * elementsToReallyTranslate does not contain any elements whose parents are also translated\r
- // * elementsToDirty contains all elements whose parents are also translated\r
- RelationshipHandler erh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);\r
- if (erh != null) {\r
- Queue<Object> todo = new ArrayDeque<Object>(elementsToTranslate);\r
- Set<Object> visited = new HashSet<Object>();\r
- Collection<Relation> relations = new ArrayList<Relation>();\r
- while (!todo.isEmpty()) {\r
- Object e = todo.poll();\r
- if (!visited.add(e))\r
- continue;\r
-\r
- // Check PARENT_OF relationships\r
- relations.clear();\r
- erh.getRelations(diagram, e, relations);\r
- for (Relation r : relations) {\r
- if (Relationship.PARENT_OF.equals(r.getRelationship())) {\r
- elementsToReallyTranslate.remove(r.getObject());\r
- if (r.getObject() instanceof IElement)\r
- elementsToDirty.add((IElement) r.getObject());\r
- if (!visited.contains(r.getObject()))\r
- todo.add(r.getObject());\r
- }\r
- }\r
-\r
- if (e instanceof IElement) {\r
- IElement el = (IElement) e;\r
-\r
- // Do not try to translate non-moveable elements.\r
- if (!el.getElementClass().containsClass(Move.class)) {\r
- elementsToReallyTranslate.remove(el);\r
- continue;\r
- }\r
-\r
- // Check Parent handler\r
- Collection<IElement> parents = ElementUtils.getParents(el);\r
- boolean parentIsTranslated = false;\r
- for (IElement parent : parents) {\r
- if (elementsToTranslate.contains(parent)) {\r
- parentIsTranslated = true;\r
- break;\r
- }\r
- }\r
- if (parentIsTranslated) {\r
- elementsToReallyTranslate.remove(el);\r
- elementsToDirty.add(el);\r
- }\r
- }\r
- }\r
- }\r
-\r
- // 2nd: Include those connections in the translation for which all\r
- // all terminal connected elements are also included in the selection.\r
-\r
- Collection<Terminal> terminals = new ArrayList<Terminal>();\r
- Collection<Connection> connections = new ArrayList<Connection>();\r
- Collection<Connection> connections2 = new ArrayList<Connection>();\r
-\r
- Topology topology = diagram.getDiagramClass().getSingleItem(Topology.class);\r
- for (IElement el : new ArrayList<IElement>(elementsToReallyTranslate)) {\r
- // Check if the selection is a connection.\r
- // If it is, translate all of its branchpoints too,\r
- // but only if all of its terminal connected elements\r
- // are about to be translated too.\r
- ElementClass ec = el.getElementClass();\r
- TerminalTopology tt = ec.getAtMostOneItemOfClass(TerminalTopology.class);\r
- if (tt != null) {\r
- terminals.clear();\r
- tt.getTerminals(el, terminals);\r
- for (Terminal terminal : terminals) {\r
- topology.getConnections(el, terminal, connections);\r
- for (Connection conn : connections) {\r
- ConnectionEntity ce = conn.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
- if (ce == null)\r
- continue;\r
- IElement connection = ce.getConnection();\r
- if (connection == null)\r
- continue;\r
- ConnectionHandler ch = connection.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);\r
- if (ch == null)\r
- continue;\r
-\r
- connections2.clear();\r
- ch.getTerminalConnections(connection, connections2);\r
-\r
- boolean allConnectedNodesSelected = true;\r
- for (Connection conn2 : connections2) {\r
- if (!elementsToReallyTranslate.contains(conn2.node)) {\r
- allConnectedNodesSelected = false;\r
- break;\r
- }\r
- }\r
-\r
- if (allConnectedNodesSelected) {\r
- // Finally! It seems like all nodes connected\r
- // with 'connection' are about to be translated.\r
- // We should also include the whole connection\r
- // in the translation.\r
- elementsToReallyTranslate.add(connection);\r
- translatedConnections.add(connection);\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- // Include all non-selected branch points in the translation that\r
- // are either:\r
- // a) among connections that have been either selected\r
- // b) exist in a connection whose all terminal connected\r
- // elements are selected.\r
-\r
- for (IElement el : new ArrayList<IElement>(elementsToReallyTranslate)) {\r
- // Check if the selection is a connection.\r
- // If it is, translate all of its branchpoints too,\r
- // but only if all of its terminal connected elements\r
- // are about to be translated too.\r
- ConnectionHandler ch = el.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);\r
- if (ch != null) {\r
- boolean anyTerminalConnectionsSelected = false;\r
- Collection<Connection> terminalConnections = new ArrayList<Connection>();\r
- ch.getTerminalConnections(el, terminalConnections);\r
- for (Connection conn : terminalConnections) {\r
- if (elementsToTranslate.contains(conn.node)) {\r
- anyTerminalConnectionsSelected = true;\r
- break;\r
- }\r
- }\r
-\r
- if (anyTerminalConnectionsSelected) {\r
- translatedConnections.add(el);\r
- Collection<IElement> branchPoints = new ArrayList<IElement>();\r
- ch.getBranchPoints(el, branchPoints);\r
- elementsToReallyTranslate.addAll(branchPoints);\r
- }\r
- }\r
- }\r
- }\r
-\r
- @Override\r
- public void addedToContext(ICanvasContext ctx) {\r
- super.addedToContext(ctx);\r
- if (quality != null)\r
- quality.setStaticQuality(Quality.LOW);\r
- IMouseCursorContext mcc = getContext().getMouseCursorContext();\r
- cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.MOVE_CURSOR));\r
- }\r
-\r
- @Override\r
- public void removedFromContext(ICanvasContext ctx) {\r
- if (cursor != null) {\r
- cursor.remove();\r
- cursor = null;\r
- }\r
- if (quality != null)\r
- quality.setStaticQuality(null);\r
- super.removedFromContext(ctx);\r
- }\r
-\r
- public AffineTransform getTransform() {\r
- double dx = currentPoint.getX() - startingPoint.getX();\r
- double dy = currentPoint.getY() - startingPoint.getY();\r
- return AffineTransform.getTranslateInstance(dx, dy);\r
- }\r
-\r
- /**\r
- * return translate in control coordinate\r
- * @return\r
- */\r
- public Point2D getTranslateVector()\r
- {\r
- double dx = currentPoint.getX() - startingPoint.getX();\r
- double dy = currentPoint.getY() - startingPoint.getY();\r
- return new Point2D.Double(dx, dy);\r
- }\r
-\r
- @EventHandler(priority = 30)\r
- public boolean handleEvent(Event e) {\r
- // Reject all mouse events not for this mouse.\r
- if (e instanceof MouseEvent) {\r
- MouseEvent me = (MouseEvent) e;\r
- if (me.mouseId != mouseId)\r
- return false;\r
- }\r
-\r
- if (e instanceof CommandEvent) {\r
- CommandEvent event = (CommandEvent) e;\r
- if (event.command.equals( Commands.CANCEL)) {\r
- setDirty();\r
- remove();\r
- return true;\r
- } else if (event.command.equals( Commands.ROTATE_ELEMENT_CCW )\r
- || event.command.equals( Commands.DELETE )\r
- || event.command.equals( Commands.ROTATE_ELEMENT_CW )\r
- || event.command.equals( Commands.FLIP_ELEMENT_HORIZONTAL )\r
- || event.command.equals( Commands.FLIP_ELEMENT_VERTICAL) ) {\r
- // Just eat these commands to disable\r
- // rotation and scaling during translation.\r
- return true;\r
- }\r
- } else if (e instanceof MouseMovedEvent) {\r
- return move((MouseMovedEvent) e);\r
- } else if (e instanceof MouseButtonReleasedEvent) {\r
- if (((MouseButtonEvent)e).button == MouseEvent.LEFT_BUTTON) {\r
- TimeLogger.resetTimeAndLog(getClass(), "handleEvent");\r
- return commit();\r
- }\r
- }\r
- return false;\r
- }\r
-\r
- protected boolean move(MouseMovedEvent event) {\r
- Point2D canvasPos = util.controlToCanvas(event.controlPosition, null);\r
- if (ObjectUtils.objectEquals(currentPoint, canvasPos)) return true;\r
-\r
- ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
- if (snapAdvisor != null) {\r
- IElement someElement = null;\r
-\r
- for (IElement elem : elementsToReallyTranslate) {\r
- someElement = elem;\r
- break;\r
- }\r
-\r
- if (someElement != null) {\r
- AffineTransform at = node.getTransform();\r
- Move m = someElement.getElementClass().getSingleItem(Move.class);\r
- Point2D oldPos = m.getPosition(someElement);\r
- oldPos.setLocation(oldPos.getX() + at.getTranslateX(), oldPos.getY() + at.getTranslateY());\r
-\r
- snapAdvisor.snap(canvasPos,\r
- new Point2D[] {\r
- new Point2D.Double(\r
- -oldPos.getX() + currentPoint.getX(),\r
- -oldPos.getY() + currentPoint.getY()\r
- )\r
- });\r
- }\r
- }\r
-\r
- dx = canvasPos.getX()-startingPoint.getX();\r
- dy = canvasPos.getY()-startingPoint.getY();\r
- if ((event.stateMask & MouseEvent.CTRL_MASK) != 0) {\r
- // Forced horizontal/vertical -only movement\r
- if (Math.abs(dx) >= Math.abs(dy)) {\r
- dy = 0;\r
- } else {\r
- dx = 0;\r
- }\r
- }\r
-\r
- AffineTransform nat = AffineTransform.getTranslateInstance(dx, dy);\r
- node.setTransform(nat);\r
-\r
- currentPoint = canvasPos;\r
-\r
- setDirty();\r
- return true;\r
- }\r
-\r
- protected boolean commit() {\r
- TimeLogger.resetTimeAndLog(getClass(), "commit");\r
- for (IElement el : elementsToReallyTranslate) {\r
- Move move = el.getElementClass().getAtMostOneItemOfClass(Move.class);\r
- if (move != null) {\r
- Point2D oldPos = move.getPosition(el);\r
- move.moveTo(el, oldPos.getX() + dx, oldPos.getY() + dy);\r
- }\r
- }\r
-\r
- // Persist all translation modifications.\r
- DiagramUtils.mutateDiagram(diagram, m -> {\r
- for (IElement e : elementsToReallyTranslate)\r
- m.modifyTransform(e);\r
- });\r
-\r
- for (IElement dirty : elementsToDirty)\r
- dirty.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
-\r
- setDirty();\r
- remove();\r
- return false;\r
- }\r
-\r
- @SGInit\r
- public void initSG(G2DParentNode parent) {\r
- this.parent = parent;\r
- node = parent.addNode("translate ghost", SingleElementNode.class);\r
- node.setZIndex(1000);\r
- node.setVisible(Boolean.TRUE);\r
- node.setComposite(AlphaComposite.SrcOver.derive(0.4f));\r
- }\r
-\r
- @SGCleanup\r
- public void cleanupSG() {\r
- if (node != null) {\r
- node.remove();\r
- node = null;\r
- }\r
- parent = null;\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.g2d.diagram.participant.pointertool;
+
+import java.awt.AlphaComposite;
+import java.awt.Cursor;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Queue;
+import java.util.Set;
+import java.util.UUID;
+
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.IMouseCursorContext;
+import org.simantics.g2d.canvas.IMouseCursorHandle;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.connection.ConnectionEntity;
+import org.simantics.g2d.connection.handler.ConnectionHandler;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.Relationship;
+import org.simantics.g2d.diagram.handler.RelationshipHandler;
+import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
+import org.simantics.g2d.diagram.handler.Topology;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.Move;
+import org.simantics.g2d.element.handler.TerminalTopology;
+import org.simantics.g2d.participant.RenderingQualityInteractor;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.scenegraph.ILookupService;
+import org.simantics.scenegraph.Node;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.events.Event;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.scenegraph.g2d.nodes.LinkNode;
+import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
+import org.simantics.scenegraph.utils.NodeUtil;
+import org.simantics.scenegraph.utils.Quality;
+import org.simantics.utils.ObjectUtils;
+import org.simantics.utils.logging.TimeLogger;
+
+/**
+ * This participant handles the diagram in translate elements mode.
+ *
+ * @author Toni Kalajainen
+ * @author Tuukka Lehtonen
+ */
+public class TranslateMode extends AbstractMode {
+
+ @Reference
+ protected RenderingQualityInteractor quality;
+
+ @Dependency
+ protected TransformUtil util;
+
+ protected Point2D startingPoint;
+ protected Point2D currentPoint;
+ protected IMouseCursorHandle cursor;
+
+ protected Collection<IElement> elementsToTranslate = Collections.emptyList();
+ protected Collection<IElement> elementsToReallyTranslate = Collections.emptyList();
+ protected Collection<IElement> translatedConnections = Collections.emptyList();
+
+ /**
+ * A set of elements gathered during
+ * {@link #getElementsToReallyTranslate(IDiagram, Collection)} that is to be
+ * marked dirty after the translation operation has been completed.
+ *
+ * <p>
+ * This exists to cover cases where indirectly related (not topologically)
+ * elements are not properly updated after translation operations.
+ */
+ protected Collection<IElement> elementsToDirty = new HashSet<IElement>();
+
+ /**
+ * The node under which the mutated diagram is ghosted in the scene graph.
+ */
+ protected G2DParentNode parent;
+
+ /**
+ * This stays null until the translated diagram parts have been initialized
+ * into the scene graph. After that, only the translations of the nodes in
+ * the scene graph are modified.
+ */
+ protected SingleElementNode node = null;
+
+ protected double dx;
+ protected double dy;
+
+ public TranslateMode(Point2D startingPoint, Point2D currentPoint, int mouseId, Collection<IElement> elements) {
+ super(mouseId);
+ if (startingPoint == null)
+ throw new NullPointerException("null startingPoint");
+ if (currentPoint == null)
+ throw new NullPointerException("null currentPoint");
+ if (elements == null)
+ throw new NullPointerException("null elements");
+
+ this.startingPoint = startingPoint;
+ this.currentPoint = currentPoint;
+ this.elementsToTranslate = elements;
+ }
+
+ @Override
+ protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
+ if (newDiagram != null) {
+// for (IElement e : elementsToTranslate) {
+// System.out.println("element: " + e);
+// }
+ processTranslatedSelection(newDiagram, elementsToTranslate);
+// for (IElement e : elementsToReallyTranslate) {
+// System.out.println("REAL element: " + e);
+// }
+
+ int i = 0;
+ ILookupService lookup = NodeUtil.getLookupService(node);
+ for (IElement e : elementsToReallyTranslate) {
+ Node n = e.getHint(ElementHints.KEY_SG_NODE);
+ if (n != null) {
+ LinkNode link = node.addNode("" + (i), LinkNode.class);
+ link.setZIndex(i);
+
+ String id = lookup.lookupId(n);
+ if (id == null) {
+ id = UUID.randomUUID().toString();
+ lookup.map(id, n);
+ link.setLookupIdOwner(true);
+ }
+ link.setDelegateId(id);
+
+ ++i;
+ }
+ }
+ }
+ }
+
+ protected void processTranslatedSelection(IDiagram diagram, Collection<IElement> elementsToTranslate) {
+ // Only translate elements that are not parents of another elements that
+ // is in the translated set also. Otherwise we would end up doing double
+ // translation for the parented elements.
+
+ // Don't move "connections only" selections.
+ int connectionCount = 0;
+ for (IElement e : elementsToTranslate) {
+ if (e.getElementClass().containsClass(ConnectionHandler.class)) {
+ ++connectionCount;
+ }
+ }
+ if (connectionCount == elementsToTranslate.size())
+ return;
+
+ elementsToReallyTranslate = new HashSet<IElement>(elementsToTranslate);
+ translatedConnections = new HashSet<IElement>();
+
+ // 1st: find out if some elements should not be translated
+ // because their parent elements are being translated also.
+ //
+ // Post-invariants:
+ // * elementsToReallyTranslate does not contain any elements whose parents are also translated
+ // * elementsToDirty contains all elements whose parents are also translated
+ RelationshipHandler erh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
+ if (erh != null) {
+ Queue<Object> todo = new ArrayDeque<Object>(elementsToTranslate);
+ Set<Object> visited = new HashSet<Object>();
+ Collection<Relation> relations = new ArrayList<Relation>();
+ while (!todo.isEmpty()) {
+ Object e = todo.poll();
+ if (!visited.add(e))
+ continue;
+
+ // Check PARENT_OF relationships
+ relations.clear();
+ erh.getRelations(diagram, e, relations);
+ for (Relation r : relations) {
+ if (Relationship.PARENT_OF.equals(r.getRelationship())) {
+ elementsToReallyTranslate.remove(r.getObject());
+ if (r.getObject() instanceof IElement)
+ elementsToDirty.add((IElement) r.getObject());
+ if (!visited.contains(r.getObject()))
+ todo.add(r.getObject());
+ }
+ }
+
+ if (e instanceof IElement) {
+ IElement el = (IElement) e;
+
+ // Do not try to translate non-moveable elements.
+ if (!el.getElementClass().containsClass(Move.class)) {
+ elementsToReallyTranslate.remove(el);
+ continue;
+ }
+
+ // Check Parent handler
+ Collection<IElement> parents = ElementUtils.getParents(el);
+ boolean parentIsTranslated = false;
+ for (IElement parent : parents) {
+ if (elementsToTranslate.contains(parent)) {
+ parentIsTranslated = true;
+ break;
+ }
+ }
+ if (parentIsTranslated) {
+ elementsToReallyTranslate.remove(el);
+ elementsToDirty.add(el);
+ }
+ }
+ }
+ }
+
+ // 2nd: Include those connections in the translation for which all
+ // all terminal connected elements are also included in the selection.
+
+ Collection<Terminal> terminals = new ArrayList<Terminal>();
+ Collection<Connection> connections = new ArrayList<Connection>();
+ Collection<Connection> connections2 = new ArrayList<Connection>();
+
+ Topology topology = diagram.getDiagramClass().getSingleItem(Topology.class);
+ for (IElement el : new ArrayList<IElement>(elementsToReallyTranslate)) {
+ // Check if the selection is a connection.
+ // If it is, translate all of its branchpoints too,
+ // but only if all of its terminal connected elements
+ // are about to be translated too.
+ ElementClass ec = el.getElementClass();
+ TerminalTopology tt = ec.getAtMostOneItemOfClass(TerminalTopology.class);
+ if (tt != null) {
+ terminals.clear();
+ tt.getTerminals(el, terminals);
+ for (Terminal terminal : terminals) {
+ topology.getConnections(el, terminal, connections);
+ for (Connection conn : connections) {
+ ConnectionEntity ce = conn.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+ if (ce == null)
+ continue;
+ IElement connection = ce.getConnection();
+ if (connection == null)
+ continue;
+ ConnectionHandler ch = connection.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
+ if (ch == null)
+ continue;
+
+ connections2.clear();
+ ch.getTerminalConnections(connection, connections2);
+
+ boolean allConnectedNodesSelected = true;
+ for (Connection conn2 : connections2) {
+ if (!elementsToReallyTranslate.contains(conn2.node)) {
+ allConnectedNodesSelected = false;
+ break;
+ }
+ }
+
+ if (allConnectedNodesSelected) {
+ // Finally! It seems like all nodes connected
+ // with 'connection' are about to be translated.
+ // We should also include the whole connection
+ // in the translation.
+ elementsToReallyTranslate.add(connection);
+ translatedConnections.add(connection);
+ }
+ }
+ }
+ }
+ }
+
+ // Include all non-selected branch points in the translation that
+ // are either:
+ // a) among connections that have been either selected
+ // b) exist in a connection whose all terminal connected
+ // elements are selected.
+
+ for (IElement el : new ArrayList<IElement>(elementsToReallyTranslate)) {
+ // Check if the selection is a connection.
+ // If it is, translate all of its branchpoints too,
+ // but only if all of its terminal connected elements
+ // are about to be translated too.
+ ConnectionHandler ch = el.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
+ if (ch != null) {
+ boolean anyTerminalConnectionsSelected = false;
+ Collection<Connection> terminalConnections = new ArrayList<Connection>();
+ ch.getTerminalConnections(el, terminalConnections);
+ for (Connection conn : terminalConnections) {
+ if (elementsToTranslate.contains(conn.node)) {
+ anyTerminalConnectionsSelected = true;
+ break;
+ }
+ }
+
+ if (anyTerminalConnectionsSelected) {
+ translatedConnections.add(el);
+ Collection<IElement> branchPoints = new ArrayList<IElement>();
+ ch.getBranchPoints(el, branchPoints);
+ elementsToReallyTranslate.addAll(branchPoints);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void addedToContext(ICanvasContext ctx) {
+ super.addedToContext(ctx);
+ if (quality != null)
+ quality.setStaticQuality(Quality.LOW);
+ IMouseCursorContext mcc = getContext().getMouseCursorContext();
+ cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.MOVE_CURSOR));
+ }
+
+ @Override
+ public void removedFromContext(ICanvasContext ctx) {
+ if (cursor != null) {
+ cursor.remove();
+ cursor = null;
+ }
+ if (quality != null)
+ quality.setStaticQuality(null);
+ super.removedFromContext(ctx);
+ }
+
+ public AffineTransform getTransform() {
+ double dx = currentPoint.getX() - startingPoint.getX();
+ double dy = currentPoint.getY() - startingPoint.getY();
+ return AffineTransform.getTranslateInstance(dx, dy);
+ }
+
+ /**
+ * return translate in control coordinate
+ * @return
+ */
+ public Point2D getTranslateVector()
+ {
+ double dx = currentPoint.getX() - startingPoint.getX();
+ double dy = currentPoint.getY() - startingPoint.getY();
+ return new Point2D.Double(dx, dy);
+ }
+
+ @EventHandler(priority = 30)
+ public boolean handleEvent(Event e) {
+ // Reject all mouse events not for this mouse.
+ if (e instanceof MouseEvent) {
+ MouseEvent me = (MouseEvent) e;
+ if (me.mouseId != mouseId)
+ return false;
+ }
+
+ if (e instanceof CommandEvent) {
+ CommandEvent event = (CommandEvent) e;
+ if (event.command.equals( Commands.CANCEL)) {
+ setDirty();
+ remove();
+ return true;
+ } else if (event.command.equals( Commands.ROTATE_ELEMENT_CCW )
+ || event.command.equals( Commands.DELETE )
+ || event.command.equals( Commands.ROTATE_ELEMENT_CW )
+ || event.command.equals( Commands.FLIP_ELEMENT_HORIZONTAL )
+ || event.command.equals( Commands.FLIP_ELEMENT_VERTICAL) ) {
+ // Just eat these commands to disable
+ // rotation and scaling during translation.
+ return true;
+ }
+ } else if (e instanceof MouseMovedEvent) {
+ return move((MouseMovedEvent) e);
+ } else if (e instanceof MouseButtonReleasedEvent) {
+ if (((MouseButtonEvent)e).button == MouseEvent.LEFT_BUTTON) {
+ TimeLogger.resetTimeAndLog(getClass(), "handleEvent");
+ return commit();
+ }
+ }
+ return false;
+ }
+
+ protected boolean move(MouseMovedEvent event) {
+ Point2D canvasPos = util.controlToCanvas(event.controlPosition, null);
+ if (ObjectUtils.objectEquals(currentPoint, canvasPos)) return true;
+
+ ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+ if (snapAdvisor != null) {
+ IElement someElement = null;
+
+ for (IElement elem : elementsToReallyTranslate) {
+ someElement = elem;
+ break;
+ }
+
+ if (someElement != null) {
+ AffineTransform at = node.getTransform();
+ Move m = someElement.getElementClass().getSingleItem(Move.class);
+ Point2D oldPos = m.getPosition(someElement);
+ oldPos.setLocation(oldPos.getX() + at.getTranslateX(), oldPos.getY() + at.getTranslateY());
+
+ snapAdvisor.snap(canvasPos,
+ new Point2D[] {
+ new Point2D.Double(
+ -oldPos.getX() + currentPoint.getX(),
+ -oldPos.getY() + currentPoint.getY()
+ )
+ });
+ }
+ }
+
+ dx = canvasPos.getX()-startingPoint.getX();
+ dy = canvasPos.getY()-startingPoint.getY();
+ if ((event.stateMask & MouseEvent.CTRL_MASK) != 0) {
+ // Forced horizontal/vertical -only movement
+ if (Math.abs(dx) >= Math.abs(dy)) {
+ dy = 0;
+ } else {
+ dx = 0;
+ }
+ }
+
+ AffineTransform nat = AffineTransform.getTranslateInstance(dx, dy);
+ node.setTransform(nat);
+
+ currentPoint = canvasPos;
+
+ setDirty();
+ return true;
+ }
+
+ protected boolean commit() {
+ TimeLogger.resetTimeAndLog(getClass(), "commit");
+ for (IElement el : elementsToReallyTranslate) {
+ Move move = el.getElementClass().getAtMostOneItemOfClass(Move.class);
+ if (move != null) {
+ Point2D oldPos = move.getPosition(el);
+ move.moveTo(el, oldPos.getX() + dx, oldPos.getY() + dy);
+ }
+ }
+
+ // Persist all translation modifications.
+ DiagramUtils.mutateDiagram(diagram, m -> {
+ for (IElement e : elementsToReallyTranslate)
+ m.modifyTransform(e);
+ });
+
+ for (IElement dirty : elementsToDirty)
+ dirty.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
+
+ setDirty();
+ remove();
+ return false;
+ }
+
+ @SGInit
+ public void initSG(G2DParentNode parent) {
+ this.parent = parent;
+ node = parent.addNode("translate ghost", SingleElementNode.class);
+ node.setZIndex(1000);
+ node.setVisible(Boolean.TRUE);
+ node.setComposite(AlphaComposite.SrcOver.derive(0.4f));
+ }
+
+ @SGCleanup
+ public void cleanupSG() {
+ if (node != null) {
+ node.remove();
+ node = null;
+ }
+ parent = null;
+ }
+
+}