]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/MouseScaleMode.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / MouseScaleMode.java
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/MouseScaleMode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/MouseScaleMode.java
new file mode 100644 (file)
index 0000000..8ba0ba7
--- /dev/null
@@ -0,0 +1,370 @@
+/*******************************************************************************\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.diagram.handler;\r
+\r
+import java.awt.AlphaComposite;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import org.simantics.Simantics;\r
+import org.simantics.db.Resource;\r
+import org.simantics.diagram.elements.ElementTransforms;\r
+import org.simantics.diagram.elements.ElementTransforms.TransformedObject;\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
+import org.simantics.g2d.diagram.DiagramHints;\r
+import org.simantics.g2d.diagram.participant.ElementPainter;\r
+import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;\r
+import org.simantics.g2d.element.ElementClass;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.SceneGraphNodeKey;\r
+import org.simantics.g2d.element.handler.Scale;\r
+import org.simantics.g2d.element.handler.Transform;\r
+import org.simantics.g2d.element.impl.MutatedElement;\r
+import org.simantics.g2d.participant.MouseUtil.MouseInfo;\r
+import org.simantics.scenegraph.INode;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\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.SingleElementNode;\r
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class MouseScaleMode extends AbstractMode {\r
+\r
+    private static final Key      KEY_SCALE_NODE = new SceneGraphNodeKey(INode.class, "SCALE_NODE");\r
+\r
+    private static final boolean  DEBUG          = false;\r
+\r
+    @Dependency ElementPainter    painter;\r
+\r
+    /**\r
+     * The set of elements that are being scaled.\r
+     */\r
+    Set<IElement>                 selection;\r
+\r
+    Map<IElement, Point2D>        originalScales = new HashMap<IElement, Point2D>();\r
+    Map<IElement, MutatedElement> scaledElements = new HashMap<IElement, MutatedElement>();\r
+\r
+    Point2D                       initialMousePos;\r
+    Point2D                       pivotPosition;\r
+    AffineTransform               pivot;\r
+    AffineTransform               pivotInverse;\r
+    Point2D                       lastMousePos   = new Point2D.Double();\r
+    Point2D                       newScale       = new Point2D.Double();\r
+    \r
+    final ISnapAdvisor                         snapAdvisor;\r
+    \r
+    final static public ISnapAdvisor DEFAULT_SNAP = new ISnapAdvisor() {\r
+\r
+               @Override\r
+               public void snap(Point2D point) {\r
+            double resolution = 0.1;\r
+            point.setLocation(\r
+                    Math.round(point.getX() / resolution) * resolution,\r
+                    Math.round(point.getY() / resolution) * resolution);\r
+               }\r
+\r
+               @Override\r
+               public void snap(Point2D point, Point2D[] features) {\r
+                       snap(point);\r
+               }\r
+       \r
+    };\r
+\r
+    public MouseScaleMode(int mouseId, MouseInfo mi, Set<IElement> selection) {\r
+       this(mouseId, mi, selection, DEFAULT_SNAP);\r
+    }\r
+\r
+    public MouseScaleMode(int mouseId, MouseInfo mi, Set<IElement> selection, ISnapAdvisor snapAdvisor) {\r
+        super(mouseId);\r
+        this.snapAdvisor = snapAdvisor;\r
+        this.selection = selection;\r
+        this.selection = new HashSet<IElement>(selection);\r
+        for (IElement e : selection)\r
+            if (!e.getElementClass().containsClass(Scale.class))\r
+                this.selection.remove(e);\r
+\r
+        if (this.selection.size() == 1) {\r
+            AffineTransform at = ElementUtils.getTransform(this.selection.iterator().next());\r
+            this.pivotPosition = new Point2D.Double(at.getTranslateX(), at.getTranslateY());\r
+        } else if (this.selection.size() > 1) {\r
+            this.pivotPosition = ElementUtils.getElementBoundsCenter(this.selection, null);\r
+        } else {\r
+            this.pivotPosition = new Point2D.Double();\r
+        }\r
+        this.pivot = AffineTransform.getTranslateInstance(pivotPosition.getX(), pivotPosition.getY());\r
+        this.pivotInverse = AffineTransform.getTranslateInstance(-pivotPosition.getX(), -pivotPosition.getY());\r
+        this.initialMousePos = mi != null ? (Point2D) mi.canvasPosition.clone() : null;\r
+\r
+        for (IElement e : this.selection) {\r
+            Scale scale = e.getElementClass().getAtMostOneItemOfClass(Scale.class);\r
+            if (scale != null) {\r
+                Point2D s = scale.getScale(e);\r
+                System.out.println("");\r
+                originalScales.put(e, s);\r
+                scaledElements.put(e, new MutatedElement(e));\r
+            }\r
+        }\r
+    }\r
+\r
+    protected SingleElementNode node = null;\r
+\r
+    @SGInit\r
+    public void initSG(G2DParentNode parent) {\r
+        // Using SingleElementNode for AlphaComposite.\r
+        node = parent.addNode("mouse scale ghost", SingleElementNode.class);\r
+        node.setZIndex(Integer.MAX_VALUE - 1000);\r
+        node.setComposite(AlphaComposite.SrcOver.derive(0.30f));\r
+    }\r
+\r
+    @SGCleanup\r
+    public void cleanupSG() {\r
+        node.remove();\r
+        node = null;\r
+    }\r
+\r
+    @Override\r
+    public void addedToContext(ICanvasContext ctx) {\r
+        super.addedToContext(ctx);\r
+\r
+        if (selection.isEmpty())\r
+            asyncExec(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                    if (!isRemoved())\r
+                        remove();\r
+                }\r
+            });\r
+        else\r
+            update();\r
+    }\r
+\r
+    @Override\r
+    public void removedFromContext(ICanvasContext ctx) {\r
+        for (MutatedElement me : scaledElements.values())\r
+            me.dispose();\r
+\r
+        super.removedFromContext(ctx);\r
+    }\r
+\r
+    public boolean handleCommand(CommandEvent ce) {\r
+        if (Commands.CANCEL.equals(ce.command)) {\r
+            cancel();\r
+            return true;\r
+        }\r
+        return true;\r
+    }\r
+\r
+    @EventHandler(priority = Integer.MAX_VALUE)\r
+    public boolean handleKeys(KeyEvent event) {\r
+        if (event instanceof KeyPressedEvent) {\r
+            if (event.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) {\r
+                cancel();\r
+            } else if (event.keyCode == java.awt.event.KeyEvent.VK_ENTER) {\r
+                commit();\r
+            }\r
+        }\r
+        return true;\r
+    }\r
+\r
+    @EventHandler(priority = Integer.MAX_VALUE)\r
+    public boolean handleMouse(MouseEvent event) {\r
+        //System.out.println("scale mouse event: " + event);\r
+        if (event instanceof MouseButtonPressedEvent) {\r
+            MouseButtonPressedEvent mbpe = (MouseButtonPressedEvent) event;\r
+            if (mbpe.button == MouseEvent.LEFT_BUTTON) {\r
+                commit();\r
+            }\r
+        } else if (event instanceof MouseMovedEvent) {\r
+            MouseMovedEvent mme = (MouseMovedEvent) event;\r
+            ElementUtils.controlToCanvasCoordinate(getContext(), mme.controlPosition, lastMousePos);\r
+\r
+            ISnapAdvisor snapAdvisor = getContext().getDefaultHintContext().getHint(DiagramHints.SNAP_ADVISOR);\r
+\r
+            double d = 0;\r
+            if (DEBUG) {\r
+                System.out.println("initialpos: " + initialMousePos);\r
+                System.out.println("pivot: " + pivotPosition);\r
+            }\r
+            if (initialMousePos != null) {\r
+                double dx = initialMousePos.getX() - pivotPosition.getX();\r
+                double dy = initialMousePos.getY() - pivotPosition.getY();\r
+                d = Math.sqrt(dx*dx + dy*dy);\r
+            }\r
+\r
+            double lx = lastMousePos.getX() - pivotPosition.getX();\r
+            double ly = lastMousePos.getY() - pivotPosition.getY();\r
+            double l = Math.sqrt(lx*lx + ly*ly);\r
+\r
+            // Safety measures.\r
+            double s = l;\r
+            if (d > 1e-9)\r
+                s /= d;\r
+            else if (d == 0)\r
+                s *= .01;\r
+            else\r
+                return true;\r
+            if(DEBUG) {\r
+               System.out.println("l: " + l);\r
+               System.out.println("d: " + d);\r
+               System.out.println("s: " + s);\r
+            }\r
+\r
+            for (Map.Entry<IElement, Point2D> entry : originalScales.entrySet()) {\r
+                IElement e = entry.getKey();\r
+                Point2D originalScale = entry.getValue();\r
+                ElementClass ec = e.getElementClass();\r
+                IElement me = scaledElements.get(e);\r
+\r
+                newScale.setLocation(originalScale.getX() * s, originalScale.getY() * s);\r
+\r
+                // Limit downwards scale to 1/10000 just to keep unwanted 0\r
+                // determinant problems away easily.\r
+                if (newScale.getX() < 2e-4 || newScale.getY() < 2e-4) {\r
+                    if(DEBUG) {\r
+                       System.out.println("DISCARD new scale:" + newScale);\r
+                    }\r
+                    continue;\r
+                }\r
+                //System.out.println("SET new scale:" + newScale);\r
+\r
+                // Try to snap to grid.\r
+                if (snapAdvisor != null) {\r
+                       this.snapAdvisor.snap(newScale);\r
+                }\r
+\r
+                double sx = newScale.getX() / originalScale.getX();\r
+                double sy = newScale.getY() / originalScale.getY();\r
+\r
+                // Reset transform\r
+\r
+                // localAt <- local transform(e)\r
+                // worldAt <- world transform(e)\r
+                AffineTransform localAt = ElementUtils.getLocalTransform(e, new AffineTransform());\r
+                Point2D localPos = ElementUtils.getPos(e, new Point2D.Double());\r
+                Point2D worldPos = ElementUtils.getAbsolutePos(e, new Point2D.Double());\r
+                Point2D worldToLocal = new Point2D.Double(localPos.getX() - worldPos.getX(), localPos.getY() - worldPos.getY());\r
+                if (DEBUG) {\r
+                    System.out.println("pivot: " + pivot);\r
+                    System.out.println("pivot^-1: " + pivotInverse);\r
+                    System.out.println("localAt: " + localAt);\r
+                    System.out.println("sx: " + sx);\r
+                }\r
+\r
+                // Prevent singular transforms from being created.\r
+                if (sx == 0 || sy == 0)\r
+                    continue;\r
+\r
+                // Scale local transform.\r
+                // The translation part of localAt is useless.\r
+                localAt.scale(sx, sy);\r
+\r
+                // Figure out the scaled element position after\r
+                // scaling about pivotPosition.\r
+                // \r
+                // L = element local coordinate system\r
+                // W = world coordinate system\r
+                // P = pivot coordinate system\r
+                // X(p): point p is in coordinate system X\r
+\r
+                // W(p) -> P(p) -> scale p -> W(p) -> L(p)\r
+                Point2D p = (Point2D) worldPos.clone();\r
+                if (DEBUG)\r
+                    System.out.println("Wp: " + p);\r
+                // -> P(p)\r
+                pivotInverse.transform(p, p);\r
+                if (DEBUG)\r
+                    System.out.println("Pp: " + p);\r
+                // scale(p)\r
+                p.setLocation(p.getX() * sx, p.getY() * sy);\r
+                // -> W(p)\r
+                pivot.transform(p, p);\r
+                if (DEBUG)\r
+                    System.out.println("Wp: " + p);\r
+                // -> L(p)\r
+                p.setLocation(p.getX() + worldToLocal.getX(), p.getY() + worldToLocal.getY());\r
+                if (DEBUG)\r
+                    System.out.println("Lp: " + p);\r
+\r
+                localAt.setTransform(\r
+                        localAt.getScaleX(), localAt.getShearX(),\r
+                        localAt.getShearY(), localAt.getScaleY(),\r
+                        p.getX(), p.getY());\r
+\r
+                if (DEBUG)\r
+                    System.out.println("  -> " + localAt);\r
+\r
+                ec.getSingleItem(Transform.class).setTransform(me, localAt);\r
+            }\r
+\r
+            update();\r
+        }\r
+        return true;\r
+    }\r
+\r
+    private void update() {\r
+        for (IElement me : scaledElements.values())\r
+            painter.updateElement(node, me, KEY_SCALE_NODE, false);\r
+        setDirty();\r
+    }\r
+\r
+    private void cancel() {\r
+        setDirty();\r
+        remove();\r
+    }\r
+\r
+    private void commit() {\r
+        Collection<TransformedObject> transformed = new ArrayList<TransformedObject>();\r
+        for (IElement e : scaledElements.values()) {\r
+            Object obj = ElementUtils.getObject(e);\r
+            if (obj instanceof Resource) {\r
+                AffineTransform at = ElementUtils.getLocalTransform(e, new AffineTransform());\r
+                transformed.add( new TransformedObject((Resource) obj, at) );\r
+            }\r
+        }\r
+\r
+        Simantics.getSession().asyncRequest(\r
+                ElementTransforms.setTransformRequest(transformed)\r
+                );\r
+\r
+        setDirty();\r
+        remove();\r
+    }\r
+\r
+//    private static AffineTransform uncheckedInverse(AffineTransform at) {\r
+//        try {\r
+//            return at.createInverse();\r
+//        } catch (NoninvertibleTransformException e) {\r
+//            throw new RuntimeException(e);\r
+//        }\r
+//    }\r
+\r
+}
\ No newline at end of file