]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/HighlightActionPointsAction.java
Sync git svn branch with SVN repository r33319.
[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.Color;\r
17 import java.awt.Composite;\r
18 import java.awt.Graphics2D;\r
19 import java.awt.Shape;\r
20 import java.awt.Stroke;\r
21 import java.awt.geom.AffineTransform;\r
22 import java.awt.geom.Point2D;\r
23 import java.awt.geom.Rectangle2D;\r
24 import java.util.ArrayList;\r
25 import java.util.Collection;\r
26 \r
27 import org.simantics.diagram.connection.RouteGraph;\r
28 import org.simantics.diagram.connection.RouteLineHalf;\r
29 import org.simantics.diagram.connection.actions.IAction;\r
30 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
31 import org.simantics.scenegraph.utils.Quality;\r
32 import org.simantics.scenegraph.utils.QualityHints;\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     private static final Shape          CROSS_SHAPE             = ActionShapes.CROSS_SHAPE;\r
71     private static final Shape          SCISSOR_SHAPE           = ActionShapes.transformShape(ActionShapes.SCISSOR_SHAPE, 1, 1, 0, 0, -Math.PI/2);\r
72 \r
73     private static final Color          CROSS_COLOR             = new Color(0xe4, 0x40, 0x61);\r
74     private static final Color          SCISSOR_COLOR           = new Color(20, 20, 20);\r
75 \r
76     public static final Stroke          STROKE                  = new BasicStroke(0.1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);\r
77     public static final AlphaComposite  NO_HIT_COMPOSITE        = AlphaComposite.SrcOver.derive(0.8f);\r
78     public static final AlphaComposite  HIT_COMPOSITE           = AlphaComposite.SrcOver.derive(0.2f);\r
79 \r
80     public static final double          DEGENERATED_LINE_LENGTH = 1;\r
81     public static final double          CUT_DIST_FROM_END       = 0.75;\r
82 \r
83     RouteGraph                          rg;\r
84 \r
85     transient Collection<RouteLineHalf> lhs                     = new ArrayList<RouteLineHalf>();\r
86     transient AffineTransform           transform               = new AffineTransform();\r
87     transient Rectangle2D               rect                    = new Rectangle2D.Double();\r
88     transient Point2D                   point                   = new Point2D.Double();\r
89 \r
90     public HighlightActionPointsAction(RouteGraph rg) {\r
91         this.rg = rg;\r
92     }\r
93 \r
94     public void setRouteGraph(RouteGraph rg) {\r
95         this.rg = rg;\r
96     }\r
97 \r
98     @Override\r
99     public void render(Graphics2D g, IRouteGraphRenderer renderer, double mouseX, double mouseY) {\r
100         // Cannot perform cut or delete segment actions\r
101         // on connections between 2 terminals.\r
102         boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);\r
103         boolean branchedConnection = rg.getTerminals().size() > 2;\r
104         if (!branchedConnection || simpleConnection)\r
105             return;\r
106 \r
107         AffineTransform preTr = g.getTransform();\r
108         double realViewScale = 1.0 / getScale(preTr);\r
109         //System.out.println(realViewScale);\r
110         // Don't render any of the actions if they could not be seen anyway.\r
111         if (realViewScale > 0.7)\r
112             return;\r
113 \r
114         lhs.clear();\r
115         rg.getLineHalves(lhs);\r
116 \r
117         Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY);\r
118 \r
119         Composite originalComposite = g.getComposite();\r
120         Composite basicComposite = pick.action != null ? HIT_COMPOSITE : NO_HIT_COMPOSITE;\r
121         g.setComposite(basicComposite);\r
122 \r
123         // Always render these in high quality because otherwise the shapes\r
124         // will render with ugly artifacts when zoom level is a bit higher.\r
125         QualityHints origQualityHints = QualityHints.getQuality(g);\r
126         QualityHints.getHints(Quality.HIGH).setQuality(g);\r
127 \r
128         // Render line removal markers\r
129         if (!simpleConnection) {\r
130             g.setPaint(CROSS_COLOR);\r
131             for (RouteLineHalf lh : lhs) {\r
132                 if (removeLocation(lh, point) == null)\r
133                     continue;\r
134                 boolean hit = pick.matches(Action.REMOVE, lh);\r
135                 if (hit)\r
136                     g.setComposite(originalComposite);\r
137                 g.translate(point.getX(), point.getY());\r
138                 g.fill(CROSS_SHAPE);\r
139                 g.setTransform(preTr);\r
140                 if (hit)\r
141                     g.setComposite(basicComposite);\r
142             }\r
143         }\r
144 \r
145         // Render reconnection markers if the connection is branched.\r
146         if (branchedConnection) {\r
147             g.setPaint(SCISSOR_COLOR);\r
148             for (RouteLineHalf lh : lhs) {\r
149                 if (reconnectLocation(lh, point) == null)\r
150                     continue;\r
151                 boolean hit = pick.matches(Action.RECONNECT, lh);\r
152                 if (hit)\r
153                     g.setComposite(originalComposite);\r
154                 transform.setToTranslation(point.getX(), point.getY());\r
155                 if (!lh.getLine().isHorizontal())\r
156                     transform.rotate(Math.PI/2);\r
157                 transform.translate(0, 0.35);\r
158                 g.transform(transform);\r
159                 g.fill(SCISSOR_SHAPE);\r
160                 g.setTransform(preTr);\r
161                 if (hit)\r
162                     g.setComposite(basicComposite);\r
163             }\r
164         }\r
165 \r
166         origQualityHints.setQuality(g);\r
167         g.setComposite(originalComposite);\r
168     }\r
169 \r
170     public Pick pickAction(RouteGraph rg, AffineTransform viewTr, double mouseX, double mouseY) {\r
171         boolean branchedConnection = rg.getTerminals().size() > 2;\r
172         boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);\r
173         if (!branchedConnection || simpleConnection)\r
174             return null;\r
175 \r
176         lhs.clear();\r
177         return pickAction(rg, rg.getLineHalves(lhs), viewTr, mouseX, mouseY);\r
178     }\r
179 \r
180     public Pick pickAction(RouteGraph rg, Collection<RouteLineHalf> lhs, AffineTransform viewTr, double mouseX, double mouseY) {\r
181         boolean branchedConnection = rg.getTerminals().size() > 2;\r
182         boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);\r
183         if (!branchedConnection || simpleConnection || viewTr == null)\r
184             return Pick.MISS;\r
185 \r
186         double viewScale = 1.0 / getScale(viewTr);\r
187         if (viewScale > 0.7)\r
188             return Pick.MISS;\r
189 \r
190         double nearest = Double.MAX_VALUE;\r
191         RouteLineHalf selected = null;\r
192         Action selectedAction = null;\r
193 \r
194         // Pick line removal markers\r
195         if (!simpleConnection) {\r
196             double s = ActionShapes.CROSS_WIDTH * 0.25;\r
197             for (RouteLineHalf lh : lhs) {\r
198                 if (removeLocation(lh, point) == null)\r
199                     continue;\r
200                 double x = point.getX();\r
201                 double y = point.getY();\r
202                 rect.setFrameFromCenter(x, y, x-s, y-s);\r
203                 boolean hit = rect.contains(mouseX, mouseY);\r
204                 if (hit) {\r
205                     double distSq = distSq(x, y, mouseX, mouseY);\r
206                     if (distSq < nearest) {\r
207                         nearest = distSq;\r
208                         selected = lh;\r
209                         selectedAction = Action.REMOVE;\r
210                     }\r
211                 }\r
212             }\r
213         }\r
214 \r
215         // Pick reconnection markers if the connection is branched.\r
216         if (branchedConnection) {\r
217             double w = ActionShapes.SCISSOR_HEIGHT * 0.4;\r
218             double h = ActionShapes.SCISSOR_WIDTH * 0.3;\r
219             for (RouteLineHalf lh : lhs) {\r
220                 if (reconnectLocation(lh, point) == null)\r
221                     continue;\r
222                 double x = point.getX();\r
223                 double y = point.getY();\r
224                 rect.setFrameFromCenter(x, y, x-w, y-h);\r
225                 boolean hit = rect.contains(mouseX, mouseY);\r
226                 if (hit) {\r
227                     double distSq = distSq(x, y, mouseX, mouseY);\r
228                     if (distSq < nearest) {\r
229                         nearest = distSq;\r
230                         selected = lh;\r
231                         selectedAction = Action.RECONNECT;\r
232                     }\r
233                 }\r
234             }\r
235         }\r
236 \r
237         return selected == null ? Pick.MISS : new Pick(selectedAction, selected);\r
238     }\r
239 \r
240     private static Point2D removeLocation(RouteLineHalf lh, Point2D p) {\r
241         if (lh.getLine().getTerminal() == null)\r
242             return null;\r
243 \r
244         double x = lh.getLink().getX();\r
245         double y = lh.getLink().getY();\r
246         if (lh.getLine().isHorizontal()) {\r
247             x = (lh.getLine().getBegin().getX() + lh.getLine().getEnd().getX()) * .5;\r
248         } else {\r
249             y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;\r
250         }\r
251         p.setLocation(x, y);\r
252         return p;\r
253     }\r
254 \r
255     private static Point2D reconnectLocation(RouteLineHalf lh, Point2D p) {\r
256         if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)\r
257             return null;\r
258 \r
259         final double dist = CUT_DIST_FROM_END;\r
260         double x = lh.getLink().getX();\r
261         double y = lh.getLink().getY();\r
262         if (lh.getLine().isHorizontal()) {\r
263             if (lh.getLink() == lh.getLine().getBegin())\r
264                 x += dist*2;\r
265             else\r
266                 x -= dist*2;\r
267         } else {\r
268             if (lh.getLink() == lh.getLine().getBegin())\r
269                 y += dist*2;\r
270             else\r
271                 y -= dist*2;\r
272         }\r
273         p.setLocation(x, y);\r
274         return p;\r
275     }\r
276 \r
277     private static double distSq(double x1, double y1, double x2, double y2) {\r
278         double dx = x2 - x1;\r
279         double dy = y2 - y1;\r
280         return dx * dx + dy * dy;\r
281     }\r
282 \r
283     private static double getScale(AffineTransform at)\r
284     {\r
285         double m00 = at.getScaleX();\r
286         double m11 = at.getScaleY();\r
287         double m10 = at.getShearY();\r
288         double m01 = at.getShearX();\r
289 \r
290         return Math.sqrt(Math.abs(m00*m11 - m10*m01));\r
291     }\r
292 \r
293 }\r