1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes;
14 import java.awt.BasicStroke;
15 import java.awt.Color;
16 import java.awt.Graphics2D;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Point2D;
20 import java.awt.geom.Rectangle2D;
22 import org.apache.batik.ext.awt.geom.Polygon2D;
23 import org.simantics.scenegraph.g2d.G2DNode;
24 import org.simantics.scenegraph.g2d.G2DSceneGraph;
25 import org.simantics.scenegraph.utils.GeometryUtils;
26 import org.simantics.scenegraph.utils.NodeUtil;
28 public class SelectionNode extends G2DNode implements Decoration {
32 private static final long serialVersionUID = -2879575230419873230L;
34 public transient static final BasicStroke SELECTION_STROKE = new BasicStroke(1.0f,
35 BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE, 10.0f,
36 new float[] { 5.0f, 5.0f }, 0.0f);
38 protected Rectangle2D bounds = null;
39 protected Color color = null;
40 protected transient Rectangle2D rect;
41 protected transient BasicStroke scaledStroke;
42 protected transient double previousScaleRecip = Double.NaN;
43 protected boolean ignore = false;
44 protected int selectionId;
46 public int getSelectionId() {
50 public void setIgnore(boolean value) {
55 * @deprecated in favor of G2DSceneGraph.PICK_DISTANCE
57 public void setPaddingFactor(double factor) {
60 @SyncField({"transform", "bounds", "color"})
61 public void init(int selectionId, AffineTransform transform, Rectangle2D bounds, Color color) {
62 this.selectionId = selectionId;
63 this.transform = transform;
68 public void init(AffineTransform transform, Rectangle2D bounds, Color color) {
69 init(0, transform, bounds, color);
73 public void render(Graphics2D g) {
74 if (bounds == null) return;
78 // Prevent exceptions during rendering.
79 if (transform.getDeterminant() == 0)
82 NavigationNode nn = NodeUtil.findNearestParentNode(this, NavigationNode.class);
85 scale = GeometryUtils.getScale(nn.getTransform());
87 double scaleRecip = 1 / scale;
88 // Prevent stroke reallocation while panning.
89 // Zooming will trigger reallocation.
90 if (scaledStroke == null || scaleRecip != previousScaleRecip) {
91 scaledStroke = GeometryUtils.scaleStroke( SELECTION_STROKE, (float) scaleRecip);
92 previousScaleRecip = scaleRecip;
95 G2DSceneGraph sg = NodeUtil.getRootNode(nn);
96 double padding = sg.getGlobalProperty(G2DSceneGraph.PICK_DISTANCE, 0.0) / scale;
98 g.setStroke(scaledStroke);
100 Shape selectionShape = transformAndExpand(bounds, transform, padding);
101 g.draw(selectionShape);
104 rect = new Rectangle2D.Double();
105 Rectangle2D r = transform.createTransformedShape(bounds).getBounds2D();
106 rect.setFrame(r.getMinX() - padding, r.getMinY() - padding,
107 r.getWidth() + 2.0*padding, r.getHeight() + 2.0*padding);
110 private static Shape transformAndExpand(Rectangle2D r, AffineTransform t, double padding) {
112 if ((t.getShearX() == 0 && t.getShearY() == 0) || t.getScaleX() == 0 && t.getScaleY() == 0) {
113 // Simple case for axis-aligned selection
114 Rectangle2D result = t.createTransformedShape(r).getBounds2D();
115 result.setRect(result.getMinX() - padding, result.getMinY() - padding, result.getWidth() + 2 * padding, result.getHeight() + 2 * padding);
119 Point2D.Double corners[] = new Point2D.Double[4];
120 corners[0] = new Point2D.Double(r.getMinX(), r.getMinY());
121 corners[1] = new Point2D.Double(r.getMinX(), r.getMaxY());
122 corners[2] = new Point2D.Double(r.getMaxX(), r.getMaxY());
123 corners[3] = new Point2D.Double(r.getMaxX(), r.getMinY());
124 t.transform(corners, 0, corners, 0, 4);
126 double det = t.getDeterminant();
127 double step = Math.PI / 2;
128 double diagonalPadding = padding * Math.sqrt(2);
130 Polygon2D poly = new Polygon2D();
131 for (int edge = 0; edge < 4; edge++) {
133 int p1 = (edge + 1) % 4;
134 int p2 = (edge + 2) % 4;
136 double a1 = Math.atan2(corners[p1].y - corners[p0].y, corners[p1].x - corners[p0].x);
137 double a2 = Math.atan2(corners[p2].y - corners[p1].y, corners[p2].x - corners[p1].x);
142 int dir1 = (int)Math.ceil(a1 / step);
143 int dir2 = (int)Math.floor(a2 / step);
144 for (int dir = dir1; dir > dir2; dir--) {
145 poly.addPoint(new Point2D.Double(
146 corners[p1].x + Math.cos((dir + 0.5) * step) * diagonalPadding,
147 corners[p1].y + Math.sin((dir + 0.5) * step) * diagonalPadding));
153 int dir1 = (int)Math.floor(a1 / step);
154 int dir2 = (int)Math.ceil(a2 / step);
155 for (int dir = dir1; dir < dir2; dir++) {
156 poly.addPoint(new Point2D.Double(
157 corners[p1].x + Math.cos((dir - 0.5) * step) * diagonalPadding,
158 corners[p1].y + Math.sin((dir - 0.5) * step) * diagonalPadding));
166 public Rectangle2D getRect() {
171 public Rectangle2D getBoundsInLocal() {