/******************************************************************************* * 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.g2d.participant; import static org.simantics.g2d.canvas.Hints.KEY_CANVAS_TRANSFORM; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.simantics.g2d.canvas.Hints; import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant; import org.simantics.g2d.canvas.impl.HintReflection.HintListener; import org.simantics.g2d.scenegraph.SceneGraphConstants; import org.simantics.g2d.utils.GeometryUtils; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.page.MarginUtils.Margins; /** * This class contains utilities for coordinate conversions between control * and canvas. * * @author Toni Kalajainen * @author J-P Laine * @author Tuukka Lehtonen */ public class TransformUtil extends AbstractCanvasParticipant { /** Set dirty if transform changes */ @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM") public void transformChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { if (oldValue != null && oldValue.equals(newValue)) return; //System.out.println("TransformUtil: CANVAS TRANSFORM CHANGED TO " + newValue); setDirty(); } /** * Get viewport rectangle. * Transforms controlRect to area on canvas. * * @param controlRect rectangle on control * @return area on canvas */ public Path2D toCanvasArea(Rectangle2D controlRect) { Point2D p[] = toCanvasCorners(controlRect); Path2D path = new Path2D.Double(); path.moveTo(p[0].getX(), p[0].getY()); path.lineTo(p[1].getX(), p[1].getY()); path.lineTo(p[2].getX(), p[2].getY()); path.lineTo(p[3].getX(), p[3].getY()); path.closePath(); return path; } /** * Returns 4 canvas corders in order: top-left, top-right, bottom-right, bottom-left * @param controlRect rectangle on control * @return area on canvas */ public Point2D[] toCanvasCorners(Rectangle2D controlRect) { // at·control point = canvas point AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (at==null) at = new AffineTransform(); try { at = at.createInverse(); } catch (NoninvertibleTransformException e) { } // Get corners Point2D p1 = new Point2D.Double(controlRect.getMinX(), controlRect.getMinY()); Point2D p2 = new Point2D.Double(controlRect.getMaxX(), controlRect.getMinY()); Point2D p3 = new Point2D.Double(controlRect.getMaxX(), controlRect.getMaxY()); Point2D p4 = new Point2D.Double(controlRect.getMinX(), controlRect.getMaxY()); // Convert corners at.transform(p1, p1); at.transform(p2, p2); at.transform(p3, p3); at.transform(p4, p4); return new Point2D[] {p1, p2, p3, p4}; } /** * Converts control rectangle to a rectangle on canvas that contains control rect. * @param controlRect rectangle on control * @return rectangle on canvas that contains control rect */ public Rectangle2D toCanvasRect(Rectangle2D controlRect) { AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (at==null) return (Rectangle2D) controlRect.clone(); Path2D path = toCanvasArea(controlRect); return path.getBounds2D(); } /** * Transforms from canvas coordinate system to canvas coordinate system (also diagram) * @param g2d */ public void controlToCanvas(Graphics2D g2d) { AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (at==null) return; g2d.transform(at); } /** * Transforms coordinate system of g2d from canvas to control * @param g2d */ public void canvasToControl(Graphics2D g2d) { AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (at==null) return; try { g2d.transform(at.createInverse()); } catch (NoninvertibleTransformException e) { } } /** * Convert control point to canvas point * @param g2d */ public Point2D controlToCanvas(Point2D control, Point2D canvas) { AffineTransform at = getInverseTransform(); if (canvas==null) canvas = new Point2D.Double(); if (at==null) { canvas.setLocation(control); return canvas; } at.transform(control, canvas); return canvas; } /** * Convert canvas point to control * @param g2d */ public Point2D canvasToControl(Point2D canvas, Point2D control) { AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (control==null) control = new Point2D.Double(); if (at==null) { control.setLocation(canvas); return canvas; } at.transform(canvas, control); return control; } /** * Convert canvas vector to control vector * @param g2d */ public Point2D canvasVectorToControlVector(Point2D canvas, Point2D control) { AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (control==null) control = new Point2D.Double(); if (at==null) { control.setLocation(canvas); return canvas; } double x = canvas.getX(); double y = canvas.getY(); double m00 = at.getScaleX(); double m11 = at.getScaleY(); double m01 = at.getShearX(); double m10 = at.getShearY(); control.setLocation( x * m00 + y * m01, x * m10 + y * m11 ); return control; } /** * Convert control vector to canvas vector * @param g2d */ public Point2D controlVectorToCanvasVector(Point2D control, Point2D canvas) { AffineTransform at = getInverseTransform(); if (canvas==null) canvas = new Point2D.Double(); if (at==null) { canvas.setLocation(control); return canvas; } double x = control.getX(); double y = control.getY(); double m00 = at.getScaleX(); double m11 = at.getScaleY(); double m01 = at.getShearX(); double m10 = at.getShearY(); canvas.setLocation( x * m00 + y * m01, x * m10 + y * m11 ); return canvas; } /** * Get a transform that converts * diagram coordinates to control coordinates. * * @return transform a clone */ public AffineTransform getTransform() { AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (at==null) return new AffineTransform(); return (AffineTransform) at.clone(); } /** * Get a transform that converts * diagram vector to control vector. * * @return transform a clone */ public AffineTransform getVectorTransform() { AffineTransform at = getHint(KEY_CANVAS_TRANSFORM); if (at==null) return new AffineTransform(); double m00 = at.getScaleX(); double m11 = at.getScaleY(); double m01 = at.getShearX(); double m10 = at.getShearY(); return new AffineTransform(m00, m10, m01, m11, 0, 0); } /** * Transforms control coordinates to diagram coordinates. * * @return inverted transform or null if inversion failed */ public AffineTransform getInverseTransform() { AffineTransform at = getTransform(); if (at==null) return null; try { at = at.createInverse(); return at; } catch (NoninvertibleTransformException e) { return null; } } /** * Tries to invert the specified {@link AffineTransform}. * * @parameter at the transform to invert * @return inverted transform or null if inversion failed */ public AffineTransform getInverseTransform(AffineTransform at) { if (at == null) return null; try { return at.createInverse(); } catch (NoninvertibleTransformException e) { return null; } } /** * Transforms control vector to diagram vector * @return a clone */ public AffineTransform getInverseVectorTransform() { AffineTransform at = getTransform(); if (at==null) return null; try { at = at.createInverse(); double m00 = at.getScaleX(); double m11 = at.getScaleY(); double m01 = at.getShearX(); double m10 = at.getShearY(); return new AffineTransform(m00, m10, m01, m11, 0, 0); } catch (NoninvertibleTransformException e) { return null; } } public void setTransform(AffineTransform at) { assert(at.getShearX()!=Double.POSITIVE_INFINITY && at.getShearX()!=Double.NEGATIVE_INFINITY); assert(at.getShearY()!=Double.POSITIVE_INFINITY && at.getShearY()!=Double.NEGATIVE_INFINITY); AffineTransform old = getHint(KEY_CANVAS_TRANSFORM); if(at.equals(old) == false) { // Send event only if the transform has really changed setHint(KEY_CANVAS_TRANSFORM, at); IG2DNode node = NodeUtil.findNodeById(getContext().getSceneGraph(), SceneGraphConstants.NAVIGATION_NODE_PATH); if (node != null) // This is not in hintListener, because we don't want to update // transform if it already came for scenegraph. node.setTransform(at); } } public void zoom(double scaleFactor) { if (scaleFactor==1.0) return; AffineTransform diagramToControl = getTransform(); diagramToControl.scale(scaleFactor, scaleFactor); setTransform(diagramToControl); } public void rotate(Point2D centerPointCanvas, double angle) { if (angle==0.0) return; // and modulos too AffineTransform diagramToControl = getTransform(); diagramToControl.rotate(-angle, centerPointCanvas.getX(), centerPointCanvas.getY()); setTransform(diagramToControl); } public void restoreOrientation(Point2D centerPointCanvas) { AffineTransform res = new AffineTransform(); AffineTransform at = getTransform(); double m01 = at.getShearX(); double m11 = at.getScaleY(); double theta = Math.atan2(m01, m11); at.rotate(-theta, centerPointCanvas.getX(), centerPointCanvas.getY()); res.translate(at.getTranslateX(), at.getTranslateY()); res.scale(at.getScaleX(), at.getScaleY()); setTransform(res); } /** * Get rotate in radians. * @return */ public double getRotate() { AffineTransform at = getTransform(); double m01 = at.getShearX(); double m11 = at.getScaleY(); return Math.atan2(m01, m11); } /** * Zoom the view port * @param scaleFactor amount to zoom * @param aroundPoint The center point of zoom in diagram coordinates */ public void zoomAroundDiagramPoint(double scaleFactor, Point2D aroundPoint) { AffineTransform diagramToControl = getTransform(); // convert to control point aroundPoint = diagramToControl.transform(aroundPoint, new Point2D.Double()); zoomAroundControlPoint(scaleFactor, aroundPoint); } /** * Zoom the view port * @param scaleFactor amount to zoom * @param centerPoint the control point to zoom around */ public void zoomAroundControlPoint(double scaleFactor, Point2D centerPoint) { if (scaleFactor==1.0) return; AffineTransform diagramToControl = getTransform(); try { Point2D pointBeforeScale = diagramToControl.inverseTransform(centerPoint, new Point2D.Double()); diagramToControl.scale(scaleFactor, scaleFactor); Point2D pointAfterScale = diagramToControl.inverseTransform(centerPoint, new Point2D.Double()); double _dx = pointAfterScale.getX() - pointBeforeScale.getX(); double _dy = pointAfterScale.getY() - pointBeforeScale.getY(); diagramToControl.translate(_dx, _dy); } catch (NoninvertibleTransformException e1) { } setTransform(diagramToControl); } /** * Transform the viewport. The transform values are in diagram coordinates * @param dx * @param dy */ public void translateWithCanvasCoordinates(double dx, double dy) { AffineTransform controlToDiagram = getTransform(); controlToDiagram.translate(dx, dy); setTransform(controlToDiagram); } /** * Transform the viewport. The transform values are in control coordinates * @param dx * @param dy */ public void translateWithControlCoordinates(Point2D offset) { if (offset.getX()==0 && offset.getY()==0) return; AffineTransform at = getInverseTransform(); offset = at.transform(offset, new Point2D.Double()); translateWithCanvasCoordinates(offset.getX()-at.getTranslateX(), offset.getY()-at.getTranslateY()); } /** * Get scale ratio between control and canvas * @param pt * @return */ public Point2D getScale(Point2D pt) { AffineTransform at = getTransform(); return GeometryUtils.getScaleXY(at, pt); } /** * Modifies transform to make canvas area fully visible. * @param controlArea * @param diagramArea * @param margins margins */ public void fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins) { AffineTransform fitTx = org.simantics.scenegraph.utils.GeometryUtils.fitArea(controlArea, diagramArea, margins); //System.out.println("fitArea(" + controlArea + ", " + diagramArea + ", " + margins + "): " + fitTx); setTransform(fitTx); } }