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