]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / TerminalPainter.java
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
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.g2d.diagram.participant;\r
13 \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
23 \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
46 \r
47 /**\r
48  * Paints terminals of elements.\r
49  *\r
50  * @author Toni Kalajainen\r
51  */\r
52 public class TerminalPainter extends AbstractDiagramParticipant {\r
53 \r
54     public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 10;\r
55 \r
56     public interface TerminalHoverStrategy {\r
57         /**\r
58          * \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
61          *         for example.\r
62          */\r
63         boolean highlightEnabled();\r
64 \r
65         /**\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
69          * \r
70          * @param ti\r
71          * @return\r
72          */\r
73         boolean highlight(TerminalInfo ti);\r
74     };\r
75 \r
76     public static abstract class ChainedHoverStrategy implements TerminalHoverStrategy {\r
77         TerminalHoverStrategy orig;\r
78         public ChainedHoverStrategy(TerminalHoverStrategy orig) {\r
79             this.orig = orig;\r
80         }\r
81         @Override\r
82         public boolean highlightEnabled() {\r
83             return orig == null ? false : orig.highlightEnabled();\r
84         }\r
85         @Override\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
89         }\r
90         public abstract boolean canHighlight(TerminalInfo ti);\r
91     }\r
92 \r
93     /**\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
96      * not.\r
97      */\r
98     public static final Key          TERMINAL_HOVER_STRATEGY = new KeyOf(TerminalHoverStrategy.class);\r
99 \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
103 \r
104 //    private final static Stroke      TERMINAL_STROKE         = new BasicStroke(1.0f);\r
105     public static final Shape        TERMINAL_SHAPE;\r
106 \r
107     @Dependency\r
108         protected TransformUtil util;\r
109     @Dependency\r
110         protected MouseUtil mice;\r
111     \r
112     @Dependency\r
113     protected PointerInteractor pointerInteractor;\r
114 \r
115     protected boolean paintPointTerminals;\r
116     protected boolean paintAreaTerminals;\r
117     protected boolean paintHoverPointTerminals;\r
118     protected boolean paintHoverAreaTerminals;\r
119 \r
120     public TerminalPainter(boolean paintPointTerminals, boolean paintHoverPointTerminals, boolean paintAreaTerminals, boolean paintHoverAreaTerminals)\r
121     {\r
122         this.paintAreaTerminals = paintAreaTerminals;\r
123         this.paintPointTerminals = paintPointTerminals;\r
124         this.paintHoverAreaTerminals = paintHoverAreaTerminals;\r
125         this.paintHoverPointTerminals = paintHoverPointTerminals;\r
126     }\r
127 \r
128     @Override\r
129     public void addedToContext(ICanvasContext ctx) {\r
130         super.addedToContext(ctx);\r
131 \r
132         ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);\r
133     }\r
134 \r
135     @Override\r
136     public void removedFromContext(ICanvasContext ctx) {\r
137         ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener);\r
138 \r
139         super.removedFromContext(ctx);\r
140     }\r
141 \r
142     public boolean highlightEnabled() {\r
143         TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);\r
144         return strategy != null ? strategy.highlightEnabled() : true;\r
145     }\r
146 \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
150     }\r
151 \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
157         }\r
158         return false;\r
159     }\r
160 \r
161     protected G2DParentNode node = null;\r
162 \r
163     @SGInit\r
164     public void initSG(G2DParentNode parent) {\r
165         node = parent.addNode("hovering terminals", G2DParentNode.class);\r
166         node.setZIndex(PAINT_PRIORITY);\r
167     }\r
168 \r
169     @SGCleanup\r
170     public void cleanupSG() {\r
171         node.remove();\r
172         node = null;\r
173     }\r
174 \r
175     public void update(boolean enabled) {\r
176         if (isRemoved())\r
177             return;\r
178 \r
179         boolean repaint = false;\r
180         if(node == null) return;\r
181         if(node.getNodeCount() > 0) {\r
182             node.removeNodes();\r
183             repaint = true;\r
184         }\r
185         if (enabled) {\r
186 \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
192             }\r
193 \r
194             if (paintHoverAreaTerminals || paintHoverPointTerminals) {\r
195                 TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY);\r
196 \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
200                     return;\r
201                 }\r
202 \r
203                 // Pick terminals\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
207 \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
211                 }\r
212             }\r
213         }\r
214         if (repaint) {\r
215             setDirty();\r
216         }\r
217     }\r
218 \r
219     public void paintTerminals(G2DParentNode parent, Color color, IDiagram diagram, Rectangle2D pickRect, Collection<TerminalInfo> tis, TerminalHoverStrategy strategy) {\r
220         if (tis.isEmpty()) {\r
221             return;\r
222         }\r
223         G2DParentNode node = parent.getOrCreateNode(""+tis.hashCode(), G2DParentNode.class);\r
224 \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
234                     maxDist = dist;\r
235                 }\r
236                 if (dist < minDist) {\r
237                     minDist = dist;\r
238                     nearest = ti;\r
239                 }\r
240             }\r
241         }\r
242 \r
243         for (TerminalInfo ti : tis) {\r
244             if (strategy != null && !strategy.highlight(ti))\r
245                 continue;\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
262                 }\r
263                 sn.setColor(blendedColor);\r
264             } else {\r
265                 sn.setColor(color);\r
266             }\r
267             sn.setTransform(ti.posDia);\r
268             sn.setFill(false);\r
269         }\r
270     }\r
271     \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
275     }\r
276     \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
281     }\r
282 \r
283     static {\r
284         Path2D.Double cross = new Path2D.Double();\r
285         double s = 2;\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
291     }\r
292 \r
293     IHintListener hoverStrategyListener = new HintListenerAdapter() {\r
294         @Override\r
295         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
296             hoverStrategyChanged((TerminalHoverStrategy) newValue);\r
297         }\r
298     };\r
299 \r
300     protected void hoverStrategyChanged(TerminalHoverStrategy strategy) {\r
301         update(strategy != null ? strategy.highlightEnabled() : false);\r
302     }\r
303 \r
304 }\r