1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.modeling.ui.diagram;
14 import java.awt.Graphics2D;
15 import java.awt.Shape;
16 import java.awt.event.ActionEvent;
17 import java.awt.event.ActionListener;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Point2D;
20 import java.awt.geom.Rectangle2D;
21 import java.util.Collection;
22 import java.util.EnumSet;
24 import javax.swing.JSlider;
26 import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
27 import org.simantics.g2d.diagram.IDiagram;
28 import org.simantics.g2d.element.ElementClass;
29 import org.simantics.g2d.element.ElementHints;
30 import org.simantics.g2d.element.ElementUtils;
31 import org.simantics.g2d.element.IElement;
32 import org.simantics.g2d.element.SceneGraphNodeKey;
33 import org.simantics.g2d.element.handler.ElementHandler;
34 import org.simantics.g2d.element.handler.InternalSize;
35 import org.simantics.g2d.element.handler.LifeCycle;
36 import org.simantics.g2d.element.handler.Move;
37 import org.simantics.g2d.element.handler.Outline;
38 import org.simantics.g2d.element.handler.Rotate;
39 import org.simantics.g2d.element.handler.Scale;
40 import org.simantics.g2d.element.handler.SceneGraph;
41 import org.simantics.g2d.element.handler.StaticSymbol;
42 import org.simantics.g2d.element.handler.TextEditor;
43 import org.simantics.g2d.element.handler.Transform;
44 import org.simantics.g2d.element.handler.TextEditor.Modifier;
45 import org.simantics.g2d.element.handler.impl.BorderColorImpl;
46 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;
47 import org.simantics.g2d.element.handler.impl.StaticSymbolImpl;
48 import org.simantics.g2d.element.handler.impl.TextColorImpl;
49 import org.simantics.g2d.element.handler.impl.TextEditorImpl;
50 import org.simantics.g2d.element.handler.impl.TextFontImpl;
51 import org.simantics.g2d.element.handler.impl.TextImpl;
52 import org.simantics.g2d.elementclass.MonitorHandler;
53 import org.simantics.g2d.image.Image;
54 import org.simantics.g2d.image.ProviderUtils;
55 import org.simantics.g2d.image.impl.AbstractImage;
56 import org.simantics.scenegraph.Node;
57 import org.simantics.scenegraph.g2d.G2DParentNode;
58 import org.simantics.scenegraph.swing.SliderNode;
59 import org.simantics.utils.datastructures.cache.IFactory;
60 import org.simantics.utils.datastructures.cache.IProvider;
61 import org.simantics.utils.datastructures.cache.ProvisionException;
62 import org.simantics.utils.datastructures.hints.IHintContext.Key;
63 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
64 import org.simantics.utils.strings.format.MetricsFormat;
65 import org.simantics.utils.strings.format.MetricsFormatList;
70 public class SliderClass {
71 public static final Key KEY_SLIDER_RESOURCE_PATH = new KeyOf(Collection.class, "SLIDER_RESOURCE_PATH");
72 public static final Key KEY_SLIDER_COMPONENT = new KeyOf(Object.class, "SLIDER_COMPONENT");
73 public static final Key KEY_SLIDER_SUFFIX = new KeyOf(String.class, "SLIDER_SUFFIX");
74 public static final Key KEY_SLIDER_RANGE = new KeyOf(Range.class, "SLIDER_SUBSTITUTIONS");
75 public static final Key KEY_SLIDER_GC = new KeyOf(Graphics2D.class, "SLIDER_GC");
76 public static final Key KEY_SLIDER_VALUE = new KeyOf(Double.class, "SLIDER_VALUE");
77 public static final Key KEY_TOOLTIP_TEXT = new KeyOf(String.class, "TOOLTIP_TEXT");
78 public static final Key KEY_NUMBER_FORMAT = new KeyOf(MetricsFormat.class, "NUMBER_FORMAT");
81 * If this hint is defined, the monitor will force its x-axis to match this
82 * angle. If this hint doesn't exist, the monitor will not force x-axis
85 public static final Key KEY_DIRECTION = new KeyOf(Double.class, "SLIDER_DIRECTION");
87 public static final Key KEY_SG_NODE = new SceneGraphNodeKey(Node.class, "SLIDER_SG_NODE");
88 public final static MetricsFormat DEFAULT_NUMBER_FORMAT = MetricsFormatList.METRICS_DECIMAL;
90 public static class Range<T> {
93 public Range(T min, T max) {
107 static MetricsFormat getNumberFormat(IElement e) {
108 return ElementUtils.getHintOrDefault(e, KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);
111 static void setNumberFormat(IElement e, MetricsFormat f) {
112 ElementUtils.setOrRemoveHint(e, KEY_NUMBER_FORMAT, f);
115 public static class SliderHandlerImpl implements MonitorHandler {
116 private static final long serialVersionUID = -4258875745321808416L;
117 public static final MonitorHandler INSTANCE = new SliderHandlerImpl();
120 static class Initializer implements LifeCycle {
121 private static final long serialVersionUID = 4404942036933073584L;
123 IElement parentElement;
130 Initializer(IElement parentElement, Range<Double> range, Double value, Object component, String suffix, Boolean hack) {
131 this.parentElement = parentElement;
133 this.component = component;
134 this.suffix = suffix;
140 public void onElementActivated(IDiagram d, IElement e) {
143 Point2D parentPos = ElementUtils.getPos(parentElement);
144 Point2D thisPos = ElementUtils.getPos(e);
145 Move move = e.getElementClass().getSingleItem(Move.class);
146 move.moveTo(e, thisPos.getX() - parentPos.getX(), thisPos.getY() - parentPos.getY());
151 public void onElementCreated(IElement e) {
152 if(parentElement != null) e.setHint(ElementHints.KEY_PARENT_ELEMENT, parentElement);
153 if(range != null) e.setHint(KEY_SLIDER_RANGE, range);
154 if(component != null) e.setHint(KEY_SLIDER_COMPONENT, component);
155 if(suffix != null) e.setHint(KEY_SLIDER_SUFFIX, suffix);
156 if(value != null) e.setHint(KEY_SLIDER_VALUE, value);
158 e.setHint(KEY_DIRECTION, 0.0);
159 e.setHint(KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);
162 public void onElementDeactivated(IDiagram d, IElement e) {
165 public void onElementDestroyed(IElement e) {
169 public static void update(IElement e) {
170 SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class);
174 public static void cleanup(IElement e) {
175 SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class);
179 public static class SliderSGNode implements SceneGraph, InternalSize, Outline {
180 private static final long serialVersionUID = -106278359626957687L;
182 public static final SliderSGNode INSTANCE = new SliderSGNode();
185 public void init(final IElement e, final G2DParentNode parent) {
186 // Create node if it doesn't exist yet
187 SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE);
188 if(node == null || node.getBounds() == null || node.getParent() != parent) {
189 node = parent.addNode(ElementUtils.generateNodeId(e), SliderNode.class);
190 e.setHint(KEY_SG_NODE, node);
191 node.setActionListener(new ActionListener() {
193 public void actionPerformed(ActionEvent event) {
194 TextEditor editor = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);
196 Modifier modifier = editor.getModifier(e);
198 modifier.modify(e, event.getActionCommand());
201 node.setBounds(new Rectangle2D.Double(0, 0, 32, 100));
203 // Rectangle2D bounds = (Rectangle2D)e.getHint(ElementHints.KEY_BOUNDS);
204 // if(bounds != null) node.setBounds(bounds);
205 // String tooltip = e.getHint(KEY_TOOLTIP_TEXT);
206 Range<Double> range = e.getHint(KEY_SLIDER_RANGE);
207 Double value = e.getHint(KEY_SLIDER_VALUE);
209 node.setMinimum((int)Math.round(range.getMin())); // FIXME
210 node.setMaximum((int)Math.round(range.getMax()));
211 node.setValue((int)Math.round(value));
212 node.setMajorTickSpacing(10);
213 node.setMinorTickSpacing(5);
214 node.setPaintLabels(true);
215 node.setOrientation(JSlider.VERTICAL);
220 public void update(IElement e) {
221 Double value = e.getHint(KEY_SLIDER_VALUE);
223 SliderNode node = (SliderNode) e.getHint(KEY_SG_NODE);
224 if (node != null && value != null) {
225 node.setValue((int)Math.round(value));
230 public void cleanup(IElement e) {
231 SliderNode node = (SliderNode)e.removeHint(KEY_SG_NODE);
237 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
238 Rectangle2D shape = new Rectangle2D.Double(0, 0, 0, 0);
240 SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE);
241 if(node != null && node.getBounds() != null) {
242 shape = node.getBounds().getBounds2D();
245 if(size != null) size.setRect(shape);
250 public Shape getElementShape(IElement e) {
251 Shape shape = new Rectangle2D.Double(0, 0, 0, 0);
253 SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE);
254 if(node != null && node.getBounds() != null) {
255 shape = node.getBounds();
263 public static class Transformer implements Transform, Move, Rotate, Scale {
265 private static final long serialVersionUID = -3704887325602085677L;
267 public static final Transformer INSTANCE = new Transformer(null);
271 public Transformer() {
275 public Transformer(Double aspectRatio) {
276 this.aspectRatio = aspectRatio;
280 public Double getFixedAspectRatio(IElement e) {
285 public Point2D getScale(IElement e) {
286 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
287 return _getScale(at);
291 public void setScale(IElement e, Point2D newScale) {
292 // Doesn't work for monitors.
293 Point2D oldScale = getScale(e);
294 double sx = newScale.getX() / oldScale.getX();
295 double sy = newScale.getY() / oldScale.getY();
296 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
297 at = new AffineTransform(at);
299 e.setHint(ElementHints.KEY_TRANSFORM, at);
303 public Point2D getMaximumScale(IElement e) {
308 public Point2D getMinimumScale(IElement e) {
312 private static Point2D _getScale(AffineTransform at) {
313 double m00 = at.getScaleX();
314 double m11 = at.getScaleY();
315 double m10 = at.getShearY();
316 double m01 = at.getShearX();
317 // Project unit vector to canvas
318 double sx = Math.sqrt(m00 * m00 + m10 * m10);
319 double sy = Math.sqrt(m01 * m01 + m11 * m11);
320 return new Point2D.Double(sx, sy);
324 public void rotate(IElement e, double theta, Point2D origin) {
325 if (Double.isNaN(theta)) return;
326 theta = Math.toDegrees(theta);
327 Double angle = e.getHint(KEY_DIRECTION);
328 double newAngle = angle != null ? angle+theta : theta;
329 newAngle = Math.IEEEremainder(newAngle, 360.0);
330 e.setHint(KEY_DIRECTION, newAngle);
334 public double getAngle(IElement e) {
335 Double angle = e.getHint(KEY_DIRECTION);
336 return angle != null ? Math.toRadians(angle) : 0;
340 public Point2D getPosition(IElement e) {
341 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
342 Point2D p = new Point2D.Double(at.getTranslateX(), at.getTranslateY());
347 public void moveTo(IElement e, double x, double y) {
348 AffineTransform origAt = e.getHint(ElementHints.KEY_TRANSFORM);
349 double oldX = origAt.getTranslateX();
350 double oldY = origAt.getTranslateY();
351 AffineTransform move = AffineTransform.getTranslateInstance(x-oldX, y-oldY);
352 AffineTransform at2 = new AffineTransform(origAt);
353 at2.preConcatenate(move);
354 e.setHint(ElementHints.KEY_TRANSFORM, at2);
358 public AffineTransform getTransform(IElement e) {
359 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
361 IElement parentElement = e.getHint(ElementHints.KEY_PARENT_ELEMENT);
362 if (parentElement == null)
365 Transform parentTransform = parentElement.getElementClass().getSingleItem(Transform.class);
366 assert(parentTransform!=null);
368 AffineTransform result = (AffineTransform)at.clone();
369 result.preConcatenate(parentTransform.getTransform(parentElement));
375 public void setTransform(IElement e, AffineTransform at) {
376 e.setHint(ElementHints.KEY_TRANSFORM, at.clone());
380 static double getOrientationDelta(IElement e, AffineTransform tr) {
381 Double angle = e.getHint(KEY_DIRECTION);
382 if (angle == null || Double.isNaN(angle))
384 double angrad = Math.toRadians(angle);
386 Vector2D forcedAxis = new Vector2D(Math.cos(angrad), Math.sin(angrad));
387 Vector2D x = new Vector2D(tr.getScaleX(), tr.getShearX()).normalize();
388 double cosa = forcedAxis.dotProduct(x);
389 double delta = Math.acos(cosa);
393 static class SliderImageFactory implements IFactory<Image> {
394 private double staticScaleX = 1, staticScaleY = 1;
396 public SliderImageFactory(double staticScaleX, double staticScaleY) {
397 this.staticScaleX = staticScaleX;
398 this.staticScaleY = staticScaleY;
402 public Image get() throws ProvisionException {
403 return new AbstractImage() {
404 Shape path = new Rectangle2D.Double(0, 0, 50*staticScaleX, 22*staticScaleY);
407 public Rectangle2D getBounds() {
408 return path.getBounds2D();
412 public EnumSet<Feature> getFeatures() {
413 return EnumSet.of(Feature.Vector);
417 public Shape getOutline() {
422 public Node init(G2DParentNode parent) {
423 SliderNode node = parent.getOrCreateNode(""+hashCode(), SliderNode.class);
425 node.setBounds(new Rectangle2D.Double(0, 0, 50, 22));
426 node.setTransform(AffineTransform.getScaleInstance(staticScaleX, staticScaleY));
433 static final IProvider<Image> SLIDER_IMAGE =
434 ProviderUtils.reference(
436 ProviderUtils.rasterize(
437 new SliderImageFactory(0.5, 0.5)
440 static final StaticSymbol SLIDER_SYMBOL = new StaticSymbolImpl( SLIDER_IMAGE.get() );
442 public static final ElementClass SLIDER_CLASS =
443 ElementClass.compile(
444 SliderHandlerImpl.INSTANCE,
445 Transformer.INSTANCE,
446 BorderColorImpl.BLACK,
447 SliderSGNode.INSTANCE,
449 TextEditorImpl.INSTANCE,
450 TextFontImpl.DEFAULT,
452 SimpleElementLayers.INSTANCE,
456 // staticScale{X,Y} define the scale of the static monitor image
457 public static ElementClass create(IElement parentElement, Range<Double> range, Double value, Object component, String suffix, double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) {
458 // Bit of a hack to be able to define the scale
459 IProvider<Image> staticSliderSymbolProvider = ProviderUtils.reference(
463 new SliderImageFactory(staticScaleX, staticScaleY))));
464 StaticSymbol staticSliderSymbol = new StaticSymbolImpl( staticSliderSymbolProvider.get() );
465 return ElementClass.compile(
466 new Initializer(parentElement, range, value, component, suffix, parentElement != null ? false : true),
467 SliderHandlerImpl.INSTANCE,
468 Transformer.INSTANCE,
469 BorderColorImpl.BLACK,
470 SliderSGNode.INSTANCE,
472 TextEditorImpl.INSTANCE,
473 TextFontImpl.DEFAULT,
475 SimpleElementLayers.INSTANCE,
477 ).newClassWith(extraHandlers);