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