]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/TransformableSelectionNode.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / TransformableSelectionNode.java
diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/TransformableSelectionNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/TransformableSelectionNode.java
new file mode 100644 (file)
index 0000000..8bf623c
--- /dev/null
@@ -0,0 +1,367 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.g2d.nodes;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Cursor;\r
+import java.awt.Graphics2D;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Line2D;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.scenegraph.g2d.G2DNode;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.IG2DNode;\r
+import org.simantics.scenegraph.g2d.IdentityAffineTransform;\r
+import org.simantics.scenegraph.g2d.events.EventTypes;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
+import org.simantics.scenegraph.utils.GeometryUtils;\r
+import org.simantics.scenegraph.utils.NodeUtil;\r
+\r
+/**\r
+ * @author J-P Laine\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class TransformableSelectionNode extends G2DNode {\r
+\r
+    public static interface TransformCallback {\r
+        public void moved(Point2D delta);\r
+        public void resized(Point2D delta);\r
+    }\r
+\r
+    private static final long serialVersionUID = -2879575230419873230L;\r
+\r
+    private static final int HEADER_HEIGHT = 10;\r
+\r
+    public transient static final BasicStroke SELECTION_STROKE = new BasicStroke(1.0f,\r
+            BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE, 10.0f,\r
+            new float[] { 5.0f, 5.0f }, 0.0f);\r
+\r
+    protected Rectangle2D                     bounds           = null;\r
+    protected Color                           color            = null;\r
+    protected Boolean                         resizeable       = Boolean.FALSE;\r
+\r
+    protected double                          minWidth         = 7;\r
+    protected double                          minHeight        = 7;\r
+\r
+    protected transient Point2D               dragDelta         = null;\r
+    protected transient Point2D               orig              = null;\r
+    protected transient Boolean               resize            = null;\r
+\r
+    protected transient Point2D               temp              = new Point2D.Double();\r
+    protected transient Path2D                path              = new Path2D.Double();\r
+    protected transient Rectangle2D           rect              = new Rectangle2D.Double();\r
+\r
+    protected transient TransformCallback     transformCallback = null;\r
+\r
+    @Override\r
+    public void init() {\r
+        super.init();\r
+        addEventHandler(this);\r
+    }\r
+\r
+    @Override\r
+    public void cleanup() {\r
+        removeEventHandler(this);\r
+        super.cleanup();\r
+    }\r
+\r
+    @SyncField({"transform", "bounds", "color", "resizeable", "minWidth", "minHeight"})\r
+    public void init(AffineTransform transform, Rectangle2D bounds, Color color, boolean resizeable, double minWidth, double minHeight) {\r
+//        System.out.println("init("+transform+", "+bounds+", "+color+", "+resizeable+")");\r
+        this.transform = transform;\r
+        this.bounds = bounds;\r
+        this.color = color;\r
+        this.resizeable = resizeable;\r
+        this.minWidth = minWidth;\r
+        this.minHeight = minHeight;\r
+    }\r
+\r
+    @SyncField({"transform", "bounds", "color", "resizeable"})\r
+    public void init(AffineTransform transform, Rectangle2D bounds, Color color, boolean resizeable) {\r
+//        System.out.println("init("+transform+", "+bounds+", "+color+", "+resizeable+")");\r
+        this.transform = transform;\r
+        this.bounds = bounds;\r
+        this.color = color;\r
+        this.resizeable = resizeable;\r
+    }\r
+\r
+    @SyncField({"transform", "bounds", "color"})\r
+    public void init(AffineTransform transform, Rectangle2D bounds, Color color) {\r
+//        System.out.println("init("+transform+", "+bounds+", "+color+")");\r
+        this.transform = transform;\r
+        this.bounds = bounds;\r
+        this.color = color;\r
+    }\r
+\r
+    @Override\r
+    public void render(Graphics2D g) {\r
+        if (bounds == null)\r
+            return;\r
+        AffineTransform ot = g.getTransform();\r
+\r
+        g.setColor(color);\r
+        g.transform(transform);\r
+\r
+        AffineTransform tx = g.getTransform();\r
+        //System.out.println("tx: " + tx);\r
+        double scale = GeometryUtils.getScale(tx);\r
+        //System.out.println("scale: " + scale);\r
+        double scaleRecip = 1.0 / scale;\r
+        //System.out.println("scale: " + scaleRecip);\r
+\r
+        BasicStroke scaledStroke = GeometryUtils.scaleStroke( SELECTION_STROKE, (float) scaleRecip);\r
+        g.setStroke(scaledStroke);\r
+\r
+        double padding = 0.0 * scaleRecip;\r
+        double paddingX = padding;\r
+        double paddingY = padding;\r
+\r
+        g.draw(new Rectangle2D.Double(bounds.getMinX() - paddingX, bounds.getMinY() - paddingY,\r
+                bounds.getWidth() + 2.0*paddingX, bounds.getHeight() + 2.0*paddingY));\r
+\r
+        double right = (bounds.getMinX() - paddingX + bounds.getWidth() + 2.0*paddingX);\r
+        double bottom = (bounds.getMinY() - paddingY + bounds.getHeight() + 2.0*paddingY);\r
+\r
+        if (resizeable) {\r
+            Path2D corner = new Path2D.Double();\r
+            corner.moveTo(right-8-paddingX, bottom);\r
+            corner.lineTo(right, bottom - 8 - paddingY);\r
+            corner.lineTo(right, bottom);\r
+            corner.closePath();\r
+            g.setColor(new Color(20, 20, 20, 120));\r
+            g.fill(corner);\r
+\r
+            g.setColor(color);\r
+            g.draw(new Line2D.Double(right-8-paddingX, bottom, right, bottom - 8 - paddingY));\r
+        }\r
+\r
+        Rectangle2D header = new Rectangle2D.Double(bounds.getMinX() - paddingX, bounds.getMinY() - paddingY, bounds.getWidth() + 2.0*paddingX, HEADER_HEIGHT);\r
+        g.setColor(new Color(20, 20, 20, 120));\r
+        g.fill(header);\r
+\r
+        g.setColor(color);\r
+        g.draw(new Line2D.Double(bounds.getMinX(), bounds.getMinY()+HEADER_HEIGHT, right, bounds.getMinY()+HEADER_HEIGHT));\r
+\r
+        g.setTransform(ot);\r
+    }\r
+\r
+    @Override\r
+    public Rectangle2D getBoundsInLocal() {\r
+        return bounds;\r
+    }\r
+\r
+    public void setTransformCallback(TransformCallback transformCallback) {\r
+        this.transformCallback = transformCallback;\r
+    }\r
+\r
+    @ServerSide\r
+    protected void resized(Point2D size) {\r
+        if (transformCallback != null) {\r
+            transformCallback.resized(size);\r
+        }\r
+    }\r
+\r
+    @ServerSide\r
+    protected void moved(Point2D location) {\r
+        if (transformCallback != null) {\r
+            transformCallback.moved(location);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public boolean mouseMoved(MouseMovedEvent e) {\r
+        boolean consume = false;\r
+\r
+        Point2D scale = getScale(temp);\r
+        final double sx = scale.getX();\r
+        final double sy = scale.getY();\r
+\r
+        Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, temp);\r
+        final double mx = localPos.getX();\r
+        final double my = localPos.getY();\r
+\r
+        AffineTransform i = AffineTransform.getTranslateInstance(-transform.getTranslateX()*sx, -transform.getTranslateY()*sy);\r
+        Point2D p = i.transform(new Point2D.Double(sx*mx, sy*my), null);\r
+\r
+        boolean dragging = (e.buttons & MouseEvent.LEFT_MASK) != 0;\r
+\r
+        if (dragging && dragDelta != null) {\r
+            double x = (p.getX() - dragDelta.getX())/sx;// /transform.getScaleX();\r
+            double y = (p.getY() - dragDelta.getY())/sy;// /transform.getScaleY();\r
+            if (Boolean.TRUE.equals(resize)) {\r
+\r
+                double width;\r
+                double pointX;\r
+                if (bounds.getWidth() + x < minWidth) {\r
+                    width = minWidth;\r
+                    pointX = dragDelta.getX();\r
+                } else {\r
+                    width = bounds.getWidth() + x;\r
+                    pointX = p.getX();\r
+                }\r
+\r
+                double height;\r
+                double pointY;\r
+                if (bounds.getHeight() + y < minHeight) {\r
+                    height = minHeight;\r
+                    pointY = dragDelta.getY();\r
+                } else {\r
+                    height = bounds.getHeight() + y;\r
+                    pointY = p.getY();\r
+                }\r
+\r
+//              System.out.println("bounds.getX()=" + bounds.getX() + " bounds.getY())=" + bounds.getY());\r
+//              System.out.println("width=" + width + " height=" + height);\r
+\r
+                bounds.setFrame(bounds.getX(), bounds.getY(), width, height);\r
+                dragDelta = new Point2D.Double(pointX, pointY); // TODO ..\r
+\r
+            } else if (Boolean.FALSE.equals(resize)) {\r
+                if (transform == IdentityAffineTransform.INSTANCE)\r
+                    transform = AffineTransform.getTranslateInstance(x, y);\r
+                else\r
+                    transform.translate(x, y);\r
+            }\r
+\r
+            //dragDelta = new Point2D.Double(me.getPoint().getX(), me.getPoint().getY());\r
+            repaint();\r
+        } else {\r
+            final double paddingX = 0.0;\r
+            final double paddingY = 0.0;\r
+\r
+            Path2D corner = createCorner(path, bounds, paddingX, paddingY, sx, sy);\r
+            Rectangle2D header = createRectangle(rect, bounds, paddingX, paddingY, sx, sy);\r
+\r
+            if (corner.contains(p)) {\r
+                setCursor(Cursor.HAND_CURSOR);\r
+                //consume = true;\r
+            } else if (header.contains(p)) {\r
+                setCursor(Cursor.HAND_CURSOR);\r
+                //consume = true;\r
+            } else {\r
+                setCursor(Cursor.DEFAULT_CURSOR);\r
+            }\r
+        }\r
+\r
+        return consume;\r
+    }\r
+\r
+    @Override\r
+    protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {\r
+        boolean consume = false;\r
+\r
+        if (e.button == MouseEvent.LEFT_BUTTON && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK)) {\r
+            Point2D scale = getScale(temp);\r
+            final double sx = scale.getX();\r
+            final double sy = scale.getY();\r
+\r
+            Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, temp);\r
+            final double mx = localPos.getX();\r
+            final double my = localPos.getY();\r
+\r
+            AffineTransform i = AffineTransform.getTranslateInstance(-transform.getTranslateX()*sx, -transform.getTranslateY()*sy);\r
+            Point2D p = i.transform(new Point2D.Double(sx*mx, sy*my), null);\r
+\r
+            final double paddingX = 0.0;\r
+            final double paddingY = 0.0;\r
+\r
+            Path2D corner = createCorner(path, bounds, paddingX, paddingY, sx, sy);\r
+            Rectangle2D header = createRectangle(rect, bounds, paddingX, paddingY, sx, sy);\r
+\r
+            if (corner.contains(p)) {// me.getPoint().getX() > right-5-paddingX && me.getPoint().getY() > bottom - 5 - paddingY) {\r
+                if (orig == null)\r
+                    orig = new Point2D.Double(bounds.getWidth(), bounds.getHeight());\r
+                resize = Boolean.TRUE;\r
+                setCursor(Cursor.SE_RESIZE_CURSOR);\r
+                consume = true;\r
+            } else if (header.contains(p)) {// me.getPoint().getY() < bounds.getMinY()+8) {\r
+                if (orig == null)\r
+                    orig = new Point2D.Double(transform.getTranslateX(), transform.getTranslateY());\r
+                resize = Boolean.FALSE;\r
+                setCursor(Cursor.MOVE_CURSOR);\r
+                consume = true;\r
+            } else {\r
+                resize = null;\r
+            }\r
+\r
+            dragDelta = new Point2D.Double(p.getX(), p.getY());\r
+\r
+            repaint();\r
+        }\r
+        return consume;\r
+    }\r
+\r
+    @Override\r
+    protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
+        if (orig != null) {\r
+            setCursor(Cursor.DEFAULT_CURSOR);\r
+            if (resize) {\r
+                Point2D delta = new Point2D.Double(bounds.getWidth() - orig.getX(), bounds.getHeight() - orig.getY());\r
+                resized(delta);\r
+            } else {\r
+                Point2D delta = new Point2D.Double((transform.getTranslateX() - orig.getX()), (transform.getTranslateY() - orig.getY()));\r
+                moved(delta);\r
+            }\r
+            orig = null;\r
+            return true;\r
+        }\r
+        return false;\r
+    }\r
+\r
+    @Override\r
+    public int getEventMask() {\r
+        return EventTypes.MouseButtonPressedMask | EventTypes.MouseButtonReleasedMask | EventTypes.MouseMovedMask;\r
+    }\r
+\r
+    private Point2D getScale(Point2D result) {\r
+        double sx = 1.0, sy = 1.0;\r
+        IG2DNode node = (IG2DNode) this.getParent();\r
+        while (node != null) {\r
+            sx *= node.getTransform().getScaleX();\r
+            sy *= node.getTransform().getScaleY();\r
+            // FIXME: it should be G2DParentNode but you can never be sure\r
+            node = (G2DParentNode) node.getParent();\r
+        }\r
+        result.setLocation(sx, sy);\r
+        return result;\r
+    }\r
+\r
+    private static Rectangle2D createRectangle(Rectangle2D result, Rectangle2D bounds,\r
+            double paddingX, double paddingY, double sx, double sy) {\r
+        result.setFrame(\r
+                (bounds.getMinX() - paddingX)*sx,\r
+                (bounds.getMinY() - paddingY)*sy,\r
+                (bounds.getWidth() + 2.0 * paddingX) * sx,\r
+                HEADER_HEIGHT*sy);\r
+        return result;\r
+    }\r
+\r
+    private static Path2D createCorner(Path2D result, Rectangle2D bounds,\r
+            double paddingX, double paddingY, double sx, double sy) {\r
+        final double right = (bounds.getMinX() - paddingX + bounds.getWidth() + 2.0 * paddingX);\r
+        final double bottom = (bounds.getMinY() - paddingY + bounds.getHeight() + 2.0 * paddingY);\r
+        result.reset();\r
+        result.moveTo((right - 8 - paddingX) * sx, bottom * sy);\r
+        result.lineTo(right * sx, (bottom - 8 - paddingY) * sy);\r
+        result.lineTo(right * sx, bottom * sy);\r
+        result.closePath();\r
+        return result;\r
+    }\r
+\r
+}\r