]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SelectionNode.java
2bdf5da7445a61976e0fb898e4aa19a5aa47fda2
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / SelectionNode.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.Graphics2D;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Point2D;
20 import java.awt.geom.Rectangle2D;
21
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;
27
28 public class SelectionNode extends G2DNode implements Decoration {
29     /**
30      * 
31      */
32     private static final long serialVersionUID = -2879575230419873230L;
33
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);
37
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;
45
46     public int getSelectionId() {
47         return selectionId;
48     }
49     
50     public void setIgnore(boolean value) {
51         ignore = value;
52     }
53     
54     /**
55      *  @deprecated in favor of G2DSceneGraph.PICK_DISTANCE
56      */
57     public void setPaddingFactor(double factor) {
58     }
59   
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;
64         this.bounds = bounds;
65         this.color = color;
66     }
67
68     public void init(AffineTransform transform, Rectangle2D bounds, Color color) {
69         init(0, transform, bounds, color);
70     }
71
72     @Override
73     public void render(Graphics2D g) {
74         if (bounds == null) return;
75         
76         if (ignore) return;
77
78         // Prevent exceptions during rendering.
79         if (transform.getDeterminant() == 0)
80             return;
81
82         NavigationNode nn = NodeUtil.findNearestParentNode(this, NavigationNode.class);
83         double scale = 1.0;
84         if (nn != null) {
85             scale = GeometryUtils.getScale(nn.getTransform());
86         }
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;
93         }
94
95         G2DSceneGraph sg = NodeUtil.getRootNode(nn);
96         double padding = sg.getGlobalProperty(G2DSceneGraph.PICK_DISTANCE, 0.0) / scale;
97
98         g.setStroke(scaledStroke);
99         g.setColor(color);
100         Shape selectionShape = transformAndExpand(bounds, transform, padding);
101         g.draw(selectionShape);
102
103         if (rect == null)
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);
108     }
109
110     private static Shape transformAndExpand(Rectangle2D r, AffineTransform t, double padding) {
111
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);
116             return result;
117         } else {
118             // General case
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);
125
126             double det = t.getDeterminant();
127             double step = Math.PI / 2;
128             double diagonalPadding = padding * Math.sqrt(2);
129
130             Polygon2D poly = new Polygon2D();
131             for (int edge = 0; edge < 4; edge++) {
132                 int p0 = edge % 4;
133                 int p1 = (edge + 1) % 4; 
134                 int p2 = (edge + 2) % 4;
135
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);
138                 if (det > 0) {
139                     if (a1 < a2) {
140                         a1 += Math.PI * 2;
141                     }
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));
148                     }
149                 } else {
150                     if (a1 > a2) {
151                         a2 += Math.PI * 2;
152                     }
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));
159                     }
160                 }
161             }
162             return poly;
163         }
164     }
165     
166     public Rectangle2D getRect() {
167         return rect;
168     }
169
170     @Override
171     public Rectangle2D getBoundsInLocal() {
172         return bounds;
173     }
174 }