]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/TransformableSelectionNode.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / TransformableSelectionNode.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes;
13
14 import java.awt.BasicStroke;
15 import java.awt.Color;
16 import java.awt.Cursor;
17 import java.awt.Graphics2D;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Line2D;
20 import java.awt.geom.Path2D;
21 import java.awt.geom.Point2D;
22 import java.awt.geom.Rectangle2D;
23
24 import org.simantics.scenegraph.g2d.G2DNode;
25 import org.simantics.scenegraph.g2d.G2DParentNode;
26 import org.simantics.scenegraph.g2d.IG2DNode;
27 import org.simantics.scenegraph.g2d.IdentityAffineTransform;
28 import org.simantics.scenegraph.g2d.events.EventTypes;
29 import org.simantics.scenegraph.g2d.events.MouseEvent;
30 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
31 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
32 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
33 import org.simantics.scenegraph.utils.GeometryUtils;
34 import org.simantics.scenegraph.utils.NodeUtil;
35
36 /**
37  * @author J-P Laine
38  * @author Tuukka Lehtonen
39  */
40 public class TransformableSelectionNode extends G2DNode {
41
42     public static interface TransformCallback {
43         public void moved(Point2D delta);
44         public void resized(Point2D delta);
45     }
46
47     private static final long serialVersionUID = -2879575230419873230L;
48
49     private static final int HEADER_HEIGHT = 10;
50
51     public transient static final BasicStroke SELECTION_STROKE = new BasicStroke(1.0f,
52             BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE, 10.0f,
53             new float[] { 5.0f, 5.0f }, 0.0f);
54
55     protected Rectangle2D                     bounds           = null;
56     protected Color                           color            = null;
57     protected Boolean                         resizeable       = Boolean.FALSE;
58
59     protected double                          minWidth         = 7;
60     protected double                          minHeight        = 7;
61
62     protected transient Point2D               dragDelta         = null;
63     protected transient Point2D               orig              = null;
64     protected transient Boolean               resize            = null;
65
66     protected transient Point2D               temp              = new Point2D.Double();
67     protected transient Path2D                path              = new Path2D.Double();
68     protected transient Rectangle2D           rect              = new Rectangle2D.Double();
69
70     protected transient TransformCallback     transformCallback = null;
71
72     @Override
73     public void init() {
74         super.init();
75         addEventHandler(this);
76     }
77
78     @Override
79     public void cleanup() {
80         removeEventHandler(this);
81         super.cleanup();
82     }
83
84     @SyncField({"transform", "bounds", "color", "resizeable", "minWidth", "minHeight"})
85     public void init(AffineTransform transform, Rectangle2D bounds, Color color, boolean resizeable, double minWidth, double minHeight) {
86 //        System.out.println("init("+transform+", "+bounds+", "+color+", "+resizeable+")");
87         this.transform = transform;
88         this.bounds = bounds;
89         this.color = color;
90         this.resizeable = resizeable;
91         this.minWidth = minWidth;
92         this.minHeight = minHeight;
93     }
94
95     @SyncField({"transform", "bounds", "color", "resizeable"})
96     public void init(AffineTransform transform, Rectangle2D bounds, Color color, boolean resizeable) {
97 //        System.out.println("init("+transform+", "+bounds+", "+color+", "+resizeable+")");
98         this.transform = transform;
99         this.bounds = bounds;
100         this.color = color;
101         this.resizeable = resizeable;
102     }
103
104     @SyncField({"transform", "bounds", "color"})
105     public void init(AffineTransform transform, Rectangle2D bounds, Color color) {
106 //        System.out.println("init("+transform+", "+bounds+", "+color+")");
107         this.transform = transform;
108         this.bounds = bounds;
109         this.color = color;
110     }
111
112     @Override
113     public void render(Graphics2D g) {
114         if (bounds == null)
115             return;
116         AffineTransform ot = g.getTransform();
117
118         g.setColor(color);
119         g.transform(transform);
120
121         AffineTransform tx = g.getTransform();
122         //System.out.println("tx: " + tx);
123         double scale = GeometryUtils.getScale(tx);
124         //System.out.println("scale: " + scale);
125         double scaleRecip = 1.0 / scale;
126         //System.out.println("scale: " + scaleRecip);
127
128         BasicStroke scaledStroke = GeometryUtils.scaleStroke( SELECTION_STROKE, (float) scaleRecip);
129         g.setStroke(scaledStroke);
130
131         double padding = 0.0 * scaleRecip;
132         double paddingX = padding;
133         double paddingY = padding;
134
135         g.draw(new Rectangle2D.Double(bounds.getMinX() - paddingX, bounds.getMinY() - paddingY,
136                 bounds.getWidth() + 2.0*paddingX, bounds.getHeight() + 2.0*paddingY));
137
138         double right = (bounds.getMinX() - paddingX + bounds.getWidth() + 2.0*paddingX);
139         double bottom = (bounds.getMinY() - paddingY + bounds.getHeight() + 2.0*paddingY);
140
141         if (resizeable) {
142             Path2D corner = new Path2D.Double();
143             corner.moveTo(right-8-paddingX, bottom);
144             corner.lineTo(right, bottom - 8 - paddingY);
145             corner.lineTo(right, bottom);
146             corner.closePath();
147             g.setColor(new Color(20, 20, 20, 120));
148             g.fill(corner);
149
150             g.setColor(color);
151             g.draw(new Line2D.Double(right-8-paddingX, bottom, right, bottom - 8 - paddingY));
152         }
153
154         Rectangle2D header = new Rectangle2D.Double(bounds.getMinX() - paddingX, bounds.getMinY() - paddingY, bounds.getWidth() + 2.0*paddingX, HEADER_HEIGHT);
155         g.setColor(new Color(20, 20, 20, 120));
156         g.fill(header);
157
158         g.setColor(color);
159         g.draw(new Line2D.Double(bounds.getMinX(), bounds.getMinY()+HEADER_HEIGHT, right, bounds.getMinY()+HEADER_HEIGHT));
160
161         g.setTransform(ot);
162     }
163
164     @Override
165     public Rectangle2D getBoundsInLocal() {
166         return bounds;
167     }
168
169     public void setTransformCallback(TransformCallback transformCallback) {
170         this.transformCallback = transformCallback;
171     }
172
173     @ServerSide
174     protected void resized(Point2D size) {
175         if (transformCallback != null) {
176             transformCallback.resized(size);
177         }
178     }
179
180     @ServerSide
181     protected void moved(Point2D location) {
182         if (transformCallback != null) {
183             transformCallback.moved(location);
184         }
185     }
186
187     @Override
188     public boolean mouseMoved(MouseMovedEvent e) {
189         boolean consume = false;
190
191         Point2D scale = getScale(temp);
192         final double sx = scale.getX();
193         final double sy = scale.getY();
194
195         Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, temp);
196         final double mx = localPos.getX();
197         final double my = localPos.getY();
198
199         AffineTransform i = AffineTransform.getTranslateInstance(-transform.getTranslateX()*sx, -transform.getTranslateY()*sy);
200         Point2D p = i.transform(new Point2D.Double(sx*mx, sy*my), null);
201
202         boolean dragging = (e.buttons & MouseEvent.LEFT_MASK) != 0;
203
204         if (dragging && dragDelta != null) {
205             double x = (p.getX() - dragDelta.getX())/sx;// /transform.getScaleX();
206             double y = (p.getY() - dragDelta.getY())/sy;// /transform.getScaleY();
207             if (Boolean.TRUE.equals(resize)) {
208
209                 double width;
210                 double pointX;
211                 if (bounds.getWidth() + x < minWidth) {
212                     width = minWidth;
213                     pointX = dragDelta.getX();
214                 } else {
215                     width = bounds.getWidth() + x;
216                     pointX = p.getX();
217                 }
218
219                 double height;
220                 double pointY;
221                 if (bounds.getHeight() + y < minHeight) {
222                     height = minHeight;
223                     pointY = dragDelta.getY();
224                 } else {
225                     height = bounds.getHeight() + y;
226                     pointY = p.getY();
227                 }
228
229 //              System.out.println("bounds.getX()=" + bounds.getX() + " bounds.getY())=" + bounds.getY());
230 //              System.out.println("width=" + width + " height=" + height);
231
232                 bounds.setFrame(bounds.getX(), bounds.getY(), width, height);
233                 dragDelta = new Point2D.Double(pointX, pointY); // TODO ..
234
235             } else if (Boolean.FALSE.equals(resize)) {
236                 if (transform == IdentityAffineTransform.INSTANCE)
237                     transform = AffineTransform.getTranslateInstance(x, y);
238                 else
239                     transform.translate(x, y);
240             }
241
242             //dragDelta = new Point2D.Double(me.getPoint().getX(), me.getPoint().getY());
243             repaint();
244         } else {
245             final double paddingX = 0.0;
246             final double paddingY = 0.0;
247
248             Path2D corner = createCorner(path, bounds, paddingX, paddingY, sx, sy);
249             Rectangle2D header = createRectangle(rect, bounds, paddingX, paddingY, sx, sy);
250
251             if (corner.contains(p)) {
252                 setCursor(Cursor.HAND_CURSOR);
253                 //consume = true;
254             } else if (header.contains(p)) {
255                 setCursor(Cursor.HAND_CURSOR);
256                 //consume = true;
257             } else {
258                 setCursor(Cursor.DEFAULT_CURSOR);
259             }
260         }
261
262         return consume;
263     }
264
265     @Override
266     protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
267         boolean consume = false;
268
269         if (e.button == MouseEvent.LEFT_BUTTON && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK)) {
270             Point2D scale = getScale(temp);
271             final double sx = scale.getX();
272             final double sy = scale.getY();
273
274             Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, temp);
275             final double mx = localPos.getX();
276             final double my = localPos.getY();
277
278             AffineTransform i = AffineTransform.getTranslateInstance(-transform.getTranslateX()*sx, -transform.getTranslateY()*sy);
279             Point2D p = i.transform(new Point2D.Double(sx*mx, sy*my), null);
280
281             final double paddingX = 0.0;
282             final double paddingY = 0.0;
283
284             Path2D corner = createCorner(path, bounds, paddingX, paddingY, sx, sy);
285             Rectangle2D header = createRectangle(rect, bounds, paddingX, paddingY, sx, sy);
286
287             if (corner.contains(p)) {// me.getPoint().getX() > right-5-paddingX && me.getPoint().getY() > bottom - 5 - paddingY) {
288                 if (orig == null)
289                     orig = new Point2D.Double(bounds.getWidth(), bounds.getHeight());
290                 resize = Boolean.TRUE;
291                 setCursor(Cursor.SE_RESIZE_CURSOR);
292                 consume = true;
293             } else if (header.contains(p)) {// me.getPoint().getY() < bounds.getMinY()+8) {
294                 if (orig == null)
295                     orig = new Point2D.Double(transform.getTranslateX(), transform.getTranslateY());
296                 resize = Boolean.FALSE;
297                 setCursor(Cursor.MOVE_CURSOR);
298                 consume = true;
299             } else {
300                 resize = null;
301             }
302
303             dragDelta = new Point2D.Double(p.getX(), p.getY());
304
305             repaint();
306         }
307         return consume;
308     }
309
310     @Override
311     protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
312         if (orig != null) {
313             setCursor(Cursor.DEFAULT_CURSOR);
314             if (resize) {
315                 Point2D delta = new Point2D.Double(bounds.getWidth() - orig.getX(), bounds.getHeight() - orig.getY());
316                 resized(delta);
317             } else {
318                 Point2D delta = new Point2D.Double((transform.getTranslateX() - orig.getX()), (transform.getTranslateY() - orig.getY()));
319                 moved(delta);
320             }
321             orig = null;
322             return true;
323         }
324         return false;
325     }
326
327     @Override
328     public int getEventMask() {
329         return EventTypes.MouseButtonPressedMask | EventTypes.MouseButtonReleasedMask | EventTypes.MouseMovedMask;
330     }
331
332     private Point2D getScale(Point2D result) {
333         double sx = 1.0, sy = 1.0;
334         IG2DNode node = (IG2DNode) this.getParent();
335         while (node != null) {
336             sx *= node.getTransform().getScaleX();
337             sy *= node.getTransform().getScaleY();
338             // FIXME: it should be G2DParentNode but you can never be sure
339             node = (G2DParentNode) node.getParent();
340         }
341         result.setLocation(sx, sy);
342         return result;
343     }
344
345     private static Rectangle2D createRectangle(Rectangle2D result, Rectangle2D bounds,
346             double paddingX, double paddingY, double sx, double sy) {
347         result.setFrame(
348                 (bounds.getMinX() - paddingX)*sx,
349                 (bounds.getMinY() - paddingY)*sy,
350                 (bounds.getWidth() + 2.0 * paddingX) * sx,
351                 HEADER_HEIGHT*sy);
352         return result;
353     }
354
355     private static Path2D createCorner(Path2D result, Rectangle2D bounds,
356             double paddingX, double paddingY, double sx, double sy) {
357         final double right = (bounds.getMinX() - paddingX + bounds.getWidth() + 2.0 * paddingX);
358         final double bottom = (bounds.getMinY() - paddingY + bounds.getHeight() + 2.0 * paddingY);
359         result.reset();
360         result.moveTo((right - 8 - paddingX) * sx, bottom * sy);
361         result.lineTo(right * sx, (bottom - 8 - paddingY) * sy);
362         result.lineTo(right * sx, bottom * sy);
363         result.closePath();
364         return result;
365     }
366
367 }