]> gerrit.simantics Code Review - simantics/platform.git/blob - 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
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.diagram.handler;\r
13 \r
14 import java.awt.AlphaComposite;\r
15 import java.awt.geom.AffineTransform;\r
16 import java.awt.geom.Point2D;\r
17 import java.util.ArrayList;\r
18 import java.util.Collection;\r
19 import java.util.HashMap;\r
20 import java.util.HashSet;\r
21 import java.util.Map;\r
22 import java.util.Set;\r
23 \r
24 import org.simantics.Simantics;\r
25 import org.simantics.db.Resource;\r
26 import org.simantics.diagram.elements.ElementTransforms;\r
27 import org.simantics.diagram.elements.ElementTransforms.TransformedObject;\r
28 import org.simantics.g2d.canvas.ICanvasContext;\r
29 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
30 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
31 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
32 import org.simantics.g2d.diagram.DiagramHints;\r
33 import org.simantics.g2d.diagram.participant.ElementPainter;\r
34 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;\r
35 import org.simantics.g2d.element.ElementClass;\r
36 import org.simantics.g2d.element.ElementUtils;\r
37 import org.simantics.g2d.element.IElement;\r
38 import org.simantics.g2d.element.SceneGraphNodeKey;\r
39 import org.simantics.g2d.element.handler.Scale;\r
40 import org.simantics.g2d.element.handler.Transform;\r
41 import org.simantics.g2d.element.impl.MutatedElement;\r
42 import org.simantics.g2d.participant.MouseUtil.MouseInfo;\r
43 import org.simantics.scenegraph.INode;\r
44 import org.simantics.scenegraph.g2d.G2DParentNode;\r
45 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
46 import org.simantics.scenegraph.g2d.events.KeyEvent;\r
47 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
48 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
49 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
50 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
51 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
52 import org.simantics.scenegraph.g2d.events.command.Commands;\r
53 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
54 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
55 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
56 \r
57 /**\r
58  * @author Tuukka Lehtonen\r
59  */\r
60 public class MouseScaleMode extends AbstractMode {\r
61 \r
62     private static final Key      KEY_SCALE_NODE = new SceneGraphNodeKey(INode.class, "SCALE_NODE");\r
63 \r
64     private static final boolean  DEBUG          = false;\r
65 \r
66     @Dependency ElementPainter    painter;\r
67 \r
68     /**\r
69      * The set of elements that are being scaled.\r
70      */\r
71     Set<IElement>                 selection;\r
72 \r
73     Map<IElement, Point2D>        originalScales = new HashMap<IElement, Point2D>();\r
74     Map<IElement, MutatedElement> scaledElements = new HashMap<IElement, MutatedElement>();\r
75 \r
76     Point2D                       initialMousePos;\r
77     Point2D                       pivotPosition;\r
78     AffineTransform               pivot;\r
79     AffineTransform               pivotInverse;\r
80     Point2D                       lastMousePos   = new Point2D.Double();\r
81     Point2D                       newScale       = new Point2D.Double();\r
82     \r
83     final ISnapAdvisor                          snapAdvisor;\r
84     \r
85     final static public ISnapAdvisor DEFAULT_SNAP = new ISnapAdvisor() {\r
86 \r
87                 @Override\r
88                 public void snap(Point2D point) {\r
89             double resolution = 0.1;\r
90             point.setLocation(\r
91                     Math.round(point.getX() / resolution) * resolution,\r
92                     Math.round(point.getY() / resolution) * resolution);\r
93                 }\r
94 \r
95                 @Override\r
96                 public void snap(Point2D point, Point2D[] features) {\r
97                         snap(point);\r
98                 }\r
99         \r
100     };\r
101 \r
102     public MouseScaleMode(int mouseId, MouseInfo mi, Set<IElement> selection) {\r
103         this(mouseId, mi, selection, DEFAULT_SNAP);\r
104     }\r
105 \r
106     public MouseScaleMode(int mouseId, MouseInfo mi, Set<IElement> selection, ISnapAdvisor snapAdvisor) {\r
107         super(mouseId);\r
108         this.snapAdvisor = snapAdvisor;\r
109         this.selection = selection;\r
110         this.selection = new HashSet<IElement>(selection);\r
111         for (IElement e : selection)\r
112             if (!e.getElementClass().containsClass(Scale.class))\r
113                 this.selection.remove(e);\r
114 \r
115         if (this.selection.size() == 1) {\r
116             AffineTransform at = ElementUtils.getTransform(this.selection.iterator().next());\r
117             this.pivotPosition = new Point2D.Double(at.getTranslateX(), at.getTranslateY());\r
118         } else if (this.selection.size() > 1) {\r
119             this.pivotPosition = ElementUtils.getElementBoundsCenter(this.selection, null);\r
120         } else {\r
121             this.pivotPosition = new Point2D.Double();\r
122         }\r
123         this.pivot = AffineTransform.getTranslateInstance(pivotPosition.getX(), pivotPosition.getY());\r
124         this.pivotInverse = AffineTransform.getTranslateInstance(-pivotPosition.getX(), -pivotPosition.getY());\r
125         this.initialMousePos = mi != null ? (Point2D) mi.canvasPosition.clone() : null;\r
126 \r
127         for (IElement e : this.selection) {\r
128             Scale scale = e.getElementClass().getAtMostOneItemOfClass(Scale.class);\r
129             if (scale != null) {\r
130                 Point2D s = scale.getScale(e);\r
131                 System.out.println("");\r
132                 originalScales.put(e, s);\r
133                 scaledElements.put(e, new MutatedElement(e));\r
134             }\r
135         }\r
136     }\r
137 \r
138     protected SingleElementNode node = null;\r
139 \r
140     @SGInit\r
141     public void initSG(G2DParentNode parent) {\r
142         // Using SingleElementNode for AlphaComposite.\r
143         node = parent.addNode("mouse scale ghost", SingleElementNode.class);\r
144         node.setZIndex(Integer.MAX_VALUE - 1000);\r
145         node.setComposite(AlphaComposite.SrcOver.derive(0.30f));\r
146     }\r
147 \r
148     @SGCleanup\r
149     public void cleanupSG() {\r
150         node.remove();\r
151         node = null;\r
152     }\r
153 \r
154     @Override\r
155     public void addedToContext(ICanvasContext ctx) {\r
156         super.addedToContext(ctx);\r
157 \r
158         if (selection.isEmpty())\r
159             asyncExec(new Runnable() {\r
160                 @Override\r
161                 public void run() {\r
162                     if (!isRemoved())\r
163                         remove();\r
164                 }\r
165             });\r
166         else\r
167             update();\r
168     }\r
169 \r
170     @Override\r
171     public void removedFromContext(ICanvasContext ctx) {\r
172         for (MutatedElement me : scaledElements.values())\r
173             me.dispose();\r
174 \r
175         super.removedFromContext(ctx);\r
176     }\r
177 \r
178     public boolean handleCommand(CommandEvent ce) {\r
179         if (Commands.CANCEL.equals(ce.command)) {\r
180             cancel();\r
181             return true;\r
182         }\r
183         return true;\r
184     }\r
185 \r
186     @EventHandler(priority = Integer.MAX_VALUE)\r
187     public boolean handleKeys(KeyEvent event) {\r
188         if (event instanceof KeyPressedEvent) {\r
189             if (event.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) {\r
190                 cancel();\r
191             } else if (event.keyCode == java.awt.event.KeyEvent.VK_ENTER) {\r
192                 commit();\r
193             }\r
194         }\r
195         return true;\r
196     }\r
197 \r
198     @EventHandler(priority = Integer.MAX_VALUE)\r
199     public boolean handleMouse(MouseEvent event) {\r
200         //System.out.println("scale mouse event: " + event);\r
201         if (event instanceof MouseButtonPressedEvent) {\r
202             MouseButtonPressedEvent mbpe = (MouseButtonPressedEvent) event;\r
203             if (mbpe.button == MouseEvent.LEFT_BUTTON) {\r
204                 commit();\r
205             }\r
206         } else if (event instanceof MouseMovedEvent) {\r
207             MouseMovedEvent mme = (MouseMovedEvent) event;\r
208             ElementUtils.controlToCanvasCoordinate(getContext(), mme.controlPosition, lastMousePos);\r
209 \r
210             ISnapAdvisor snapAdvisor = getContext().getDefaultHintContext().getHint(DiagramHints.SNAP_ADVISOR);\r
211 \r
212             double d = 0;\r
213             if (DEBUG) {\r
214                 System.out.println("initialpos: " + initialMousePos);\r
215                 System.out.println("pivot: " + pivotPosition);\r
216             }\r
217             if (initialMousePos != null) {\r
218                 double dx = initialMousePos.getX() - pivotPosition.getX();\r
219                 double dy = initialMousePos.getY() - pivotPosition.getY();\r
220                 d = Math.sqrt(dx*dx + dy*dy);\r
221             }\r
222 \r
223             double lx = lastMousePos.getX() - pivotPosition.getX();\r
224             double ly = lastMousePos.getY() - pivotPosition.getY();\r
225             double l = Math.sqrt(lx*lx + ly*ly);\r
226 \r
227             // Safety measures.\r
228             double s = l;\r
229             if (d > 1e-9)\r
230                 s /= d;\r
231             else if (d == 0)\r
232                 s *= .01;\r
233             else\r
234                 return true;\r
235             if(DEBUG) {\r
236                 System.out.println("l: " + l);\r
237                 System.out.println("d: " + d);\r
238                 System.out.println("s: " + s);\r
239             }\r
240 \r
241             for (Map.Entry<IElement, Point2D> entry : originalScales.entrySet()) {\r
242                 IElement e = entry.getKey();\r
243                 Point2D originalScale = entry.getValue();\r
244                 ElementClass ec = e.getElementClass();\r
245                 IElement me = scaledElements.get(e);\r
246 \r
247                 newScale.setLocation(originalScale.getX() * s, originalScale.getY() * s);\r
248 \r
249                 // Limit downwards scale to 1/10000 just to keep unwanted 0\r
250                 // determinant problems away easily.\r
251                 if (newScale.getX() < 2e-4 || newScale.getY() < 2e-4) {\r
252                     if(DEBUG) {\r
253                         System.out.println("DISCARD new scale:" + newScale);\r
254                     }\r
255                     continue;\r
256                 }\r
257                 //System.out.println("SET new scale:" + newScale);\r
258 \r
259                 // Try to snap to grid.\r
260                 if (snapAdvisor != null) {\r
261                         this.snapAdvisor.snap(newScale);\r
262                 }\r
263 \r
264                 double sx = newScale.getX() / originalScale.getX();\r
265                 double sy = newScale.getY() / originalScale.getY();\r
266 \r
267                 // Reset transform\r
268 \r
269                 // localAt <- local transform(e)\r
270                 // worldAt <- world transform(e)\r
271                 AffineTransform localAt = ElementUtils.getLocalTransform(e, new AffineTransform());\r
272                 Point2D localPos = ElementUtils.getPos(e, new Point2D.Double());\r
273                 Point2D worldPos = ElementUtils.getAbsolutePos(e, new Point2D.Double());\r
274                 Point2D worldToLocal = new Point2D.Double(localPos.getX() - worldPos.getX(), localPos.getY() - worldPos.getY());\r
275                 if (DEBUG) {\r
276                     System.out.println("pivot: " + pivot);\r
277                     System.out.println("pivot^-1: " + pivotInverse);\r
278                     System.out.println("localAt: " + localAt);\r
279                     System.out.println("sx: " + sx);\r
280                 }\r
281 \r
282                 // Prevent singular transforms from being created.\r
283                 if (sx == 0 || sy == 0)\r
284                     continue;\r
285 \r
286                 // Scale local transform.\r
287                 // The translation part of localAt is useless.\r
288                 localAt.scale(sx, sy);\r
289 \r
290                 // Figure out the scaled element position after\r
291                 // scaling about pivotPosition.\r
292                 // \r
293                 // L = element local coordinate system\r
294                 // W = world coordinate system\r
295                 // P = pivot coordinate system\r
296                 // X(p): point p is in coordinate system X\r
297 \r
298                 // W(p) -> P(p) -> scale p -> W(p) -> L(p)\r
299                 Point2D p = (Point2D) worldPos.clone();\r
300                 if (DEBUG)\r
301                     System.out.println("Wp: " + p);\r
302                 // -> P(p)\r
303                 pivotInverse.transform(p, p);\r
304                 if (DEBUG)\r
305                     System.out.println("Pp: " + p);\r
306                 // scale(p)\r
307                 p.setLocation(p.getX() * sx, p.getY() * sy);\r
308                 // -> W(p)\r
309                 pivot.transform(p, p);\r
310                 if (DEBUG)\r
311                     System.out.println("Wp: " + p);\r
312                 // -> L(p)\r
313                 p.setLocation(p.getX() + worldToLocal.getX(), p.getY() + worldToLocal.getY());\r
314                 if (DEBUG)\r
315                     System.out.println("Lp: " + p);\r
316 \r
317                 localAt.setTransform(\r
318                         localAt.getScaleX(), localAt.getShearX(),\r
319                         localAt.getShearY(), localAt.getScaleY(),\r
320                         p.getX(), p.getY());\r
321 \r
322                 if (DEBUG)\r
323                     System.out.println("  -> " + localAt);\r
324 \r
325                 ec.getSingleItem(Transform.class).setTransform(me, localAt);\r
326             }\r
327 \r
328             update();\r
329         }\r
330         return true;\r
331     }\r
332 \r
333     private void update() {\r
334         for (IElement me : scaledElements.values())\r
335             painter.updateElement(node, me, KEY_SCALE_NODE, false);\r
336         setDirty();\r
337     }\r
338 \r
339     private void cancel() {\r
340         setDirty();\r
341         remove();\r
342     }\r
343 \r
344     private void commit() {\r
345         Collection<TransformedObject> transformed = new ArrayList<TransformedObject>();\r
346         for (IElement e : scaledElements.values()) {\r
347             Object obj = ElementUtils.getObject(e);\r
348             if (obj instanceof Resource) {\r
349                 AffineTransform at = ElementUtils.getLocalTransform(e, new AffineTransform());\r
350                 transformed.add( new TransformedObject((Resource) obj, at) );\r
351             }\r
352         }\r
353 \r
354         Simantics.getSession().asyncRequest(\r
355                 ElementTransforms.setTransformRequest(transformed)\r
356                 );\r
357 \r
358         setDirty();\r
359         remove();\r
360     }\r
361 \r
362 //    private static AffineTransform uncheckedInverse(AffineTransform at) {\r
363 //        try {\r
364 //            return at.createInverse();\r
365 //        } catch (NoninvertibleTransformException e) {\r
366 //            throw new RuntimeException(e);\r
367 //        }\r
368 //    }\r
369 \r
370 }