]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/ResizeNode.java
Sync git svn branch with SVN repository r33269.
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / ResizeNode.java
1 package org.simantics.diagram.elements;\r
2 \r
3 import java.awt.BasicStroke;\r
4 import java.awt.Cursor;\r
5 import java.awt.Graphics2D;\r
6 import java.awt.Shape;\r
7 import java.awt.Stroke;\r
8 import java.awt.geom.AffineTransform;\r
9 import java.awt.geom.Point2D;\r
10 import java.awt.geom.Rectangle2D;\r
11 \r
12 import org.simantics.g2d.canvas.impl.CanvasContext;\r
13 import org.simantics.scenegraph.g2d.G2DNode;\r
14 import org.simantics.scenegraph.g2d.events.EventTypes;\r
15 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
16 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
17 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
18 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
19 import org.simantics.scenegraph.utils.NodeUtil;\r
20 \r
21 /**\r
22  * Invisible resize node for resizing rectangular elements\r
23  * \r
24  * @author Teemu Lempinen\r
25  *\r
26  */\r
27 public class ResizeNode extends G2DNode {\r
28         \r
29         private static final long serialVersionUID = 7997312998598071328L;\r
30 \r
31         \r
32         /**\r
33          * Interface for listeners listening resize events in nodes\r
34          * @author Teemu Lempinen\r
35          *\r
36          */\r
37         public interface ResizeListener {\r
38             \r
39             /**\r
40              * Triggered when a node has been resized\r
41              * @param newBounds new bounds for the node\r
42              */\r
43             public void elementResized(Rectangle2D newBounds, AffineTransform transform, boolean synchronizeToBackend);\r
44         }\r
45         \r
46         /**\r
47          * Enumeration for indicating which side of the resize bounds should affect translate\r
48          * properties. \r
49          * @author Teemu Lempinen\r
50          *\r
51          */\r
52         public enum TranslateEdge {\r
53             NONE, NORTH, SOUTH, EAST, WEST;\r
54         }\r
55         \r
56         \r
57         private boolean dragging = false;\r
58         private ResizeListener resizeListener;\r
59         private Rectangle2D bounds;\r
60         private Stroke stroke;\r
61         private TranslateEdge xTranslateEdge = TranslateEdge.WEST;\r
62         private TranslateEdge YTranslateEdge = TranslateEdge.NORTH;\r
63 \r
64         int cursor = Cursor.DEFAULT_CURSOR;\r
65         \r
66         /**\r
67          * Create a new Resize node with default border width (1)\r
68          */\r
69         public ResizeNode() {\r
70                 this(1);\r
71         }\r
72          \r
73         /**\r
74          * Create a new Resize node\r
75          * \r
76          * @param borderWidth Width of the border for handling mouse dragging\r
77          */\r
78         public ResizeNode(float borderWidth) {\r
79                 this.stroke = new BasicStroke(borderWidth);\r
80         }\r
81 \r
82         @PropertySetter("Bounds")\r
83         @SyncField("bounds")\r
84         public void setBounds(Rectangle2D bounds) {\r
85                 assert(bounds != null);\r
86                 this.bounds = bounds;\r
87         }\r
88 \r
89         @PropertySetter("stroke")\r
90         @SyncField("stroke")\r
91         public void setStroke(Stroke stroke) {\r
92                 this.stroke = stroke;\r
93         }\r
94 \r
95         /**\r
96          * Is dragging (resizing) active\r
97          * @return\r
98          */\r
99         public boolean dragging() {\r
100                 return dragging;\r
101         }\r
102 \r
103         /**\r
104          * Set a ResizeListener for this node\r
105          * @param listener ResizeListener\r
106          */\r
107         public void setResizeListener(ResizeListener listener) {\r
108                 this.resizeListener = listener;\r
109         }\r
110 \r
111         @Override\r
112         public void cleanup() {\r
113                 removeEventHandler(this);\r
114                 super.cleanup();\r
115         }\r
116 \r
117         @Override\r
118         public void init() {\r
119                 super.init();\r
120                 addEventHandler(this);\r
121         }\r
122 \r
123         /**\r
124          * Outline stroke shape of the bounds of this node\r
125          * \r
126          * @return Outline stroke shape of the bounds of this node\r
127          */\r
128         protected Shape getOutline() {\r
129                 return stroke.createStrokedShape(new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()));\r
130         }\r
131 \r
132         /**\r
133          * Dragging is started on mouse pressed, if mouse was pressed on the edge of bounds\r
134          */\r
135         @Override\r
136         protected boolean mouseButtonPressed(MouseButtonPressedEvent event) {\r
137                 if(bounds != null && NodeUtil.isSelected(this, 1)) {\r
138                         // get mouse position\r
139                         Point2D local = controlToLocal( event.controlPosition );\r
140                         local = parentToLocal(local);\r
141 \r
142                         // Get outline of this node\r
143                         Shape outline = getOutline();\r
144 \r
145                         if (outline.contains(local)) {\r
146                                 dragging = true;\r
147                                 return true;\r
148                         }\r
149                 }\r
150                 return super.mouseButtonPressed(event);\r
151         }\r
152 \r
153         /**\r
154          * Get resize cursor for the location where the drag started.\r
155          * \r
156          * @param local Point of origin for drag\r
157          * @return Cursor int\r
158          */\r
159         private int getCursorDirection(Point2D local) {\r
160                 float width = ((BasicStroke)stroke).getLineWidth();\r
161 \r
162                 // Check the direction of the resize\r
163                 int cursor = 0;\r
164                 if (local.getX() >= bounds.getX() - width / 2 && \r
165                                 local.getX() <= bounds.getX() + width / 2)\r
166                         // West side\r
167                         cursor = Cursor.W_RESIZE_CURSOR;\r
168                 else if  (local.getX() >= bounds.getMaxX() - width / 2 && \r
169                                 local.getX() <= bounds.getMaxX() + width / 2)\r
170                         // East size\r
171                         cursor = Cursor.E_RESIZE_CURSOR;\r
172 \r
173                 if(local.getY() >= bounds.getY() - width / 2 && \r
174                                 local.getY() <= bounds.getY() + width / 2) {\r
175                         // North side\r
176                         if(cursor == Cursor.W_RESIZE_CURSOR)\r
177                                 cursor = Cursor.NW_RESIZE_CURSOR;\r
178                         else if(cursor == Cursor.E_RESIZE_CURSOR)\r
179                                 cursor = Cursor.NE_RESIZE_CURSOR;\r
180                         else\r
181                                 cursor = Cursor.N_RESIZE_CURSOR;\r
182                 } else if(local.getY() >= bounds.getMaxY() - width / 2 && \r
183                                 local.getY() <= bounds.getMaxY() + width / 2) {\r
184                         // South side\r
185                         if(cursor == Cursor.W_RESIZE_CURSOR)\r
186                                 cursor = Cursor.SW_RESIZE_CURSOR;\r
187                         else if(cursor == Cursor.E_RESIZE_CURSOR)\r
188                                 cursor = Cursor.SE_RESIZE_CURSOR;\r
189                         else\r
190                                 cursor = cursor | Cursor.S_RESIZE_CURSOR;\r
191                 }\r
192                 return cursor;\r
193         }\r
194 \r
195         double dragTolerance = 0.5;\r
196         \r
197         @Override\r
198         protected boolean mouseMoved(MouseMovedEvent e) {\r
199                 if(dragging) {\r
200                         // If dragging is active and mouse is moved enough, resize the element\r
201                         Point2D local = controlToLocal( e.controlPosition );\r
202                         local = parentToLocal(local);\r
203 \r
204                         Rectangle2D bounds = getBoundsInLocal().getBounds2D();\r
205 \r
206                         if(Math.abs(bounds.getMaxX() - local.getX()) > dragTolerance ||\r
207                                         Math.abs(bounds.getMaxY() - local.getY()) > dragTolerance) {\r
208                                 resize(local, false);\r
209                         }\r
210                         return true;\r
211                 } else if(NodeUtil.isSelected(this, 1)){\r
212                         // Dragging is not active. Change mouse cursor if entered or exited border\r
213                         Point2D local = controlToLocal( e.controlPosition );\r
214                         local = parentToLocal(local);\r
215 \r
216                         Shape outline = getOutline();\r
217                         if (outline.contains(local)) {\r
218                                 cursor = getCursorDirection(local);\r
219                                 CanvasContext ctx = (CanvasContext)e.getContext();\r
220                                 ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(cursor));\r
221                         } else if(cursor != 0) {\r
222                                 cursor = 0;\r
223                                 CanvasContext ctx = (CanvasContext)e.getContext();\r
224                                 ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));\r
225                         }\r
226 \r
227                 }\r
228                 return super.mouseMoved(e);\r
229         }\r
230 \r
231         @Override\r
232         protected boolean mouseDragged(MouseDragBegin e) {\r
233                 if(dragging) {\r
234                         return true; // Consume event\r
235                 } else {\r
236                         return false;\r
237                 }\r
238         }\r
239         \r
240         private static double minSize = 5; // Minimum size for an element\r
241 \r
242         private void resize(Point2D local, boolean synchronize) {\r
243                 Rectangle2D bounds = getBoundsInLocal().getBounds2D();\r
244                 double x = bounds.getX();\r
245                 double y = bounds.getY();\r
246                 double w = bounds.getWidth();\r
247                 double h = bounds.getHeight();\r
248                 double dx = 0, dy = 0;\r
249 \r
250                 /*\r
251                  * Resize to east and north:\r
252                  * \r
253                  * Move x and y coordinates and resize to get maxX and maxY to stay in their place\r
254                  */\r
255                 if(cursor == Cursor.W_RESIZE_CURSOR || cursor == Cursor.NW_RESIZE_CURSOR || cursor == Cursor.SW_RESIZE_CURSOR) {\r
256                         double dw = local.getX() - x;\r
257                         if(w - dw < minSize)\r
258                                 dw = w - minSize;\r
259                         w = w - dw;\r
260                         \r
261                         \r
262                         if(TranslateEdge.WEST.equals(xTranslateEdge))\r
263                             dx = dw;\r
264                 } \r
265                 \r
266                 if(cursor == Cursor.N_RESIZE_CURSOR || cursor == Cursor.NW_RESIZE_CURSOR || cursor == Cursor.NE_RESIZE_CURSOR ) {\r
267                         double dh = local.getY() - y;\r
268                         if(h - dh < minSize)\r
269                                 dh = h - minSize;\r
270                         h = h - dh;\r
271                         \r
272                         if(TranslateEdge.NORTH.equals(YTranslateEdge))\r
273                             dy = dh;\r
274                 } \r
275                 \r
276                 /*\r
277                  * Resize to west and south:\r
278                  * \r
279                  * Adjust width and height\r
280                  */\r
281                 if(cursor == Cursor.E_RESIZE_CURSOR || cursor == Cursor.NE_RESIZE_CURSOR || cursor == Cursor.SE_RESIZE_CURSOR) {\r
282                         double dw = local.getX() - bounds.getMaxX();\r
283                         w = w + dw > minSize ? w + dw : minSize;\r
284                         \r
285                         if(TranslateEdge.EAST.equals(xTranslateEdge))\r
286                             dx = w - bounds.getWidth();\r
287                 }\r
288 \r
289                 if(cursor == Cursor.S_RESIZE_CURSOR || cursor == Cursor.SW_RESIZE_CURSOR || cursor == Cursor.SE_RESIZE_CURSOR) {\r
290                     double dh = local.getY() - bounds.getMaxY();\r
291                     h = h + dh > minSize ? h + dh : minSize;\r
292 \r
293                     if(TranslateEdge.SOUTH.equals(YTranslateEdge))\r
294                         dy = h - bounds.getHeight();\r
295                 }\r
296 \r
297                 /*\r
298                  *  Set bounds and transform to the element before calling resize listener.\r
299                  *  This prevents unwanted movement due to unsynchronized transform and bounds.\r
300                  */\r
301                 this.bounds.setRect(x, y, w, h);\r
302 \r
303         AffineTransform at = new AffineTransform();\r
304                 at.translate(dx, dy);\r
305                 if(resizeListener != null)\r
306                         resizeListener.elementResized(this.bounds, at, synchronize);\r
307         }\r
308 \r
309         @Override\r
310         protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
311                 if(dragging) {\r
312                         // Stop resizing and set the size to its final state. \r
313                         Point2D local = controlToLocal( e.controlPosition );\r
314                         local = parentToLocal(local);\r
315 \r
316                         resize(local, true);\r
317                         dragging = false;\r
318 \r
319                         // Revert cursor to normal.\r
320                         CanvasContext ctx = (CanvasContext)e.getContext();\r
321                         cursor = Cursor.DEFAULT_CURSOR;\r
322                         ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(cursor));\r
323                 }\r
324                 return super.mouseButtonReleased(e);\r
325         }\r
326 \r
327         @Override\r
328         public int getEventMask() {\r
329                 return super.getEventMask() | EventTypes.MouseButtonPressedMask\r
330                                 | EventTypes.MouseMovedMask\r
331                                 | EventTypes.MouseButtonReleasedMask\r
332                                 ;\r
333         }\r
334 \r
335         @Override\r
336         public Rectangle2D getBoundsInLocal() {\r
337                 if(bounds == null) return null;\r
338                 return bounds.getBounds2D();\r
339         }\r
340 \r
341         @Override\r
342         public void render(Graphics2D g2d) {\r
343                 // Do not draw anything\r
344         }\r
345         \r
346         /**\r
347          * Set which edge should affect X-translation \r
348          * @param xTranslateEdge TranslateEdge.NONE, EAST or WEST\r
349          */\r
350         public void setxTranslateEdge(TranslateEdge xTranslateEdge) {\r
351         this.xTranslateEdge = xTranslateEdge;\r
352     }\r
353         \r
354            /**\r
355      * Set which edge should affect Y-translation \r
356      * @param yTranslateEdge TranslateEdge.NONE, SOUTH or NORTH\r
357      */\r
358         public void setYTranslateEdge(TranslateEdge yTranslateEdge) {\r
359         YTranslateEdge = yTranslateEdge;\r
360     }\r
361 }\r