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