1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
\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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.g2d.nodes.connection;
\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
27 import javax.imageio.ImageIO;
\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
35 * @author Tuukka Lehtonen
\r
37 public class HighlightActionPointsAction implements IAction {
\r
39 public static enum Action {
\r
44 public static class Pick {
\r
45 public static final Pick MISS = new Pick(null, null);
\r
50 public Pick(Action action, RouteLineHalf line) {
\r
51 this.action = action;
\r
55 public boolean hasAction(Action action) {
\r
56 return this.action == action;
\r
59 public boolean hasResult() {
\r
60 return action != null && line != null;
\r
63 public boolean matches(Action action, RouteLineHalf line) {
\r
64 if (this.action == null || this.line == null)
\r
66 return this.action.equals(action) && this.line.equals(line);
\r
70 static BufferedImage cross;
\r
71 static BufferedImage cut;
\r
74 cross = safeReadImage("cross.png");
\r
75 cut = safeReadImage("cut.png");
\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
81 public static final double DEGENERATED_LINE_LENGTH = 1;
\r
82 public static final double CUT_DIST_FROM_END = 0.5;
\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
91 public HighlightActionPointsAction(RouteGraph rg) {
\r
95 public void setRouteGraph(RouteGraph rg) {
\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
103 boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);
\r
104 boolean branchedConnection = rg.getTerminals().size() > 2;
\r
107 rg.getLineHalves(lhs);
\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
115 Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY);
\r
117 if (!simpleConnection) {
\r
118 double crossW = cross.getWidth()*viewScale*.5;
\r
119 double crossH = cross.getHeight()*viewScale*.5;
\r
121 // Render line removal markers
\r
122 for (RouteLineHalf lh : lhs) {
\r
123 // if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH)
\r
125 // if (!lh.getLine().isTransient())
\r
127 if (lh.getLine().getTerminal() == null)
\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
134 y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;
\r
137 boolean hit = pick.matches(Action.REMOVE, lh);
\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
146 g.setComposite(COMPOSITE);
\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
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
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
167 if (lh.getLink() == lh.getLine().getBegin())
\r
173 boolean hit = pick.matches(Action.RECONNECT, lh);
\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
181 g.transform(transform);
\r
182 g.drawImage(cut, transform2, null);
\r
183 g.setTransform(preTr);
\r
185 g.setComposite(COMPOSITE);
\r
189 g.setComposite(originalComposite);
\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
199 return pickAction(rg, rg.getLineHalves(lhs), viewTr, mouseX, mouseY);
\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
209 rg.getLineHalves(lhs);
\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
216 if (!simpleConnection) {
\r
217 double crossW = cross.getWidth()*viewScale*.5;
\r
218 double crossH = cross.getHeight()*viewScale*.5;
\r
220 // Render line removal markers
\r
221 for (RouteLineHalf lh : lhs) {
\r
222 // if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH)
\r
224 // if (!lh.getLine().isTransient())
\r
226 if (lh.getLine().getTerminal() == null)
\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
233 y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;
\r
236 rect.setFrameFromCenter(x, y, x-crossW, y-crossH);
\r
237 boolean hit = rect.contains(mouseX, mouseY);
\r
239 double distSq = distSq(x, y, mouseX, mouseY);
\r
240 if (distSq < nearest) {
\r
243 selectedAction = Action.REMOVE;
\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
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
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
266 if (lh.getLink() == lh.getLine().getBegin())
\r
272 rect.setFrameFromCenter(x, y, x-cutW, y-cutH);
\r
273 boolean hit = rect.contains(mouseX, mouseY);
\r
275 double distSq = distSq(x, y, mouseX, mouseY);
\r
276 if (distSq < nearest) {
\r
279 selectedAction = Action.RECONNECT;
\r
285 return selected == null ? Pick.MISS : new Pick(selectedAction, selected);
\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
294 private static double getScale(AffineTransform at)
\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
301 return Math.sqrt(Math.abs(m00*m11 - m10*m01));
\r
304 private static BufferedImage safeReadImage(String name) {
\r
306 URL url = HighlightActionPointsAction.class.getResource(name);
\r
307 return ImageIO.read(url);
\r
308 } catch (IOException e) {
\r