]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/HighlightActionPointsAction.java
04d08e3ec155242b1b6d58fad89f62adbab6e3d7
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / connection / HighlightActionPointsAction.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
3  * 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.connection;\r
13 \r
14 import java.awt.AlphaComposite;\r
15 import java.awt.BasicStroke;\r
16 import java.awt.Composite;\r
17 import java.awt.Graphics2D;\r
18 import java.awt.Stroke;\r
19 import java.awt.geom.AffineTransform;\r
20 import java.awt.geom.Rectangle2D;\r
21 import java.awt.image.BufferedImage;\r
22 import java.io.IOException;\r
23 import java.net.URL;\r
24 import java.util.ArrayList;\r
25 import java.util.Collection;\r
26 \r
27 import javax.imageio.ImageIO;\r
28 \r
29 import org.simantics.diagram.connection.RouteGraph;\r
30 import org.simantics.diagram.connection.RouteLineHalf;\r
31 import org.simantics.diagram.connection.actions.IAction;\r
32 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
33 \r
34 /**\r
35  * @author Tuukka Lehtonen\r
36  */\r
37 public class HighlightActionPointsAction implements IAction {\r
38 \r
39     public static enum Action {\r
40         REMOVE,\r
41         RECONNECT\r
42     }\r
43 \r
44     public static class Pick {\r
45         public static final Pick MISS = new Pick(null, null);\r
46 \r
47         Action                   action;\r
48         RouteLineHalf            line;\r
49 \r
50         public Pick(Action action, RouteLineHalf line) {\r
51             this.action = action;\r
52             this.line = line;\r
53         }\r
54 \r
55         public boolean hasAction(Action action) {\r
56             return this.action == action;\r
57         }\r
58 \r
59         public boolean hasResult() {\r
60             return action != null && line != null;\r
61         }\r
62 \r
63         public boolean matches(Action action, RouteLineHalf line) {\r
64             if (this.action == null || this.line == null)\r
65                 return false;\r
66             return this.action.equals(action) && this.line.equals(line);\r
67         }\r
68     }\r
69 \r
70     static BufferedImage                cross;\r
71     static BufferedImage                cut;\r
72 \r
73     static {\r
74         cross = safeReadImage("cross.png");\r
75         cut = safeReadImage("cut.png");\r
76     }\r
77 \r
78     public static final Stroke          STROKE                  = new BasicStroke(0.1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);\r
79     public static final AlphaComposite  COMPOSITE               = AlphaComposite.SrcOver.derive(0.6f);\r
80 \r
81     public static final double          DEGENERATED_LINE_LENGTH = 1;\r
82     public static final double          CUT_DIST_FROM_END       = 0.5;\r
83 \r
84     RouteGraph                          rg;\r
85 \r
86     transient Collection<RouteLineHalf> lhs                     = new ArrayList<RouteLineHalf>();\r
87     transient AffineTransform           transform               = new AffineTransform();\r
88     transient AffineTransform           transform2              = new AffineTransform();\r
89     transient Rectangle2D               rect                    = new Rectangle2D.Double();\r
90 \r
91     public HighlightActionPointsAction(RouteGraph rg) {\r
92         this.rg = rg;\r
93     }\r
94 \r
95     public void setRouteGraph(RouteGraph rg) {\r
96         this.rg = rg;\r
97     }\r
98 \r
99     @Override\r
100     public void render(Graphics2D g, IRouteGraphRenderer renderer, double mouseX, double mouseY) {\r
101         // Cannot perform cut or delete segment actions on connections between 2\r
102         // terminals.\r
103         boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);\r
104         boolean branchedConnection = rg.getTerminals().size() > 2;\r
105 \r
106         lhs.clear();\r
107         rg.getLineHalves(lhs);\r
108 \r
109         AffineTransform preTr = g.getTransform();\r
110         double viewScale = 1.0 / getScale(preTr);\r
111         transform2.setToScale(viewScale, viewScale);\r
112         Composite originalComposite = g.getComposite();\r
113         g.setComposite(COMPOSITE);\r
114 \r
115         Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY);\r
116 \r
117         if (!simpleConnection) {\r
118             double crossW = cross.getWidth()*viewScale*.5;\r
119             double crossH = cross.getHeight()*viewScale*.5;\r
120 \r
121             // Render line removal markers\r
122             for (RouteLineHalf lh : lhs) {\r
123 //                if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH)\r
124 //                    continue;\r
125 //                if (!lh.getLine().isTransient())\r
126 //                    continue;\r
127                 if (lh.getLine().getTerminal() == null)\r
128                     continue;\r
129                 double x = lh.getLink().getX();\r
130                 double y = lh.getLink().getY();\r
131                 if (lh.getLine().isHorizontal()) {\r
132                     x = (lh.getLine().getBegin().getX() + lh.getLine().getEnd().getX()) * .5;\r
133                 } else {\r
134                     y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;\r
135                 }\r
136 \r
137                 boolean hit = pick.matches(Action.REMOVE, lh);\r
138 \r
139                 if (hit)\r
140                     g.setComposite(originalComposite);\r
141                 transform.setToTranslation(x-crossW, y-crossH);\r
142                 g.transform(transform);\r
143                 g.drawImage(cross, transform2, null);\r
144                 g.setTransform(preTr);\r
145                 if (hit)\r
146                     g.setComposite(COMPOSITE);\r
147             }\r
148         }\r
149 \r
150         // Render reconnection markers if the connection is branched.\r
151         if (branchedConnection) {\r
152             double cutW = cut.getWidth()*viewScale*.5;\r
153             double cutH = cut.getHeight()*viewScale*.5;\r
154 \r
155             final double dist = CUT_DIST_FROM_END;\r
156             for (RouteLineHalf lh : lhs) {\r
157                 if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)\r
158                     continue;\r
159                 double x = lh.getLink().getX();\r
160                 double y = lh.getLink().getY();\r
161                 if (lh.getLine().isHorizontal()) {\r
162                     if (lh.getLink() == lh.getLine().getBegin())\r
163                         x += dist*2;\r
164                     else\r
165                         x -= dist*2;\r
166                 } else {\r
167                     if (lh.getLink() == lh.getLine().getBegin())\r
168                         y += dist*2;\r
169                     else\r
170                         y -= dist*2;\r
171                 }\r
172 \r
173                 boolean hit = pick.matches(Action.RECONNECT, lh);\r
174 \r
175                 if (hit)\r
176                     g.setComposite(originalComposite);\r
177                 transform.setToTranslation(x-cutW, y-cutH);\r
178                 if (!lh.getLine().isHorizontal()) {\r
179                     transform.rotate(Math.PI/2, cutW, cutH);\r
180                 }\r
181                 g.transform(transform);\r
182                 g.drawImage(cut, transform2, null);\r
183                 g.setTransform(preTr);\r
184                 if (hit)\r
185                     g.setComposite(COMPOSITE);\r
186             }\r
187         }\r
188 \r
189         g.setComposite(originalComposite);\r
190     }\r
191 \r
192     public Pick pickAction(RouteGraph rg, AffineTransform viewTr, double mouseX, double mouseY) {\r
193         boolean branchedConnection = rg.getTerminals().size() > 2;\r
194         boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);\r
195         if (!branchedConnection || simpleConnection)\r
196             return null;\r
197 \r
198         lhs.clear();\r
199         return pickAction(rg, rg.getLineHalves(lhs), viewTr, mouseX, mouseY);\r
200     }\r
201 \r
202     public Pick pickAction(RouteGraph rg, Collection<RouteLineHalf> lhs, AffineTransform viewTr, double mouseX, double mouseY) {\r
203         boolean branchedConnection = rg.getTerminals().size() > 2;\r
204         boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);\r
205         if (!branchedConnection || simpleConnection || viewTr == null)\r
206             return Pick.MISS;\r
207 \r
208         lhs.clear();\r
209         rg.getLineHalves(lhs);\r
210 \r
211         RouteLineHalf selected = null;\r
212         Action selectedAction = null;\r
213         double nearest = Double.MAX_VALUE;\r
214         double viewScale = 1.0 / getScale(viewTr);\r
215 \r
216         if (!simpleConnection) {\r
217             double crossW = cross.getWidth()*viewScale*.5;\r
218             double crossH = cross.getHeight()*viewScale*.5;\r
219 \r
220             // Render line removal markers\r
221             for (RouteLineHalf lh : lhs) {\r
222 //                if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH)\r
223 //                    continue;\r
224 //                if (!lh.getLine().isTransient())\r
225 //                    continue;\r
226                 if (lh.getLine().getTerminal() == null)\r
227                     continue;\r
228                 double x = lh.getLink().getX();\r
229                 double y = lh.getLink().getY();\r
230                 if (lh.getLine().isHorizontal()) {\r
231                     x = (lh.getLine().getBegin().getX() + lh.getLine().getEnd().getX()) * .5;\r
232                 } else {\r
233                     y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;\r
234                 }\r
235 \r
236                 rect.setFrameFromCenter(x, y, x-crossW, y-crossH);\r
237                 boolean hit = rect.contains(mouseX, mouseY);\r
238                 if (hit) {\r
239                     double distSq = distSq(x, y, mouseX, mouseY);\r
240                     if (distSq < nearest) {\r
241                         nearest = distSq;\r
242                         selected = lh;\r
243                         selectedAction = Action.REMOVE;\r
244                     }\r
245                 }\r
246             }\r
247         }\r
248 \r
249         // Render reconnection markers if the connection is branched.\r
250         if (branchedConnection) {\r
251             double cutW = cut.getWidth()*viewScale*.5;\r
252             double cutH = cut.getHeight()*viewScale*.5;\r
253 \r
254             final double dist = CUT_DIST_FROM_END;\r
255             for (RouteLineHalf lh : lhs) {\r
256                 if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)\r
257                     continue;\r
258                 double x = lh.getLink().getX();\r
259                 double y = lh.getLink().getY();\r
260                 if (lh.getLine().isHorizontal()) {\r
261                     if (lh.getLink() == lh.getLine().getBegin())\r
262                         x += dist*2;\r
263                     else\r
264                         x -= dist*2;\r
265                 } else {\r
266                     if (lh.getLink() == lh.getLine().getBegin())\r
267                         y += dist*2;\r
268                     else\r
269                         y -= dist*2;\r
270                 }\r
271 \r
272                 rect.setFrameFromCenter(x, y, x-cutW, y-cutH);\r
273                 boolean hit = rect.contains(mouseX, mouseY);\r
274                 if (hit) {\r
275                     double distSq = distSq(x, y, mouseX, mouseY);\r
276                     if (distSq < nearest) {\r
277                         nearest = dist;\r
278                         selected = lh;\r
279                         selectedAction = Action.RECONNECT;\r
280                     }\r
281                 }\r
282             }\r
283         }\r
284 \r
285         return selected == null ? Pick.MISS : new Pick(selectedAction, selected);\r
286     }\r
287 \r
288     private static double distSq(double x1, double y1, double x2, double y2) {\r
289         double dx = x2 - x1;\r
290         double dy = y2 - y1;\r
291         return dx * dx + dy * dy;\r
292     }\r
293 \r
294     private static double getScale(AffineTransform at)\r
295     {\r
296         double m00 = at.getScaleX();\r
297         double m11 = at.getScaleY();\r
298         double m10 = at.getShearY();\r
299         double m01 = at.getShearX();\r
300 \r
301         return Math.sqrt(Math.abs(m00*m11 - m10*m01));\r
302     }\r
303 \r
304     private static BufferedImage safeReadImage(String name) {\r
305         try {\r
306             URL url = HighlightActionPointsAction.class.getResource(name);\r
307             return ImageIO.read(url);\r
308         } catch (IOException e) {\r
309             return null;\r
310         }\r
311     }\r
312 \r
313 }\r