]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / pointertool / TranslateMode.java
index fee686f26ce1ea301a88ccaf1aabfbe36cc61133..814afe5195af48d065cec44d011268592fdea7b2 100644 (file)
-/*******************************************************************************\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;
+    }
+
+}