/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.diagram.handler; import java.awt.AlphaComposite; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.simantics.Simantics; import org.simantics.db.Resource; import org.simantics.diagram.elements.ElementTransforms; import org.simantics.diagram.elements.ElementTransforms.TransformedObject; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.participant.ElementPainter; import org.simantics.g2d.diagram.participant.pointertool.AbstractMode; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.SceneGraphNodeKey; import org.simantics.g2d.element.handler.Scale; import org.simantics.g2d.element.handler.Transform; import org.simantics.g2d.element.impl.MutatedElement; import org.simantics.g2d.participant.MouseUtil.MouseInfo; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.KeyEvent; import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; import org.simantics.scenegraph.g2d.events.command.CommandEvent; import org.simantics.scenegraph.g2d.events.command.Commands; import org.simantics.scenegraph.g2d.nodes.SingleElementNode; import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; import org.simantics.utils.datastructures.hints.IHintContext.Key; /** * @author Tuukka Lehtonen */ public class MouseScaleMode extends AbstractMode { private static final Key KEY_SCALE_NODE = new SceneGraphNodeKey(INode.class, "SCALE_NODE"); private static final boolean DEBUG = false; @Dependency ElementPainter painter; /** * The set of elements that are being scaled. */ Set selection; Map originalScales = new HashMap(); Map scaledElements = new HashMap(); Point2D initialMousePos; Point2D pivotPosition; AffineTransform pivot; AffineTransform pivotInverse; Point2D lastMousePos = new Point2D.Double(); Point2D newScale = new Point2D.Double(); final ISnapAdvisor snapAdvisor; final static public ISnapAdvisor DEFAULT_SNAP = new ISnapAdvisor() { @Override public void snap(Point2D point) { double resolution = 0.1; point.setLocation( Math.round(point.getX() / resolution) * resolution, Math.round(point.getY() / resolution) * resolution); } @Override public void snap(Point2D point, Point2D[] features) { snap(point); } }; public MouseScaleMode(int mouseId, MouseInfo mi, Set selection) { this(mouseId, mi, selection, DEFAULT_SNAP); } public MouseScaleMode(int mouseId, MouseInfo mi, Set selection, ISnapAdvisor snapAdvisor) { super(mouseId); this.snapAdvisor = snapAdvisor; this.selection = selection; this.selection = new HashSet(selection); for (IElement e : selection) if (!e.getElementClass().containsClass(Scale.class)) this.selection.remove(e); if (this.selection.size() == 1) { AffineTransform at = ElementUtils.getTransform(this.selection.iterator().next()); this.pivotPosition = new Point2D.Double(at.getTranslateX(), at.getTranslateY()); } else if (this.selection.size() > 1) { this.pivotPosition = ElementUtils.getElementBoundsCenter(this.selection, null); } else { this.pivotPosition = new Point2D.Double(); } this.pivot = AffineTransform.getTranslateInstance(pivotPosition.getX(), pivotPosition.getY()); this.pivotInverse = AffineTransform.getTranslateInstance(-pivotPosition.getX(), -pivotPosition.getY()); this.initialMousePos = mi != null ? (Point2D) mi.canvasPosition.clone() : null; for (IElement e : this.selection) { Scale scale = e.getElementClass().getAtMostOneItemOfClass(Scale.class); if (scale != null) { Point2D s = scale.getScale(e); System.out.println(""); originalScales.put(e, s); scaledElements.put(e, new MutatedElement(e)); } } } protected SingleElementNode node = null; @SGInit public void initSG(G2DParentNode parent) { // Using SingleElementNode for AlphaComposite. node = parent.addNode("mouse scale ghost", SingleElementNode.class); node.setZIndex(Integer.MAX_VALUE - 1000); node.setComposite(AlphaComposite.SrcOver.derive(0.30f)); } @SGCleanup public void cleanupSG() { node.remove(); node = null; } @Override public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); if (selection.isEmpty()) asyncExec(new Runnable() { @Override public void run() { if (!isRemoved()) remove(); } }); else update(); } @Override public void removedFromContext(ICanvasContext ctx) { for (MutatedElement me : scaledElements.values()) me.dispose(); super.removedFromContext(ctx); } public boolean handleCommand(CommandEvent ce) { if (Commands.CANCEL.equals(ce.command)) { cancel(); return true; } return true; } @EventHandler(priority = Integer.MAX_VALUE) public boolean handleKeys(KeyEvent event) { if (event instanceof KeyPressedEvent) { if (event.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) { cancel(); } else if (event.keyCode == java.awt.event.KeyEvent.VK_ENTER) { commit(); } } return true; } @EventHandler(priority = Integer.MAX_VALUE) public boolean handleMouse(MouseEvent event) { //System.out.println("scale mouse event: " + event); if (event instanceof MouseButtonPressedEvent) { MouseButtonPressedEvent mbpe = (MouseButtonPressedEvent) event; if (mbpe.button == MouseEvent.LEFT_BUTTON) { commit(); } } else if (event instanceof MouseMovedEvent) { MouseMovedEvent mme = (MouseMovedEvent) event; ElementUtils.controlToCanvasCoordinate(getContext(), mme.controlPosition, lastMousePos); ISnapAdvisor snapAdvisor = getContext().getDefaultHintContext().getHint(DiagramHints.SNAP_ADVISOR); double d = 0; if (DEBUG) { System.out.println("initialpos: " + initialMousePos); System.out.println("pivot: " + pivotPosition); } if (initialMousePos != null) { double dx = initialMousePos.getX() - pivotPosition.getX(); double dy = initialMousePos.getY() - pivotPosition.getY(); d = Math.sqrt(dx*dx + dy*dy); } double lx = lastMousePos.getX() - pivotPosition.getX(); double ly = lastMousePos.getY() - pivotPosition.getY(); double l = Math.sqrt(lx*lx + ly*ly); // Safety measures. double s = l; if (d > 1e-9) s /= d; else if (d == 0) s *= .01; else return true; if(DEBUG) { System.out.println("l: " + l); System.out.println("d: " + d); System.out.println("s: " + s); } for (Map.Entry entry : originalScales.entrySet()) { IElement e = entry.getKey(); Point2D originalScale = entry.getValue(); ElementClass ec = e.getElementClass(); IElement me = scaledElements.get(e); newScale.setLocation(originalScale.getX() * s, originalScale.getY() * s); // Limit downwards scale to 1/10000 just to keep unwanted 0 // determinant problems away easily. if (newScale.getX() < 2e-4 || newScale.getY() < 2e-4) { if(DEBUG) { System.out.println("DISCARD new scale:" + newScale); } continue; } //System.out.println("SET new scale:" + newScale); // Try to snap to grid. if (snapAdvisor != null) { this.snapAdvisor.snap(newScale); } double sx = newScale.getX() / originalScale.getX(); double sy = newScale.getY() / originalScale.getY(); // Reset transform // localAt <- local transform(e) // worldAt <- world transform(e) AffineTransform localAt = ElementUtils.getLocalTransform(e, new AffineTransform()); Point2D localPos = ElementUtils.getPos(e, new Point2D.Double()); Point2D worldPos = ElementUtils.getAbsolutePos(e, new Point2D.Double()); Point2D worldToLocal = new Point2D.Double(localPos.getX() - worldPos.getX(), localPos.getY() - worldPos.getY()); if (DEBUG) { System.out.println("pivot: " + pivot); System.out.println("pivot^-1: " + pivotInverse); System.out.println("localAt: " + localAt); System.out.println("sx: " + sx); } // Prevent singular transforms from being created. if (sx == 0 || sy == 0) continue; // Scale local transform. // The translation part of localAt is useless. localAt.scale(sx, sy); // Figure out the scaled element position after // scaling about pivotPosition. // // L = element local coordinate system // W = world coordinate system // P = pivot coordinate system // X(p): point p is in coordinate system X // W(p) -> P(p) -> scale p -> W(p) -> L(p) Point2D p = (Point2D) worldPos.clone(); if (DEBUG) System.out.println("Wp: " + p); // -> P(p) pivotInverse.transform(p, p); if (DEBUG) System.out.println("Pp: " + p); // scale(p) p.setLocation(p.getX() * sx, p.getY() * sy); // -> W(p) pivot.transform(p, p); if (DEBUG) System.out.println("Wp: " + p); // -> L(p) p.setLocation(p.getX() + worldToLocal.getX(), p.getY() + worldToLocal.getY()); if (DEBUG) System.out.println("Lp: " + p); localAt.setTransform( localAt.getScaleX(), localAt.getShearY(), localAt.getShearX(), localAt.getScaleY(), p.getX(), p.getY()); if (DEBUG) System.out.println(" -> " + localAt); ec.getSingleItem(Transform.class).setTransform(me, localAt); } update(); } return true; } private void update() { for (IElement me : scaledElements.values()) painter.updateElement(node, me, KEY_SCALE_NODE, false); setDirty(); } private void cancel() { setDirty(); remove(); } private void commit() { Collection transformed = new ArrayList(); for (IElement e : scaledElements.values()) { Object obj = ElementUtils.getObject(e); if (obj instanceof Resource) { AffineTransform at = ElementUtils.getLocalTransform(e, new AffineTransform()); transformed.add( new TransformedObject((Resource) obj, at) ); } } Simantics.getSession().asyncRequest( ElementTransforms.setTransformRequest(transformed) ); setDirty(); remove(); } // private static AffineTransform uncheckedInverse(AffineTransform at) { // try { // return at.createInverse(); // } catch (NoninvertibleTransformException e) { // throw new RuntimeException(e); // } // } }