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