1 /*******************************************************************************
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes.connection;
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;
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;
35 * @author Tuukka Lehtonen
37 public class HighlightActionPointsAction implements IAction {
39 public static enum Action {
44 public static class Pick {
45 public static final Pick MISS = new Pick(null, null);
50 public Pick(Action action, RouteLineHalf line) {
55 public boolean hasAction(Action action) {
56 return this.action == action;
59 public boolean hasResult() {
60 return action != null && line != null;
63 public boolean matches(Action action, RouteLineHalf line) {
64 if (this.action == null || this.line == null)
66 return this.action.equals(action) && this.line.equals(line);
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);
73 private static final Color CROSS_COLOR = new Color(0xe4, 0x40, 0x61);
74 private static final Color SCISSOR_COLOR = new Color(20, 20, 20);
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);
80 public static final double DEGENERATED_LINE_LENGTH = 1;
81 public static final double CUT_DIST_FROM_END = 0.75;
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();
90 public HighlightActionPointsAction(RouteGraph rg) {
94 public void setRouteGraph(RouteGraph rg) {
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)
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)
115 rg.getLineHalves(lhs);
117 Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY);
119 Composite originalComposite = g.getComposite();
120 Composite basicComposite = pick.action != null ? HIT_COMPOSITE : NO_HIT_COMPOSITE;
121 g.setComposite(basicComposite);
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);
128 // Render line removal markers
129 if (!simpleConnection) {
130 g.setPaint(CROSS_COLOR);
131 for (RouteLineHalf lh : lhs) {
132 if (removeLocation(lh, point) == null)
134 boolean hit = pick.matches(Action.REMOVE, lh);
136 g.setComposite(originalComposite);
137 g.translate(point.getX(), point.getY());
139 g.setTransform(preTr);
141 g.setComposite(basicComposite);
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)
151 boolean hit = pick.matches(Action.RECONNECT, lh);
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);
162 g.setComposite(basicComposite);
166 origQualityHints.setQuality(g);
167 g.setComposite(originalComposite);
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)
177 return pickAction(rg, rg.getLineHalves(lhs), viewTr, mouseX, mouseY);
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)
186 double viewScale = 1.0 / getScale(viewTr);
190 double nearest = Double.MAX_VALUE;
191 RouteLineHalf selected = null;
192 Action selectedAction = null;
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)
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);
205 double distSq = distSq(x, y, mouseX, mouseY);
206 if (distSq < nearest) {
209 selectedAction = Action.REMOVE;
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)
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);
227 double distSq = distSq(x, y, mouseX, mouseY);
228 if (distSq < nearest) {
231 selectedAction = Action.RECONNECT;
237 return selected == null ? Pick.MISS : new Pick(selectedAction, selected);
240 private static Point2D removeLocation(RouteLineHalf lh, Point2D p) {
241 if (lh.getLine().getTerminal() == null)
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;
249 y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;
255 private static Point2D reconnectLocation(RouteLineHalf lh, Point2D p) {
256 if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)
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())
268 if (lh.getLink() == lh.getLine().getBegin())
277 private static double distSq(double x1, double y1, double x2, double y2) {
280 return dx * dx + dy * dy;
283 private static double getScale(AffineTransform at)
285 double m00 = at.getScaleX();
286 double m11 = at.getScaleY();
287 double m10 = at.getShearY();
288 double m01 = at.getShearX();
290 return Math.sqrt(Math.abs(m00*m11 - m10*m01));