1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.handler;
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;
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;
58 * @author Tuukka Lehtonen
60 public class MouseScaleMode extends AbstractMode {
62 private static final Key KEY_SCALE_NODE = new SceneGraphNodeKey(INode.class, "SCALE_NODE");
64 private static final boolean DEBUG = false;
66 @Dependency ElementPainter painter;
69 * The set of elements that are being scaled.
71 Set<IElement> selection;
73 Map<IElement, Point2D> originalScales = new HashMap<IElement, Point2D>();
74 Map<IElement, MutatedElement> scaledElements = new HashMap<IElement, MutatedElement>();
76 Point2D initialMousePos;
77 Point2D pivotPosition;
78 AffineTransform pivot;
79 AffineTransform pivotInverse;
80 Point2D lastMousePos = new Point2D.Double();
81 Point2D newScale = new Point2D.Double();
83 final ISnapAdvisor snapAdvisor;
85 final static public ISnapAdvisor DEFAULT_SNAP = new ISnapAdvisor() {
88 public void snap(Point2D point) {
89 double resolution = 0.1;
91 Math.round(point.getX() / resolution) * resolution,
92 Math.round(point.getY() / resolution) * resolution);
96 public void snap(Point2D point, Point2D[] features) {
102 public MouseScaleMode(int mouseId, MouseInfo mi, Set<IElement> selection) {
103 this(mouseId, mi, selection, DEFAULT_SNAP);
106 public MouseScaleMode(int mouseId, MouseInfo mi, Set<IElement> selection, ISnapAdvisor snapAdvisor) {
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);
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);
121 this.pivotPosition = new Point2D.Double();
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;
127 for (IElement e : this.selection) {
128 Scale scale = e.getElementClass().getAtMostOneItemOfClass(Scale.class);
130 Point2D s = scale.getScale(e);
131 System.out.println("");
132 originalScales.put(e, s);
133 scaledElements.put(e, new MutatedElement(e));
138 protected SingleElementNode node = null;
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));
149 public void cleanupSG() {
155 public void addedToContext(ICanvasContext ctx) {
156 super.addedToContext(ctx);
158 if (selection.isEmpty())
159 asyncExec(new Runnable() {
171 public void removedFromContext(ICanvasContext ctx) {
172 for (MutatedElement me : scaledElements.values())
175 super.removedFromContext(ctx);
178 public boolean handleCommand(CommandEvent ce) {
179 if (Commands.CANCEL.equals(ce.command)) {
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) {
191 } else if (event.keyCode == java.awt.event.KeyEvent.VK_ENTER) {
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) {
206 } else if (event instanceof MouseMovedEvent) {
207 MouseMovedEvent mme = (MouseMovedEvent) event;
208 ElementUtils.controlToCanvasCoordinate(getContext(), mme.controlPosition, lastMousePos);
210 ISnapAdvisor snapAdvisor = getContext().getDefaultHintContext().getHint(DiagramHints.SNAP_ADVISOR);
214 System.out.println("initialpos: " + initialMousePos);
215 System.out.println("pivot: " + pivotPosition);
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);
223 double lx = lastMousePos.getX() - pivotPosition.getX();
224 double ly = lastMousePos.getY() - pivotPosition.getY();
225 double l = Math.sqrt(lx*lx + ly*ly);
236 System.out.println("l: " + l);
237 System.out.println("d: " + d);
238 System.out.println("s: " + s);
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);
247 newScale.setLocation(originalScale.getX() * s, originalScale.getY() * s);
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) {
253 System.out.println("DISCARD new scale:" + newScale);
257 //System.out.println("SET new scale:" + newScale);
259 // Try to snap to grid.
260 if (snapAdvisor != null) {
261 this.snapAdvisor.snap(newScale);
264 double sx = newScale.getX() / originalScale.getX();
265 double sy = newScale.getY() / originalScale.getY();
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());
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);
282 // Prevent singular transforms from being created.
283 if (sx == 0 || sy == 0)
286 // Scale local transform.
287 // The translation part of localAt is useless.
288 localAt.scale(sx, sy);
290 // Figure out the scaled element position after
291 // scaling about pivotPosition.
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
298 // W(p) -> P(p) -> scale p -> W(p) -> L(p)
299 Point2D p = (Point2D) worldPos.clone();
301 System.out.println("Wp: " + p);
303 pivotInverse.transform(p, p);
305 System.out.println("Pp: " + p);
307 p.setLocation(p.getX() * sx, p.getY() * sy);
309 pivot.transform(p, p);
311 System.out.println("Wp: " + p);
313 p.setLocation(p.getX() + worldToLocal.getX(), p.getY() + worldToLocal.getY());
315 System.out.println("Lp: " + p);
317 localAt.setTransform(
318 localAt.getScaleX(), localAt.getShearX(),
319 localAt.getShearY(), localAt.getScaleY(),
323 System.out.println(" -> " + localAt);
325 ec.getSingleItem(Transform.class).setTransform(me, localAt);
333 private void update() {
334 for (IElement me : scaledElements.values())
335 painter.updateElement(node, me, KEY_SCALE_NODE, false);
339 private void cancel() {
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) );
354 Simantics.getSession().asyncRequest(
355 ElementTransforms.setTransformRequest(transformed)
362 // private static AffineTransform uncheckedInverse(AffineTransform at) {
364 // return at.createInverse();
365 // } catch (NoninvertibleTransformException e) {
366 // throw new RuntimeException(e);