1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in 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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.diagram.participant;
\r
14 import java.awt.BasicStroke;
\r
15 import java.awt.Color;
\r
16 import java.awt.Shape;
\r
17 import java.awt.Stroke;
\r
18 import java.awt.geom.AffineTransform;
\r
19 import java.awt.geom.Path2D;
\r
20 import java.awt.geom.Rectangle2D;
\r
21 import java.util.Collection;
\r
22 import java.util.List;
\r
24 import org.simantics.g2d.canvas.ICanvasContext;
\r
25 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
26 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
\r
27 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
\r
28 import org.simantics.g2d.diagram.IDiagram;
\r
29 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
\r
30 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
\r
31 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
\r
32 import org.simantics.g2d.participant.MouseUtil;
\r
33 import org.simantics.g2d.participant.TransformUtil;
\r
34 import org.simantics.g2d.participant.MouseUtil.MouseInfo;
\r
35 import org.simantics.g2d.utils.GeometryUtils;
\r
36 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
37 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
\r
38 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
39 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
\r
40 import org.simantics.scenegraph.utils.ColorUtil;
\r
41 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
\r
42 import org.simantics.utils.datastructures.hints.IHintListener;
\r
43 import org.simantics.utils.datastructures.hints.IHintObservable;
\r
44 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
45 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
48 * Paints terminals of elements.
\r
50 * @author Toni Kalajainen
\r
52 public class TerminalPainter extends AbstractDiagramParticipant {
\r
54 public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 10;
\r
56 public interface TerminalHoverStrategy {
\r
59 * @return <code>true</code> if highlighting is enabled at the moment in
\r
60 * general. This may depend on the current modifier key state
\r
63 boolean highlightEnabled();
\r
66 * Checks whether the specified terminal should be highlighted at the
\r
67 * moment or not. Whether to highlight or not may depend for example on
\r
68 * the current modifier key state.
\r
73 boolean highlight(TerminalInfo ti);
\r
76 public static abstract class ChainedHoverStrategy implements TerminalHoverStrategy {
\r
77 TerminalHoverStrategy orig;
\r
78 public ChainedHoverStrategy(TerminalHoverStrategy orig) {
\r
82 public boolean highlightEnabled() {
\r
83 return orig == null ? false : orig.highlightEnabled();
\r
86 public final boolean highlight(TerminalInfo ti) {
\r
87 boolean ret = canHighlight(ti);
\r
88 return (ret || orig == null) ? ret : orig.highlight(ti);
\r
90 public abstract boolean canHighlight(TerminalInfo ti);
\r
94 * If this hint is set to a Callable<Boolean>, the terminal painter will use
\r
95 * the callable to evaluate whether it should highlight terminal hovers or
\r
98 public static final Key TERMINAL_HOVER_STRATEGY = new KeyOf(TerminalHoverStrategy.class);
\r
100 // private static final Stroke STROKE1 = new BasicStroke(1.0f);
\r
101 // private static final Stroke STROKE15 = new BasicStroke(1.5f);
\r
102 private static final Stroke STROKE25 = new BasicStroke(2.5f);
\r
104 // private final static Stroke TERMINAL_STROKE = new BasicStroke(1.0f);
\r
105 public static final Shape TERMINAL_SHAPE;
\r
108 protected TransformUtil util;
\r
110 protected MouseUtil mice;
\r
113 protected PointerInteractor pointerInteractor;
\r
115 protected boolean paintPointTerminals;
\r
116 protected boolean paintAreaTerminals;
\r
117 protected boolean paintHoverPointTerminals;
\r
118 protected boolean paintHoverAreaTerminals;
\r
120 public TerminalPainter(boolean paintPointTerminals, boolean paintHoverPointTerminals, boolean paintAreaTerminals, boolean paintHoverAreaTerminals)
\r
122 this.paintAreaTerminals = paintAreaTerminals;
\r
123 this.paintPointTerminals = paintPointTerminals;
\r
124 this.paintHoverAreaTerminals = paintHoverAreaTerminals;
\r
125 this.paintHoverPointTerminals = paintHoverPointTerminals;
\r
129 public void addedToContext(ICanvasContext ctx) {
\r
130 super.addedToContext(ctx);
\r
132 ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);
\r
136 public void removedFromContext(ICanvasContext ctx) {
\r
137 ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);
\r
139 super.removedFromContext(ctx);
\r
142 public boolean highlightEnabled() {
\r
143 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
\r
144 return strategy != null ? strategy.highlightEnabled() : true;
\r
147 public boolean highlightTerminal(TerminalInfo ti) {
\r
148 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
\r
149 return strategy != null ? strategy.highlight(ti) : true;
\r
152 @EventHandler(priority = 0)
\r
153 public boolean handleMove(MouseMovedEvent me) {
\r
154 if ( (paintHoverAreaTerminals && paintAreaTerminals) ||
\r
155 (paintHoverPointTerminals && paintPointTerminals) ) {
\r
156 update(highlightEnabled());
\r
161 protected G2DParentNode node = null;
\r
164 public void initSG(G2DParentNode parent) {
\r
165 node = parent.addNode("hovering terminals", G2DParentNode.class);
\r
166 node.setZIndex(PAINT_PRIORITY);
\r
170 public void cleanupSG() {
\r
175 public void update(boolean enabled) {
\r
179 boolean repaint = false;
\r
180 if(node == null) return;
\r
181 if(node.getNodeCount() > 0) {
\r
182 node.removeNodes();
\r
187 // Paint terminals normally
\r
188 if (paintAreaTerminals || paintPointTerminals) {
\r
189 List<TerminalInfo> pickedTerminals = TerminalUtil.pickTerminals(diagram, null, paintPointTerminals, paintAreaTerminals);
\r
190 paintTerminals(node, Color.BLUE, diagram, null, pickedTerminals, null);
\r
191 if(pickedTerminals.size() > 0) repaint = true;
\r
194 if (paintHoverAreaTerminals || paintHoverPointTerminals) {
\r
195 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
\r
197 AffineTransform invTx = util.getInverseTransform();
\r
198 if (invTx == null) {
\r
199 System.err.println("NO CANVAS TRANSFORM INVERSE AVAILABLE, CANVAS TRANSFORM IS: " + util.getTransform());
\r
204 for (MouseInfo mi : mice.getMiceInfo().values()) {
\r
205 Rectangle2D controlPickRect = getPickRectangle(mi.controlPosition.getX(), mi.controlPosition.getY());
\r
206 Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, invTx);
\r
208 List<TerminalInfo> tis = TerminalUtil.pickTerminals(diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals);
\r
209 paintTerminals(node, Color.RED, diagram, canvasPickRect.getBounds2D(), tis, strategy);
\r
210 if(tis.size() > 0) repaint = true;
\r
219 public void paintTerminals(G2DParentNode parent, Color color, IDiagram diagram, Rectangle2D pickRect, Collection<TerminalInfo> tis, TerminalHoverStrategy strategy) {
\r
220 if (tis.isEmpty()) {
\r
223 G2DParentNode node = parent.getOrCreateNode(""+tis.hashCode(), G2DParentNode.class);
\r
225 double minDist = Double.MAX_VALUE;
\r
226 double maxDist = 0;
\r
227 TerminalInfo nearest = null;
\r
228 if (pickRect != null) {
\r
229 for (TerminalInfo ti : tis) {
\r
230 double dx = ti.posDia.getTranslateX() - pickRect.getCenterX();
\r
231 double dy = ti.posDia.getTranslateY() - pickRect.getCenterY();
\r
232 double dist = Math.sqrt(dx*dx+dy*dy);
\r
233 if (dist > maxDist) {
\r
236 if (dist < minDist) {
\r
243 for (TerminalInfo ti : tis) {
\r
244 if (strategy != null && !strategy.highlight(ti))
\r
246 Shape shape = ti.shape != null ? ti.shape : getTerminalShape();
\r
247 //System.out.println("painting terminal " + ti + ": " + shape);
\r
248 ShapeNode sn = node.getOrCreateNode("terminal_"+ti.hashCode(), ShapeNode.class);
\r
249 sn.setShape(shape);
\r
250 sn.setStroke(STROKE25);
\r
251 sn.setScaleStroke(true);
\r
252 if (pickRect != null) {
\r
253 Color blendedColor = color;
\r
254 if (ti != nearest) {
\r
255 double dx = ti.posDia.getTranslateX() - pickRect.getCenterX();
\r
256 double dy = ti.posDia.getTranslateY() - pickRect.getCenterY();
\r
257 double dist = Math.sqrt(dx*dx+dy*dy);
\r
258 double normalizedDistance = dist / maxDist;
\r
259 final double maxFade = 0.5;
\r
260 float alpha = (float)(1 - normalizedDistance*maxFade);
\r
261 blendedColor = ColorUtil.withAlpha(ColorUtil.blend(color, Color.WHITE, normalizedDistance*maxFade), alpha);
\r
263 sn.setColor(blendedColor);
\r
265 sn.setColor(color);
\r
267 sn.setTransform(ti.posDia);
\r
272 public Rectangle2D getTerminalShape() {
\r
273 double pickDist = pointerInteractor.getPickDistance();
\r
274 return new Rectangle2D.Double(-pickDist - 0.5, -pickDist - 0.5, pickDist * 2 + 1, pickDist * 2 + 1);
\r
277 public Rectangle2D getPickRectangle(double x, double y) {
\r
278 double pickDist = pointerInteractor.getPickDistance();
\r
279 Rectangle2D controlPickRect = new Rectangle2D.Double(x-pickDist, y-pickDist, pickDist*2+1, pickDist*2+1);
\r
280 return controlPickRect;
\r
284 Path2D.Double cross = new Path2D.Double();
\r
286 cross.moveTo(-s, -s);
\r
287 cross.lineTo(s, s);
\r
288 cross.moveTo(-s, s);
\r
289 cross.lineTo(s, -s);
\r
290 TERMINAL_SHAPE = cross;
\r
293 IHintListener hoverStrategyListener = new HintListenerAdapter() {
\r
295 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
296 hoverStrategyChanged((TerminalHoverStrategy) newValue);
\r
300 protected void hoverStrategyChanged(TerminalHoverStrategy strategy) {
\r
301 update(strategy != null ? strategy.highlightEnabled() : false);
\r