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