/*******************************************************************************
* 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);
}
}