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