]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java
G2DParentNode handles "undefined" child bounds separately
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / TerminalPainter.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.diagram.participant;
13
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;
23
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;
48
49 /**
50  * Paints terminals of elements.
51  *
52  * @author Toni Kalajainen
53  */
54 public class TerminalPainter extends AbstractDiagramParticipant {
55
56     private static final Logger LOGGER = LoggerFactory.getLogger(TerminalPainter.class);
57     public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 10;
58
59     public interface TerminalHoverStrategy {
60         /**
61          * 
62          * @return <code>true</code> if highlighting is enabled at the moment in
63          *         general. This may depend on the current modifier key state
64          *         for example.
65          */
66         boolean highlightEnabled();
67
68         /**
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.
72          * 
73          * @param ti
74          * @return
75          */
76         boolean highlight(TerminalInfo ti);
77     };
78
79     public static abstract class ChainedHoverStrategy implements TerminalHoverStrategy {
80         TerminalHoverStrategy orig;
81         public ChainedHoverStrategy(TerminalHoverStrategy orig) {
82             this.orig = orig;
83         }
84         @Override
85         public boolean highlightEnabled() {
86             return orig == null ? false : orig.highlightEnabled();
87         }
88         @Override
89         public final boolean highlight(TerminalInfo ti) {
90             boolean ret = canHighlight(ti);
91             return (ret || orig == null) ? ret : orig.highlight(ti);
92         }
93         public abstract boolean canHighlight(TerminalInfo ti);
94     }
95
96     /**
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
99      * not.
100      */
101     public static final Key          TERMINAL_HOVER_STRATEGY = new KeyOf(TerminalHoverStrategy.class);
102
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);
106
107 //    private final static Stroke      TERMINAL_STROKE         = new BasicStroke(1.0f);
108     public static final Shape        TERMINAL_SHAPE;
109
110     @Dependency
111         protected TransformUtil util;
112     @Dependency
113         protected MouseUtil mice;
114     
115     @Dependency
116     protected PointerInteractor pointerInteractor;
117
118     protected boolean paintPointTerminals;
119     protected boolean paintAreaTerminals;
120     protected boolean paintHoverPointTerminals;
121     protected boolean paintHoverAreaTerminals;
122
123     public TerminalPainter(boolean paintPointTerminals, boolean paintHoverPointTerminals, boolean paintAreaTerminals, boolean paintHoverAreaTerminals)
124     {
125         this.paintAreaTerminals = paintAreaTerminals;
126         this.paintPointTerminals = paintPointTerminals;
127         this.paintHoverAreaTerminals = paintHoverAreaTerminals;
128         this.paintHoverPointTerminals = paintHoverPointTerminals;
129     }
130
131     @Override
132     public void addedToContext(ICanvasContext ctx) {
133         super.addedToContext(ctx);
134
135         ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);
136     }
137
138     @Override
139     public void removedFromContext(ICanvasContext ctx) {
140         ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);
141
142         super.removedFromContext(ctx);
143     }
144
145     public boolean highlightEnabled() {
146         TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
147         return strategy != null ? strategy.highlightEnabled() : true;
148     }
149
150     public boolean highlightTerminal(TerminalInfo ti) {
151         TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
152         return strategy != null ? strategy.highlight(ti) : true;
153     }
154
155     @EventHandler(priority = 0)
156     public boolean handleMove(MouseMovedEvent me) {
157         if ( (paintHoverAreaTerminals && paintAreaTerminals) ||
158                 (paintHoverPointTerminals && paintPointTerminals) ) {
159             update(highlightEnabled());
160         }
161         return false;
162     }
163
164     protected G2DParentNode node = null;
165
166     @SGInit
167     public void initSG(G2DParentNode parent) {
168         node = parent.addNode("hovering terminals", G2DParentNode.class);
169         node.setZIndex(PAINT_PRIORITY);
170     }
171
172     @SGCleanup
173     public void cleanupSG() {
174         node.remove();
175         node = null;
176     }
177
178     public void update(boolean enabled) {
179         if (isRemoved())
180             return;
181
182         boolean repaint = false;
183         if(node == null) return;
184         if(node.getNodeCount() > 0) {
185             node.removeNodes();
186             repaint = true;
187         }
188         if (enabled) {
189
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;
195             }
196
197             if (paintHoverAreaTerminals || paintHoverPointTerminals) {
198                 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);
199
200                 AffineTransform invTx = util.getInverseTransform();
201                 if (invTx == null) {
202                     LOGGER.warn("NO CANVAS TRANSFORM INVERSE AVAILABLE, CANVAS TRANSFORM IS: " + util.getTransform());
203                     return;
204                 }
205
206                 // Pick terminals
207                 for (MouseInfo mi : mice.getMiceInfo().values()) {
208                         Rectangle2D controlPickRect = getPickRectangle(mi.controlPosition.getX(), mi.controlPosition.getY());
209                     Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, invTx);
210
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;
214                 }
215             }
216         }
217         if (repaint) {
218             setDirty();
219         }
220     }
221
222     public void paintTerminals(G2DParentNode parent, Color color, IDiagram diagram, Rectangle2D pickRect, Collection<TerminalInfo> tis, TerminalHoverStrategy strategy) {
223         if (tis.isEmpty()) {
224             return;
225         }
226         G2DParentNode node = parent.getOrCreateNode(""+tis.hashCode(), G2DParentNode.class);
227
228         double minDist = Double.MAX_VALUE;
229         double maxDist = 0;
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) {
237                     maxDist = dist;
238                 }
239                 if (dist < minDist) {
240                     minDist = dist;
241                     nearest = ti;
242                 }
243             }
244         }
245
246         for (TerminalInfo ti : tis) {
247             if (strategy != null && !strategy.highlight(ti))
248                 continue;
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);
252             sn.setShape(shape);
253             sn.setStroke(STROKE25);
254             sn.setScaleStroke(true);
255             if (pickRect != null) {
256                 Color blendedColor = color;
257                 if (ti != nearest) {
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);
265                 }
266                 sn.setColor(blendedColor);
267             } else {
268                 sn.setColor(color);
269             }
270             sn.setTransform(ti.posDia);
271             sn.setFill(false);
272         }
273     }
274     
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);
278     }
279     
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;
284     }
285
286     static {
287         Path2D.Double cross = new Path2D.Double();
288         double s = 2;
289         cross.moveTo(-s, -s);
290         cross.lineTo(s, s);
291         cross.moveTo(-s, s);
292         cross.lineTo(s, -s);
293         TERMINAL_SHAPE = cross;
294     }
295
296     IHintListener hoverStrategyListener = new HintListenerAdapter() {
297         @Override
298         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
299             hoverStrategyChanged((TerminalHoverStrategy) newValue);
300         }
301     };
302
303     protected void hoverStrategyChanged(TerminalHoverStrategy strategy) {
304         update(strategy != null ? strategy.highlightEnabled() : false);
305     }
306
307 }