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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.modeling.ui.diagram;
\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
24 import javax.swing.JSlider;
\r
25 import javax.vecmath.Vector2d;
\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
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
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
85 public static final Key KEY_DIRECTION = new KeyOf(Double.class, "SLIDER_DIRECTION");
\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
90 public static class Range<T> {
\r
93 public Range(T min, T max) {
\r
102 public T getMax() {
\r
107 static MetricsFormat getNumberFormat(IElement e) {
\r
108 return ElementUtils.getHintOrDefault(e, KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);
\r
111 static void setNumberFormat(IElement e, MetricsFormat f) {
\r
112 ElementUtils.setOrRemoveHint(e, KEY_NUMBER_FORMAT, f);
\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
120 static class Initializer implements LifeCycle {
\r
121 private static final long serialVersionUID = 4404942036933073584L;
\r
123 IElement parentElement;
\r
124 Range<Double> range;
\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
140 public void onElementActivated(IDiagram d, IElement e) {
\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
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
158 e.setHint(KEY_DIRECTION, 0.0);
\r
159 e.setHint(KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);
\r
162 public void onElementDeactivated(IDiagram d, IElement e) {
\r
165 public void onElementDestroyed(IElement e) {
\r
169 public static void update(IElement e) {
\r
170 SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class);
\r
174 public static void cleanup(IElement e) {
\r
175 SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class);
\r
179 public static class SliderSGNode implements SceneGraph, InternalSize, Outline {
\r
180 private static final long serialVersionUID = -106278359626957687L;
\r
182 public static final SliderSGNode INSTANCE = new SliderSGNode();
\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
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
201 node.setBounds(new Rectangle2D.Double(0, 0, 32, 100));
\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
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
220 public void update(IElement e) {
\r
221 Double value = e.getHint(KEY_SLIDER_VALUE);
\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
230 public void cleanup(IElement e) {
\r
231 SliderNode node = (SliderNode)e.removeHint(KEY_SG_NODE);
\r
237 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
\r
238 Rectangle2D shape = new Rectangle2D.Double(0, 0, 0, 0);
\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
245 if(size != null) size.setRect(shape);
\r
250 public Shape getElementShape(IElement e) {
\r
251 Shape shape = new Rectangle2D.Double(0, 0, 0, 0);
\r
253 SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE);
\r
254 if(node != null && node.getBounds() != null) {
\r
255 shape = node.getBounds();
\r
263 public static class Transformer implements Transform, Move, Rotate, Scale {
\r
265 private static final long serialVersionUID = -3704887325602085677L;
\r
267 public static final Transformer INSTANCE = new Transformer(null);
\r
269 Double aspectRatio;
\r
271 public Transformer() {
\r
275 public Transformer(Double aspectRatio) {
\r
276 this.aspectRatio = aspectRatio;
\r
280 public Double getFixedAspectRatio(IElement e) {
\r
281 return aspectRatio;
\r
285 public Point2D getScale(IElement e) {
\r
286 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
\r
287 return _getScale(at);
\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
299 e.setHint(ElementHints.KEY_TRANSFORM, at);
\r
303 public Point2D getMaximumScale(IElement e) {
\r
308 public Point2D getMinimumScale(IElement e) {
\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
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
334 public double getAngle(IElement e) {
\r
335 Double angle = e.getHint(KEY_DIRECTION);
\r
336 return angle != null ? Math.toRadians(angle) : 0;
\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
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
358 public AffineTransform getTransform(IElement e) {
\r
359 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
\r
361 IElement parentElement = e.getHint(ElementHints.KEY_PARENT_ELEMENT);
\r
362 if (parentElement == null)
\r
365 Transform parentTransform = parentElement.getElementClass().getSingleItem(Transform.class);
\r
366 assert(parentTransform!=null);
\r
368 AffineTransform result = (AffineTransform)at.clone();
\r
369 result.preConcatenate(parentTransform.getTransform(parentElement));
\r
375 public void setTransform(IElement e, AffineTransform at) {
\r
376 e.setHint(ElementHints.KEY_TRANSFORM, at.clone());
\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
384 double angrad = Math.toRadians(angle);
\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
390 double cosa = forcedAxis.dot(x);
\r
391 double delta = Math.acos(cosa);
\r
395 static class SliderImageFactory implements IFactory<Image> {
\r
396 private double staticScaleX = 1, staticScaleY = 1;
\r
398 public SliderImageFactory(double staticScaleX, double staticScaleY) {
\r
399 this.staticScaleX = staticScaleX;
\r
400 this.staticScaleY = staticScaleY;
\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
409 public Rectangle2D getBounds() {
\r
410 return path.getBounds2D();
\r
414 public EnumSet<Feature> getFeatures() {
\r
415 return EnumSet.of(Feature.Vector);
\r
419 public Shape getOutline() {
\r
424 public Node init(G2DParentNode parent) {
\r
425 SliderNode node = parent.getOrCreateNode(""+hashCode(), SliderNode.class);
\r
427 node.setBounds(new Rectangle2D.Double(0, 0, 50, 22));
\r
428 node.setTransform(AffineTransform.getScaleInstance(staticScaleX, staticScaleY));
\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
442 static final StaticSymbol SLIDER_SYMBOL = new StaticSymbolImpl( SLIDER_IMAGE.get() );
\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
451 TextEditorImpl.INSTANCE,
\r
452 TextFontImpl.DEFAULT,
\r
453 TextColorImpl.BLACK,
\r
454 SimpleElementLayers.INSTANCE,
\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
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
474 TextEditorImpl.INSTANCE,
\r
475 TextFontImpl.DEFAULT,
\r
476 TextColorImpl.BLACK,
\r
477 SimpleElementLayers.INSTANCE,
\r
479 ).newClassWith(extraHandlers);
\r