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;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Paints terminals of elements.
52 * @author Toni Kalajainen
54 public class TerminalPainter extends AbstractDiagramParticipant {
56 private static final Logger LOGGER = LoggerFactory.getLogger(TerminalPainter.class);
57 public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 10;
59 public interface TerminalHoverStrategy {
62 * @return <code>true</code> if highlighting is enabled at the moment in
63 * general. This may depend on the current modifier key state
66 boolean highlightEnabled();
69 * Checks whether the specified terminal should be highlighted at the
70 * moment or not. Whether to highlight or not may depend for example on
71 * the current modifier key state.
76 boolean highlight(TerminalInfo ti);
79 public static abstract class ChainedHoverStrategy implements TerminalHoverStrategy {
80 TerminalHoverStrategy orig;
81 public ChainedHoverStrategy(TerminalHoverStrategy orig) {
85 public boolean highlightEnabled() {
86 return orig == null ? false : orig.highlightEnabled();
89 public final boolean highlight(TerminalInfo ti) {
90 boolean ret = canHighlight(ti);
91 return (ret || orig == null) ? ret : orig.highlight(ti);
93 public abstract boolean canHighlight(TerminalInfo ti);
97 * If this hint is set to a Callable<Boolean>, the terminal painter will use
98 * the callable to evaluate whether it should highlight terminal hovers or
101 public static final Key TERMINAL_HOVER_STRATEGY = new KeyOf(TerminalHoverStrategy.class);
103 // private static final Stroke STROKE1 = new BasicStroke(1.0f);
104 // private static final Stroke STROKE15 = new BasicStroke(1.5f);
105 private static final Stroke STROKE25 = new BasicStroke(2.5f);
107 // private final static Stroke TERMINAL_STROKE = new BasicStroke(1.0f);
108 public static final Shape TERMINAL_SHAPE;
111 protected TransformUtil util;
113 protected MouseUtil mice;
116 protected PointerInteractor pointerInteractor;
118 protected boolean paintPointTerminals;
119 protected boolean paintAreaTerminals;
120 protected boolean paintHoverPointTerminals;
121 protected boolean paintHoverAreaTerminals;
123 public TerminalPainter(boolean paintPointTerminals, boolean paintHoverPointTerminals, boolean paintAreaTerminals, boolean paintHoverAreaTerminals)
125 this.paintAreaTerminals = paintAreaTerminals;
126 this.paintPointTerminals = paintPointTerminals;
127 this.paintHoverAreaTerminals = paintHoverAreaTerminals;
128 this.paintHoverPointTerminals = paintHoverPointTerminals;
132 public void addedToContext(ICanvasContext ctx) {
133 super.addedToContext(ctx);
135 ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);
139 public void removedFromContext(ICanvasContext ctx) {
140 ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);
142 super.removedFromContext(ctx);
145 public boolean highlightEnabled() {
146 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
147 return strategy != null ? strategy.highlightEnabled() : true;
150 public boolean highlightTerminal(TerminalInfo ti) {
151 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
152 return strategy != null ? strategy.highlight(ti) : true;
155 @EventHandler(priority = 0)
156 public boolean handleMove(MouseMovedEvent me) {
157 if ( (paintHoverAreaTerminals && paintAreaTerminals) ||
158 (paintHoverPointTerminals && paintPointTerminals) ) {
159 update(highlightEnabled());
164 protected G2DParentNode node = null;
167 public void initSG(G2DParentNode parent) {
168 node = parent.addNode("hovering terminals", G2DParentNode.class);
169 node.setZIndex(PAINT_PRIORITY);
173 public void cleanupSG() {
178 public void update(boolean enabled) {
182 boolean repaint = false;
183 if(node == null) return;
184 if(node.getNodeCount() > 0) {
190 // Paint terminals normally
191 if (paintAreaTerminals || paintPointTerminals) {
192 List<TerminalInfo> pickedTerminals = TerminalUtil.pickTerminals(getContext(), diagram, null, paintPointTerminals, paintAreaTerminals);
193 paintTerminals(node, Color.BLUE, diagram, null, pickedTerminals, null);
194 if(pickedTerminals.size() > 0) repaint = true;
197 if (paintHoverAreaTerminals || paintHoverPointTerminals) {
198 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
200 AffineTransform invTx = util.getInverseTransform();
202 LOGGER.warn("NO CANVAS TRANSFORM INVERSE AVAILABLE, CANVAS TRANSFORM IS: " + util.getTransform());
207 for (MouseInfo mi : mice.getMiceInfo().values()) {
208 Rectangle2D controlPickRect = getPickRectangle(mi.controlPosition.getX(), mi.controlPosition.getY());
209 Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, invTx);
211 List<TerminalInfo> tis = TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals);
212 paintTerminals(node, Color.RED, diagram, canvasPickRect.getBounds2D(), tis, strategy);
213 if(tis.size() > 0) repaint = true;
222 public void paintTerminals(G2DParentNode parent, Color color, IDiagram diagram, Rectangle2D pickRect, Collection<TerminalInfo> tis, TerminalHoverStrategy strategy) {
226 G2DParentNode node = parent.getOrCreateNode(""+tis.hashCode(), G2DParentNode.class);
228 double minDist = Double.MAX_VALUE;
230 TerminalInfo nearest = null;
231 if (pickRect != null) {
232 for (TerminalInfo ti : tis) {
233 double dx = ti.posDia.getTranslateX() - pickRect.getCenterX();
234 double dy = ti.posDia.getTranslateY() - pickRect.getCenterY();
235 double dist = Math.sqrt(dx*dx+dy*dy);
236 if (dist > maxDist) {
239 if (dist < minDist) {
246 for (TerminalInfo ti : tis) {
247 if (strategy != null && !strategy.highlight(ti))
249 Shape shape = ti.shape != null ? ti.shape : getTerminalShape();
250 //System.out.println("painting terminal " + ti + ": " + shape);
251 ShapeNode sn = node.getOrCreateNode("terminal_"+ti.hashCode(), ShapeNode.class);
253 sn.setStroke(STROKE25);
254 sn.setScaleStroke(true);
255 if (pickRect != null) {
256 Color blendedColor = color;
258 double dx = ti.posDia.getTranslateX() - pickRect.getCenterX();
259 double dy = ti.posDia.getTranslateY() - pickRect.getCenterY();
260 double dist = Math.sqrt(dx*dx+dy*dy);
261 double normalizedDistance = dist / maxDist;
262 final double maxFade = 0.5;
263 float alpha = (float)(1 - normalizedDistance*maxFade);
264 blendedColor = ColorUtil.withAlpha(ColorUtil.blend(color, Color.WHITE, normalizedDistance*maxFade), alpha);
266 sn.setColor(blendedColor);
270 sn.setTransform(ti.posDia);
275 public Rectangle2D getTerminalShape() {
276 double pickDist = pointerInteractor.getPickDistance();
277 return new Rectangle2D.Double(-pickDist - 0.5, -pickDist - 0.5, pickDist * 2 + 1, pickDist * 2 + 1);
280 public Rectangle2D getPickRectangle(double x, double y) {
281 double pickDist = pointerInteractor.getPickDistance();
282 Rectangle2D controlPickRect = new Rectangle2D.Double(x-pickDist, y-pickDist, pickDist*2+1, pickDist*2+1);
283 return controlPickRect;
287 Path2D.Double cross = new Path2D.Double();
289 cross.moveTo(-s, -s);
293 TERMINAL_SHAPE = cross;
296 IHintListener hoverStrategyListener = new HintListenerAdapter() {
298 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
299 hoverStrategyChanged((TerminalHoverStrategy) newValue);
303 protected void hoverStrategyChanged(TerminalHoverStrategy strategy) {
304 update(strategy != null ? strategy.highlightEnabled() : false);