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.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
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
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 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
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
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
80 public static final double DEGENERATED_LINE_LENGTH = 1;
\r
81 public static final double CUT_DIST_FROM_END = 0.75;
\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
90 public HighlightActionPointsAction(RouteGraph rg) {
\r
94 public void setRouteGraph(RouteGraph rg) {
\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
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
115 rg.getLineHalves(lhs);
\r
117 Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY);
\r
119 Composite originalComposite = g.getComposite();
\r
120 Composite basicComposite = pick.action != null ? HIT_COMPOSITE : NO_HIT_COMPOSITE;
\r
121 g.setComposite(basicComposite);
\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
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
134 boolean hit = pick.matches(Action.REMOVE, lh);
\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
141 g.setComposite(basicComposite);
\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
151 boolean hit = pick.matches(Action.RECONNECT, lh);
\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
162 g.setComposite(basicComposite);
\r
166 origQualityHints.setQuality(g);
\r
167 g.setComposite(originalComposite);
\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
177 return pickAction(rg, rg.getLineHalves(lhs), viewTr, mouseX, mouseY);
\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
186 double viewScale = 1.0 / getScale(viewTr);
\r
187 if (viewScale > 0.7)
\r
190 double nearest = Double.MAX_VALUE;
\r
191 RouteLineHalf selected = null;
\r
192 Action selectedAction = null;
\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
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
205 double distSq = distSq(x, y, mouseX, mouseY);
\r
206 if (distSq < nearest) {
\r
209 selectedAction = Action.REMOVE;
\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
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
227 double distSq = distSq(x, y, mouseX, mouseY);
\r
228 if (distSq < nearest) {
\r
231 selectedAction = Action.RECONNECT;
\r
237 return selected == null ? Pick.MISS : new Pick(selectedAction, selected);
\r
240 private static Point2D removeLocation(RouteLineHalf lh, Point2D p) {
\r
241 if (lh.getLine().getTerminal() == null)
\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
249 y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;
\r
251 p.setLocation(x, y);
\r
255 private static Point2D reconnectLocation(RouteLineHalf lh, Point2D p) {
\r
256 if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)
\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
268 if (lh.getLink() == lh.getLine().getBegin())
\r
273 p.setLocation(x, y);
\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
283 private static double getScale(AffineTransform at)
\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
290 return Math.sqrt(Math.abs(m00*m11 - m10*m01));
\r