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