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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.g2d.nodes;
\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
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
38 * @author Tuukka Lehtonen
\r
40 public class TransformableSelectionNode extends G2DNode {
\r
42 public static interface TransformCallback {
\r
43 public void moved(Point2D delta);
\r
44 public void resized(Point2D delta);
\r
47 private static final long serialVersionUID = -2879575230419873230L;
\r
49 private static final int HEADER_HEIGHT = 10;
\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
55 protected Rectangle2D bounds = null;
\r
56 protected Color color = null;
\r
57 protected Boolean resizeable = Boolean.FALSE;
\r
59 protected double minWidth = 7;
\r
60 protected double minHeight = 7;
\r
62 protected transient Point2D dragDelta = null;
\r
63 protected transient Point2D orig = null;
\r
64 protected transient Boolean resize = null;
\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
70 protected transient TransformCallback transformCallback = null;
\r
73 public void init() {
\r
75 addEventHandler(this);
\r
79 public void cleanup() {
\r
80 removeEventHandler(this);
\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
90 this.resizeable = resizeable;
\r
91 this.minWidth = minWidth;
\r
92 this.minHeight = minHeight;
\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
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
113 public void render(Graphics2D g) {
\r
114 if (bounds == null)
\r
116 AffineTransform ot = g.getTransform();
\r
119 g.transform(transform);
\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
128 BasicStroke scaledStroke = GeometryUtils.scaleStroke( SELECTION_STROKE, (float) scaleRecip);
\r
129 g.setStroke(scaledStroke);
\r
131 double padding = 0.0 * scaleRecip;
\r
132 double paddingX = padding;
\r
133 double paddingY = padding;
\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
138 double right = (bounds.getMinX() - paddingX + bounds.getWidth() + 2.0*paddingX);
\r
139 double bottom = (bounds.getMinY() - paddingY + bounds.getHeight() + 2.0*paddingY);
\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
151 g.draw(new Line2D.Double(right-8-paddingX, bottom, right, bottom - 8 - paddingY));
\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
159 g.draw(new Line2D.Double(bounds.getMinX(), bounds.getMinY()+HEADER_HEIGHT, right, bounds.getMinY()+HEADER_HEIGHT));
\r
161 g.setTransform(ot);
\r
165 public Rectangle2D getBoundsInLocal() {
\r
169 public void setTransformCallback(TransformCallback transformCallback) {
\r
170 this.transformCallback = transformCallback;
\r
174 protected void resized(Point2D size) {
\r
175 if (transformCallback != null) {
\r
176 transformCallback.resized(size);
\r
181 protected void moved(Point2D location) {
\r
182 if (transformCallback != null) {
\r
183 transformCallback.moved(location);
\r
188 public boolean mouseMoved(MouseMovedEvent e) {
\r
189 boolean consume = false;
\r
191 Point2D scale = getScale(temp);
\r
192 final double sx = scale.getX();
\r
193 final double sy = scale.getY();
\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
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
202 boolean dragging = (e.buttons & MouseEvent.LEFT_MASK) != 0;
\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
211 if (bounds.getWidth() + x < minWidth) {
\r
213 pointX = dragDelta.getX();
\r
215 width = bounds.getWidth() + x;
\r
221 if (bounds.getHeight() + y < minHeight) {
\r
222 height = minHeight;
\r
223 pointY = dragDelta.getY();
\r
225 height = bounds.getHeight() + y;
\r
229 // System.out.println("bounds.getX()=" + bounds.getX() + " bounds.getY())=" + bounds.getY());
\r
230 // System.out.println("width=" + width + " height=" + height);
\r
232 bounds.setFrame(bounds.getX(), bounds.getY(), width, height);
\r
233 dragDelta = new Point2D.Double(pointX, pointY); // TODO ..
\r
235 } else if (Boolean.FALSE.equals(resize)) {
\r
236 if (transform == IdentityAffineTransform.INSTANCE)
\r
237 transform = AffineTransform.getTranslateInstance(x, y);
\r
239 transform.translate(x, y);
\r
242 //dragDelta = new Point2D.Double(me.getPoint().getX(), me.getPoint().getY());
\r
245 final double paddingX = 0.0;
\r
246 final double paddingY = 0.0;
\r
248 Path2D corner = createCorner(path, bounds, paddingX, paddingY, sx, sy);
\r
249 Rectangle2D header = createRectangle(rect, bounds, paddingX, paddingY, sx, sy);
\r
251 if (corner.contains(p)) {
\r
252 setCursor(Cursor.HAND_CURSOR);
\r
254 } else if (header.contains(p)) {
\r
255 setCursor(Cursor.HAND_CURSOR);
\r
258 setCursor(Cursor.DEFAULT_CURSOR);
\r
266 protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
\r
267 boolean consume = false;
\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
274 Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, temp);
\r
275 final double mx = localPos.getX();
\r
276 final double my = localPos.getY();
\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
281 final double paddingX = 0.0;
\r
282 final double paddingY = 0.0;
\r
284 Path2D corner = createCorner(path, bounds, paddingX, paddingY, sx, sy);
\r
285 Rectangle2D header = createRectangle(rect, bounds, paddingX, paddingY, sx, sy);
\r
287 if (corner.contains(p)) {// me.getPoint().getX() > right-5-paddingX && me.getPoint().getY() > bottom - 5 - paddingY) {
\r
289 orig = new Point2D.Double(bounds.getWidth(), bounds.getHeight());
\r
290 resize = Boolean.TRUE;
\r
291 setCursor(Cursor.SE_RESIZE_CURSOR);
\r
293 } else if (header.contains(p)) {// me.getPoint().getY() < bounds.getMinY()+8) {
\r
295 orig = new Point2D.Double(transform.getTranslateX(), transform.getTranslateY());
\r
296 resize = Boolean.FALSE;
\r
297 setCursor(Cursor.MOVE_CURSOR);
\r
303 dragDelta = new Point2D.Double(p.getX(), p.getY());
\r
311 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
\r
312 if (orig != null) {
\r
313 setCursor(Cursor.DEFAULT_CURSOR);
\r
315 Point2D delta = new Point2D.Double(bounds.getWidth() - orig.getX(), bounds.getHeight() - orig.getY());
\r
318 Point2D delta = new Point2D.Double((transform.getTranslateX() - orig.getX()), (transform.getTranslateY() - orig.getY()));
\r
328 public int getEventMask() {
\r
329 return EventTypes.MouseButtonPressedMask | EventTypes.MouseButtonReleasedMask | EventTypes.MouseMovedMask;
\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
341 result.setLocation(sx, sy);
\r
345 private static Rectangle2D createRectangle(Rectangle2D result, Rectangle2D bounds,
\r
346 double paddingX, double paddingY, double sx, double sy) {
\r
348 (bounds.getMinX() - paddingX)*sx,
\r
349 (bounds.getMinY() - paddingY)*sy,
\r
350 (bounds.getWidth() + 2.0 * paddingX) * sx,
\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
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