--- /dev/null
+/*******************************************************************************\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