]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/SliderClass.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / diagram / SliderClass.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.modeling.ui.diagram;\r
13 \r
14 import java.awt.Graphics2D;\r
15 import java.awt.Shape;\r
16 import java.awt.event.ActionEvent;\r
17 import java.awt.event.ActionListener;\r
18 import java.awt.geom.AffineTransform;\r
19 import java.awt.geom.Point2D;\r
20 import java.awt.geom.Rectangle2D;\r
21 import java.util.Collection;\r
22 import java.util.EnumSet;\r
23 \r
24 import javax.swing.JSlider;\r
25 import javax.vecmath.Vector2d;\r
26 \r
27 import org.simantics.g2d.diagram.IDiagram;\r
28 import org.simantics.g2d.element.ElementClass;\r
29 import org.simantics.g2d.element.ElementHints;\r
30 import org.simantics.g2d.element.ElementUtils;\r
31 import org.simantics.g2d.element.IElement;\r
32 import org.simantics.g2d.element.SceneGraphNodeKey;\r
33 import org.simantics.g2d.element.handler.ElementHandler;\r
34 import org.simantics.g2d.element.handler.InternalSize;\r
35 import org.simantics.g2d.element.handler.LifeCycle;\r
36 import org.simantics.g2d.element.handler.Move;\r
37 import org.simantics.g2d.element.handler.Outline;\r
38 import org.simantics.g2d.element.handler.Rotate;\r
39 import org.simantics.g2d.element.handler.Scale;\r
40 import org.simantics.g2d.element.handler.SceneGraph;\r
41 import org.simantics.g2d.element.handler.StaticSymbol;\r
42 import org.simantics.g2d.element.handler.TextEditor;\r
43 import org.simantics.g2d.element.handler.Transform;\r
44 import org.simantics.g2d.element.handler.TextEditor.Modifier;\r
45 import org.simantics.g2d.element.handler.impl.BorderColorImpl;\r
46 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;\r
47 import org.simantics.g2d.element.handler.impl.StaticSymbolImpl;\r
48 import org.simantics.g2d.element.handler.impl.TextColorImpl;\r
49 import org.simantics.g2d.element.handler.impl.TextEditorImpl;\r
50 import org.simantics.g2d.element.handler.impl.TextFontImpl;\r
51 import org.simantics.g2d.element.handler.impl.TextImpl;\r
52 import org.simantics.g2d.elementclass.MonitorHandler;\r
53 import org.simantics.g2d.image.Image;\r
54 import org.simantics.g2d.image.ProviderUtils;\r
55 import org.simantics.g2d.image.impl.AbstractImage;\r
56 import org.simantics.scenegraph.Node;\r
57 import org.simantics.scenegraph.g2d.G2DParentNode;\r
58 import org.simantics.scenegraph.swing.SliderNode;\r
59 import org.simantics.utils.datastructures.cache.IFactory;\r
60 import org.simantics.utils.datastructures.cache.IProvider;\r
61 import org.simantics.utils.datastructures.cache.ProvisionException;\r
62 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
63 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
64 import org.simantics.utils.strings.format.MetricsFormat;\r
65 import org.simantics.utils.strings.format.MetricsFormatList;\r
66 \r
67 /**\r
68  * @author J-P Laine\r
69  */\r
70 public class SliderClass {\r
71     public static final Key  KEY_SLIDER_RESOURCE_PATH = new KeyOf(Collection.class, "SLIDER_RESOURCE_PATH");\r
72     public static final Key  KEY_SLIDER_COMPONENT     = new KeyOf(Object.class, "SLIDER_COMPONENT");\r
73     public static final Key  KEY_SLIDER_SUFFIX        = new KeyOf(String.class, "SLIDER_SUFFIX");\r
74     public static final Key  KEY_SLIDER_RANGE         = new KeyOf(Range.class, "SLIDER_SUBSTITUTIONS");\r
75     public static final Key  KEY_SLIDER_GC            = new KeyOf(Graphics2D.class, "SLIDER_GC");\r
76     public static final Key  KEY_SLIDER_VALUE             = new KeyOf(Double.class, "SLIDER_VALUE");\r
77     public static final Key  KEY_TOOLTIP_TEXT             = new KeyOf(String.class, "TOOLTIP_TEXT");\r
78     public static final Key  KEY_NUMBER_FORMAT            = new KeyOf(MetricsFormat.class, "NUMBER_FORMAT");\r
79 \r
80     /**\r
81      * If this hint is defined, the monitor will force its x-axis to match this\r
82      * angle. If this hint doesn't exist, the monitor will not force x-axis\r
83      * orientation.\r
84      */\r
85     public static final Key  KEY_DIRECTION             = new KeyOf(Double.class, "SLIDER_DIRECTION");\r
86 \r
87     public static final Key  KEY_SG_NODE             = new SceneGraphNodeKey(Node.class, "SLIDER_SG_NODE");\r
88     public final static MetricsFormat DEFAULT_NUMBER_FORMAT  = MetricsFormatList.METRICS_DECIMAL;\r
89 \r
90     public static class Range<T> {\r
91         T min;\r
92         T max;\r
93         public Range(T min, T max) {\r
94                 this.min = min;\r
95                 this.max = max;\r
96         }\r
97         \r
98         public T getMin() {\r
99                 return min;\r
100         }\r
101         \r
102         public T getMax() {\r
103                 return max;\r
104         }\r
105     }\r
106     \r
107     static MetricsFormat getNumberFormat(IElement e) {\r
108         return ElementUtils.getHintOrDefault(e, KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);\r
109     }\r
110 \r
111     static void setNumberFormat(IElement e, MetricsFormat f) {\r
112         ElementUtils.setOrRemoveHint(e, KEY_NUMBER_FORMAT, f);\r
113     }\r
114 \r
115     public static class SliderHandlerImpl implements MonitorHandler {\r
116         private static final long          serialVersionUID = -4258875745321808416L;\r
117         public static final MonitorHandler INSTANCE         = new SliderHandlerImpl();\r
118     }\r
119 \r
120     static class Initializer implements LifeCycle {\r
121         private static final long serialVersionUID = 4404942036933073584L;\r
122 \r
123         IElement parentElement;\r
124         Range<Double> range;\r
125         Object component;\r
126         String suffix;\r
127         Double value;\r
128         Boolean hack;\r
129 \r
130         Initializer(IElement parentElement, Range<Double> range, Double value, Object component, String suffix, Boolean hack) {\r
131             this.parentElement = parentElement;\r
132             this.range = range;\r
133             this.component = component;\r
134             this.suffix = suffix;\r
135             this.value = value;\r
136             this.hack = hack;\r
137         }\r
138 \r
139         @Override\r
140         public void onElementActivated(IDiagram d, IElement e) {\r
141             if(!hack) {\r
142                 hack = true;\r
143                 Point2D parentPos = ElementUtils.getPos(parentElement);\r
144                 Point2D thisPos = ElementUtils.getPos(e);\r
145                 Move move = e.getElementClass().getSingleItem(Move.class);\r
146                 move.moveTo(e, thisPos.getX() - parentPos.getX(), thisPos.getY() - parentPos.getY());\r
147             }\r
148         }\r
149         \r
150         @Override\r
151         public void onElementCreated(IElement e) {\r
152             if(parentElement != null) e.setHint(ElementHints.KEY_PARENT_ELEMENT, parentElement);\r
153             if(range != null) e.setHint(KEY_SLIDER_RANGE, range);\r
154             if(component != null) e.setHint(KEY_SLIDER_COMPONENT, component);\r
155             if(suffix != null) e.setHint(KEY_SLIDER_SUFFIX, suffix);\r
156             if(value != null) e.setHint(KEY_SLIDER_VALUE, value);\r
157 \r
158             e.setHint(KEY_DIRECTION, 0.0);\r
159             e.setHint(KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);\r
160         }\r
161         @Override\r
162         public void onElementDeactivated(IDiagram d, IElement e) {\r
163         }\r
164         @Override\r
165         public void onElementDestroyed(IElement e) {\r
166         }\r
167     };\r
168 \r
169     public static void update(IElement e) {\r
170         SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class);\r
171         node.update(e);\r
172     }\r
173 \r
174     public static void cleanup(IElement e) {\r
175         SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class);\r
176         node.cleanup(e);\r
177     }\r
178 \r
179     public static class SliderSGNode implements SceneGraph, InternalSize, Outline {\r
180         private static final long serialVersionUID = -106278359626957687L;\r
181 \r
182         public static final SliderSGNode INSTANCE = new SliderSGNode();\r
183 \r
184         @Override\r
185         public void init(final IElement e, final G2DParentNode parent) {\r
186             // Create node if it doesn't exist yet\r
187             SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE);\r
188             if(node == null || node.getBounds() == null || node.getParent() != parent) {\r
189                 node = parent.addNode(ElementUtils.generateNodeId(e), SliderNode.class);\r
190                 e.setHint(KEY_SG_NODE, node);\r
191                 node.setActionListener(new ActionListener() {\r
192                     @Override\r
193                     public void actionPerformed(ActionEvent event) {\r
194                         TextEditor editor = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);\r
195                         if(editor != null) {\r
196                             Modifier modifier = editor.getModifier(e);\r
197                             if(modifier != null)\r
198                                 modifier.modify(e, event.getActionCommand());\r
199                         }\r
200                     }});\r
201                 node.setBounds(new Rectangle2D.Double(0, 0, 32, 100));\r
202 \r
203 //                Rectangle2D bounds = (Rectangle2D)e.getHint(ElementHints.KEY_BOUNDS);\r
204 //                if(bounds != null) node.setBounds(bounds);\r
205 //                String tooltip = e.getHint(KEY_TOOLTIP_TEXT);\r
206                 Range<Double> range = e.getHint(KEY_SLIDER_RANGE);\r
207                 Double value = e.getHint(KEY_SLIDER_VALUE);\r
208                 \r
209                     node.setMinimum((int)Math.round(range.getMin())); // FIXME\r
210                     node.setMaximum((int)Math.round(range.getMax())); \r
211                     node.setValue((int)Math.round(value));\r
212                     node.setMajorTickSpacing(10);\r
213                     node.setMinorTickSpacing(5);\r
214                     node.setPaintLabels(true);\r
215                     node.setOrientation(JSlider.VERTICAL);\r
216             }\r
217             update(e);\r
218         }\r
219 \r
220         public void update(IElement e) {\r
221             Double value = e.getHint(KEY_SLIDER_VALUE);\r
222 \r
223             SliderNode node = (SliderNode) e.getHint(KEY_SG_NODE);\r
224             if (node != null && value != null) {\r
225                 node.setValue((int)Math.round(value));\r
226             }\r
227         }\r
228 \r
229         @Override\r
230         public void cleanup(IElement e) {\r
231                 SliderNode node = (SliderNode)e.removeHint(KEY_SG_NODE);\r
232             if (node != null)\r
233                 node.remove();\r
234         }\r
235 \r
236         @Override\r
237         public Rectangle2D getBounds(IElement e, Rectangle2D size) {\r
238             Rectangle2D shape = new Rectangle2D.Double(0, 0, 0, 0);\r
239 \r
240             SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE);\r
241             if(node != null && node.getBounds() != null) {\r
242                 shape = node.getBounds().getBounds2D();\r
243             }\r
244 \r
245             if(size != null) size.setRect(shape);\r
246             return shape;\r
247         }\r
248 \r
249         @Override\r
250         public Shape getElementShape(IElement e) {\r
251             Shape shape = new Rectangle2D.Double(0, 0, 0, 0);\r
252 \r
253             SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE);\r
254             if(node != null && node.getBounds() != null) {\r
255                 shape = node.getBounds();\r
256             }\r
257 \r
258             return shape;\r
259         }\r
260 \r
261     }\r
262 \r
263     public static class Transformer implements Transform, Move, Rotate, Scale {\r
264 \r
265         private static final long serialVersionUID = -3704887325602085677L;\r
266 \r
267         public static final Transformer INSTANCE = new Transformer(null);\r
268 \r
269         Double aspectRatio;\r
270 \r
271         public Transformer() {\r
272             this(null);\r
273         }\r
274 \r
275         public Transformer(Double aspectRatio) {\r
276             this.aspectRatio = aspectRatio;\r
277         }\r
278 \r
279         @Override\r
280         public Double getFixedAspectRatio(IElement e) {\r
281             return aspectRatio;\r
282         }\r
283 \r
284         @Override\r
285         public Point2D getScale(IElement e) {\r
286             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);\r
287             return _getScale(at);\r
288         }\r
289 \r
290         @Override\r
291         public void setScale(IElement e, Point2D newScale) {\r
292             // Doesn't work for monitors.\r
293             Point2D oldScale = getScale(e);\r
294             double sx = newScale.getX() / oldScale.getX();\r
295             double sy = newScale.getY() / oldScale.getY();\r
296             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);\r
297             at = new AffineTransform(at);\r
298             at.scale(sx, sy);\r
299             e.setHint(ElementHints.KEY_TRANSFORM, at);\r
300         }\r
301 \r
302         @Override\r
303         public Point2D getMaximumScale(IElement e) {\r
304             return null;\r
305         }\r
306 \r
307         @Override\r
308         public Point2D getMinimumScale(IElement e) {\r
309             return null;\r
310         }\r
311 \r
312         private static Point2D _getScale(AffineTransform at) {\r
313             double m00 = at.getScaleX();\r
314             double m11 = at.getScaleY();\r
315             double m10 = at.getShearY();\r
316             double m01 = at.getShearX();\r
317             // Project unit vector to canvas\r
318             double sx = Math.sqrt(m00 * m00 + m10 * m10);\r
319             double sy = Math.sqrt(m01 * m01 + m11 * m11);\r
320             return new Point2D.Double(sx, sy);\r
321         }\r
322 \r
323         @Override\r
324         public void rotate(IElement e, double theta, Point2D origin) {\r
325             if (Double.isNaN(theta)) return;\r
326             theta = Math.toDegrees(theta);\r
327             Double angle = e.getHint(KEY_DIRECTION);\r
328             double newAngle = angle != null ? angle+theta : theta;\r
329             newAngle = Math.IEEEremainder(newAngle, 360.0);\r
330             e.setHint(KEY_DIRECTION, newAngle);\r
331         }\r
332 \r
333         @Override\r
334         public double getAngle(IElement e) {\r
335             Double angle = e.getHint(KEY_DIRECTION);\r
336             return angle != null ? Math.toRadians(angle) : 0;\r
337         }\r
338 \r
339         @Override\r
340         public Point2D getPosition(IElement e) {\r
341             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);\r
342             Point2D p = new Point2D.Double(at.getTranslateX(), at.getTranslateY());\r
343             return p;\r
344         }\r
345 \r
346         @Override\r
347         public void moveTo(IElement e, double x, double y) {\r
348             AffineTransform origAt = e.getHint(ElementHints.KEY_TRANSFORM);\r
349             double oldX = origAt.getTranslateX();\r
350             double oldY = origAt.getTranslateY();\r
351             AffineTransform move = AffineTransform.getTranslateInstance(x-oldX, y-oldY);\r
352             AffineTransform at2 = new AffineTransform(origAt);\r
353             at2.preConcatenate(move);\r
354             e.setHint(ElementHints.KEY_TRANSFORM, at2);\r
355         }\r
356 \r
357         @Override\r
358         public AffineTransform getTransform(IElement e) {\r
359             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);\r
360 \r
361             IElement parentElement = e.getHint(ElementHints.KEY_PARENT_ELEMENT);\r
362             if (parentElement == null)\r
363                 return at;\r
364 \r
365             Transform parentTransform = parentElement.getElementClass().getSingleItem(Transform.class);\r
366             assert(parentTransform!=null);\r
367 \r
368             AffineTransform result = (AffineTransform)at.clone();\r
369             result.preConcatenate(parentTransform.getTransform(parentElement));\r
370 \r
371             return result;\r
372         }\r
373 \r
374         @Override\r
375         public void setTransform(IElement e, AffineTransform at) {\r
376             e.setHint(ElementHints.KEY_TRANSFORM, at.clone());\r
377         }\r
378     }\r
379 \r
380     static double getOrientationDelta(IElement e, AffineTransform tr) {\r
381         Double angle = e.getHint(KEY_DIRECTION);\r
382         if (angle == null || Double.isNaN(angle))\r
383             return Double.NaN;\r
384         double angrad = Math.toRadians(angle);\r
385 \r
386         Vector2d forcedAxis = new Vector2d(Math.cos(angrad), Math.sin(angrad));\r
387         Vector2d x = new Vector2d(tr.getScaleX(), tr.getShearX());\r
388         forcedAxis.normalize();\r
389         x.normalize();\r
390         double cosa = forcedAxis.dot(x);\r
391         double delta = Math.acos(cosa);\r
392         return delta;\r
393     }\r
394 \r
395     static class SliderImageFactory implements IFactory<Image> {\r
396         private double staticScaleX = 1, staticScaleY = 1;\r
397 \r
398         public SliderImageFactory(double staticScaleX, double staticScaleY) {\r
399             this.staticScaleX = staticScaleX;\r
400             this.staticScaleY = staticScaleY;\r
401         }\r
402 \r
403         @Override\r
404         public Image get() throws ProvisionException {\r
405             return new AbstractImage() {\r
406                 Shape path = new Rectangle2D.Double(0, 0, 50*staticScaleX, 22*staticScaleY);\r
407 \r
408                 @Override\r
409                 public Rectangle2D getBounds() {\r
410                     return path.getBounds2D();\r
411                 }\r
412 \r
413                 @Override\r
414                 public EnumSet<Feature> getFeatures() {\r
415                     return EnumSet.of(Feature.Vector);\r
416                 }\r
417 \r
418                 @Override\r
419                 public Shape getOutline() {\r
420                     return path;\r
421                 }\r
422 \r
423                 @Override\r
424                 public Node init(G2DParentNode parent) {\r
425                     SliderNode node = parent.getOrCreateNode(""+hashCode(), SliderNode.class);\r
426                     node.setValue(0);\r
427                     node.setBounds(new Rectangle2D.Double(0, 0, 50, 22));\r
428                     node.setTransform(AffineTransform.getScaleInstance(staticScaleX, staticScaleY));\r
429                     return node;\r
430                 }\r
431             };\r
432         }\r
433     }\r
434 \r
435     static final IProvider<Image> SLIDER_IMAGE =\r
436         ProviderUtils.reference(\r
437                 ProviderUtils.cache(\r
438                         ProviderUtils.rasterize(\r
439                                 new SliderImageFactory(0.5, 0.5)\r
440                         )));\r
441 \r
442     static final StaticSymbol SLIDER_SYMBOL = new StaticSymbolImpl( SLIDER_IMAGE.get() );\r
443 \r
444     public static final ElementClass SLIDER_CLASS =\r
445         ElementClass.compile(\r
446                 SliderHandlerImpl.INSTANCE,\r
447                 Transformer.INSTANCE,\r
448                 BorderColorImpl.BLACK,\r
449                 SliderSGNode.INSTANCE,\r
450                 TextImpl.INSTANCE,\r
451                 TextEditorImpl.INSTANCE,\r
452                 TextFontImpl.DEFAULT,\r
453                 TextColorImpl.BLACK,\r
454                 SimpleElementLayers.INSTANCE,\r
455                 SLIDER_SYMBOL\r
456         );\r
457 \r
458     // staticScale{X,Y} define the scale of the static monitor image\r
459     public static ElementClass create(IElement parentElement, Range<Double> range, Double value, Object component, String suffix, double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) {\r
460         // Bit of a hack to be able to define the scale\r
461         IProvider<Image> staticSliderSymbolProvider = ProviderUtils.reference(\r
462                 ProviderUtils.cache(\r
463                         ProviderUtils\r
464                         .rasterize(\r
465                                 new SliderImageFactory(staticScaleX, staticScaleY))));\r
466         StaticSymbol staticSliderSymbol = new StaticSymbolImpl( staticSliderSymbolProvider.get() );\r
467         return ElementClass.compile(\r
468                 new Initializer(parentElement, range, value, component, suffix, parentElement != null ? false : true),\r
469                 SliderHandlerImpl.INSTANCE,\r
470                 Transformer.INSTANCE,\r
471                 BorderColorImpl.BLACK,\r
472                 SliderSGNode.INSTANCE,\r
473                 TextImpl.INSTANCE,\r
474                 TextEditorImpl.INSTANCE,\r
475                 TextFontImpl.DEFAULT,\r
476                 TextColorImpl.BLACK,\r
477                 SimpleElementLayers.INSTANCE,\r
478                 staticSliderSymbol\r
479         ).newClassWith(extraHandlers);\r
480     }\r
481 \r
482 }\r