]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / pointertool / TranslateMode.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java
new file mode 100644 (file)
index 0000000..fee686f
--- /dev/null
@@ -0,0 +1,492 @@
+/*******************************************************************************\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