/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.modeling.ui.diagram; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.EnumSet; import javax.swing.JSlider; import javax.vecmath.Vector2d; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.ElementHints; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.SceneGraphNodeKey; import org.simantics.g2d.element.handler.ElementHandler; import org.simantics.g2d.element.handler.InternalSize; import org.simantics.g2d.element.handler.LifeCycle; import org.simantics.g2d.element.handler.Move; import org.simantics.g2d.element.handler.Outline; import org.simantics.g2d.element.handler.Rotate; import org.simantics.g2d.element.handler.Scale; import org.simantics.g2d.element.handler.SceneGraph; import org.simantics.g2d.element.handler.StaticSymbol; import org.simantics.g2d.element.handler.TextEditor; import org.simantics.g2d.element.handler.Transform; import org.simantics.g2d.element.handler.TextEditor.Modifier; import org.simantics.g2d.element.handler.impl.BorderColorImpl; import org.simantics.g2d.element.handler.impl.SimpleElementLayers; import org.simantics.g2d.element.handler.impl.StaticSymbolImpl; import org.simantics.g2d.element.handler.impl.TextColorImpl; import org.simantics.g2d.element.handler.impl.TextEditorImpl; import org.simantics.g2d.element.handler.impl.TextFontImpl; import org.simantics.g2d.element.handler.impl.TextImpl; import org.simantics.g2d.elementclass.MonitorHandler; import org.simantics.g2d.image.Image; import org.simantics.g2d.image.ProviderUtils; import org.simantics.g2d.image.impl.AbstractImage; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.swing.SliderNode; import org.simantics.utils.datastructures.cache.IFactory; import org.simantics.utils.datastructures.cache.IProvider; import org.simantics.utils.datastructures.cache.ProvisionException; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; import org.simantics.utils.strings.format.MetricsFormat; import org.simantics.utils.strings.format.MetricsFormatList; /** * @author J-P Laine */ public class SliderClass { public static final Key KEY_SLIDER_RESOURCE_PATH = new KeyOf(Collection.class, "SLIDER_RESOURCE_PATH"); public static final Key KEY_SLIDER_COMPONENT = new KeyOf(Object.class, "SLIDER_COMPONENT"); public static final Key KEY_SLIDER_SUFFIX = new KeyOf(String.class, "SLIDER_SUFFIX"); public static final Key KEY_SLIDER_RANGE = new KeyOf(Range.class, "SLIDER_SUBSTITUTIONS"); public static final Key KEY_SLIDER_GC = new KeyOf(Graphics2D.class, "SLIDER_GC"); public static final Key KEY_SLIDER_VALUE = new KeyOf(Double.class, "SLIDER_VALUE"); public static final Key KEY_TOOLTIP_TEXT = new KeyOf(String.class, "TOOLTIP_TEXT"); public static final Key KEY_NUMBER_FORMAT = new KeyOf(MetricsFormat.class, "NUMBER_FORMAT"); /** * If this hint is defined, the monitor will force its x-axis to match this * angle. If this hint doesn't exist, the monitor will not force x-axis * orientation. */ public static final Key KEY_DIRECTION = new KeyOf(Double.class, "SLIDER_DIRECTION"); public static final Key KEY_SG_NODE = new SceneGraphNodeKey(Node.class, "SLIDER_SG_NODE"); public final static MetricsFormat DEFAULT_NUMBER_FORMAT = MetricsFormatList.METRICS_DECIMAL; public static class Range { T min; T max; public Range(T min, T max) { this.min = min; this.max = max; } public T getMin() { return min; } public T getMax() { return max; } } static MetricsFormat getNumberFormat(IElement e) { return ElementUtils.getHintOrDefault(e, KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT); } static void setNumberFormat(IElement e, MetricsFormat f) { ElementUtils.setOrRemoveHint(e, KEY_NUMBER_FORMAT, f); } public static class SliderHandlerImpl implements MonitorHandler { private static final long serialVersionUID = -4258875745321808416L; public static final MonitorHandler INSTANCE = new SliderHandlerImpl(); } static class Initializer implements LifeCycle { private static final long serialVersionUID = 4404942036933073584L; IElement parentElement; Range range; Object component; String suffix; Double value; Boolean hack; Initializer(IElement parentElement, Range range, Double value, Object component, String suffix, Boolean hack) { this.parentElement = parentElement; this.range = range; this.component = component; this.suffix = suffix; this.value = value; this.hack = hack; } @Override public void onElementActivated(IDiagram d, IElement e) { if(!hack) { hack = true; Point2D parentPos = ElementUtils.getPos(parentElement); Point2D thisPos = ElementUtils.getPos(e); Move move = e.getElementClass().getSingleItem(Move.class); move.moveTo(e, thisPos.getX() - parentPos.getX(), thisPos.getY() - parentPos.getY()); } } @Override public void onElementCreated(IElement e) { if(parentElement != null) e.setHint(ElementHints.KEY_PARENT_ELEMENT, parentElement); if(range != null) e.setHint(KEY_SLIDER_RANGE, range); if(component != null) e.setHint(KEY_SLIDER_COMPONENT, component); if(suffix != null) e.setHint(KEY_SLIDER_SUFFIX, suffix); if(value != null) e.setHint(KEY_SLIDER_VALUE, value); e.setHint(KEY_DIRECTION, 0.0); e.setHint(KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT); } @Override public void onElementDeactivated(IDiagram d, IElement e) { } @Override public void onElementDestroyed(IElement e) { } }; public static void update(IElement e) { SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class); node.update(e); } public static void cleanup(IElement e) { SliderSGNode node = e.getElementClass().getAtMostOneItemOfClass(SliderSGNode.class); node.cleanup(e); } public static class SliderSGNode implements SceneGraph, InternalSize, Outline { private static final long serialVersionUID = -106278359626957687L; public static final SliderSGNode INSTANCE = new SliderSGNode(); @Override public void init(final IElement e, final G2DParentNode parent) { // Create node if it doesn't exist yet SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE); if(node == null || node.getBounds() == null || node.getParent() != parent) { node = parent.addNode(ElementUtils.generateNodeId(e), SliderNode.class); e.setHint(KEY_SG_NODE, node); node.setActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { TextEditor editor = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class); if(editor != null) { Modifier modifier = editor.getModifier(e); if(modifier != null) modifier.modify(e, event.getActionCommand()); } }}); node.setBounds(new Rectangle2D.Double(0, 0, 32, 100)); // Rectangle2D bounds = (Rectangle2D)e.getHint(ElementHints.KEY_BOUNDS); // if(bounds != null) node.setBounds(bounds); // String tooltip = e.getHint(KEY_TOOLTIP_TEXT); Range range = e.getHint(KEY_SLIDER_RANGE); Double value = e.getHint(KEY_SLIDER_VALUE); node.setMinimum((int)Math.round(range.getMin())); // FIXME node.setMaximum((int)Math.round(range.getMax())); node.setValue((int)Math.round(value)); node.setMajorTickSpacing(10); node.setMinorTickSpacing(5); node.setPaintLabels(true); node.setOrientation(JSlider.VERTICAL); } update(e); } public void update(IElement e) { Double value = e.getHint(KEY_SLIDER_VALUE); SliderNode node = (SliderNode) e.getHint(KEY_SG_NODE); if (node != null && value != null) { node.setValue((int)Math.round(value)); } } @Override public void cleanup(IElement e) { SliderNode node = (SliderNode)e.removeHint(KEY_SG_NODE); if (node != null) node.remove(); } @Override public Rectangle2D getBounds(IElement e, Rectangle2D size) { Rectangle2D shape = new Rectangle2D.Double(0, 0, 0, 0); SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE); if(node != null && node.getBounds() != null) { shape = node.getBounds().getBounds2D(); } if(size != null) size.setRect(shape); return shape; } @Override public Shape getElementShape(IElement e) { Shape shape = new Rectangle2D.Double(0, 0, 0, 0); SliderNode node = (SliderNode)e.getHint(KEY_SG_NODE); if(node != null && node.getBounds() != null) { shape = node.getBounds(); } return shape; } } public static class Transformer implements Transform, Move, Rotate, Scale { private static final long serialVersionUID = -3704887325602085677L; public static final Transformer INSTANCE = new Transformer(null); Double aspectRatio; public Transformer() { this(null); } public Transformer(Double aspectRatio) { this.aspectRatio = aspectRatio; } @Override public Double getFixedAspectRatio(IElement e) { return aspectRatio; } @Override public Point2D getScale(IElement e) { AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM); return _getScale(at); } @Override public void setScale(IElement e, Point2D newScale) { // Doesn't work for monitors. Point2D oldScale = getScale(e); double sx = newScale.getX() / oldScale.getX(); double sy = newScale.getY() / oldScale.getY(); AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM); at = new AffineTransform(at); at.scale(sx, sy); e.setHint(ElementHints.KEY_TRANSFORM, at); } @Override public Point2D getMaximumScale(IElement e) { return null; } @Override public Point2D getMinimumScale(IElement e) { return null; } private static Point2D _getScale(AffineTransform at) { double m00 = at.getScaleX(); double m11 = at.getScaleY(); double m10 = at.getShearY(); double m01 = at.getShearX(); // Project unit vector to canvas double sx = Math.sqrt(m00 * m00 + m10 * m10); double sy = Math.sqrt(m01 * m01 + m11 * m11); return new Point2D.Double(sx, sy); } @Override public void rotate(IElement e, double theta, Point2D origin) { if (Double.isNaN(theta)) return; theta = Math.toDegrees(theta); Double angle = e.getHint(KEY_DIRECTION); double newAngle = angle != null ? angle+theta : theta; newAngle = Math.IEEEremainder(newAngle, 360.0); e.setHint(KEY_DIRECTION, newAngle); } @Override public double getAngle(IElement e) { Double angle = e.getHint(KEY_DIRECTION); return angle != null ? Math.toRadians(angle) : 0; } @Override public Point2D getPosition(IElement e) { AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM); Point2D p = new Point2D.Double(at.getTranslateX(), at.getTranslateY()); return p; } @Override public void moveTo(IElement e, double x, double y) { AffineTransform origAt = e.getHint(ElementHints.KEY_TRANSFORM); double oldX = origAt.getTranslateX(); double oldY = origAt.getTranslateY(); AffineTransform move = AffineTransform.getTranslateInstance(x-oldX, y-oldY); AffineTransform at2 = new AffineTransform(origAt); at2.preConcatenate(move); e.setHint(ElementHints.KEY_TRANSFORM, at2); } @Override public AffineTransform getTransform(IElement e) { AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM); IElement parentElement = e.getHint(ElementHints.KEY_PARENT_ELEMENT); if (parentElement == null) return at; Transform parentTransform = parentElement.getElementClass().getSingleItem(Transform.class); assert(parentTransform!=null); AffineTransform result = (AffineTransform)at.clone(); result.preConcatenate(parentTransform.getTransform(parentElement)); return result; } @Override public void setTransform(IElement e, AffineTransform at) { e.setHint(ElementHints.KEY_TRANSFORM, at.clone()); } } static double getOrientationDelta(IElement e, AffineTransform tr) { Double angle = e.getHint(KEY_DIRECTION); if (angle == null || Double.isNaN(angle)) return Double.NaN; double angrad = Math.toRadians(angle); Vector2d forcedAxis = new Vector2d(Math.cos(angrad), Math.sin(angrad)); Vector2d x = new Vector2d(tr.getScaleX(), tr.getShearX()); forcedAxis.normalize(); x.normalize(); double cosa = forcedAxis.dot(x); double delta = Math.acos(cosa); return delta; } static class SliderImageFactory implements IFactory { private double staticScaleX = 1, staticScaleY = 1; public SliderImageFactory(double staticScaleX, double staticScaleY) { this.staticScaleX = staticScaleX; this.staticScaleY = staticScaleY; } @Override public Image get() throws ProvisionException { return new AbstractImage() { Shape path = new Rectangle2D.Double(0, 0, 50*staticScaleX, 22*staticScaleY); @Override public Rectangle2D getBounds() { return path.getBounds2D(); } @Override public EnumSet getFeatures() { return EnumSet.of(Feature.Vector); } @Override public Shape getOutline() { return path; } @Override public Node init(G2DParentNode parent) { SliderNode node = parent.getOrCreateNode(""+hashCode(), SliderNode.class); node.setValue(0); node.setBounds(new Rectangle2D.Double(0, 0, 50, 22)); node.setTransform(AffineTransform.getScaleInstance(staticScaleX, staticScaleY)); return node; } }; } } static final IProvider SLIDER_IMAGE = ProviderUtils.reference( ProviderUtils.cache( ProviderUtils.rasterize( new SliderImageFactory(0.5, 0.5) ))); static final StaticSymbol SLIDER_SYMBOL = new StaticSymbolImpl( SLIDER_IMAGE.get() ); public static final ElementClass SLIDER_CLASS = ElementClass.compile( SliderHandlerImpl.INSTANCE, Transformer.INSTANCE, BorderColorImpl.BLACK, SliderSGNode.INSTANCE, TextImpl.INSTANCE, TextEditorImpl.INSTANCE, TextFontImpl.DEFAULT, TextColorImpl.BLACK, SimpleElementLayers.INSTANCE, SLIDER_SYMBOL ); // staticScale{X,Y} define the scale of the static monitor image public static ElementClass create(IElement parentElement, Range range, Double value, Object component, String suffix, double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) { // Bit of a hack to be able to define the scale IProvider staticSliderSymbolProvider = ProviderUtils.reference( ProviderUtils.cache( ProviderUtils .rasterize( new SliderImageFactory(staticScaleX, staticScaleY)))); StaticSymbol staticSliderSymbol = new StaticSymbolImpl( staticSliderSymbolProvider.get() ); return ElementClass.compile( new Initializer(parentElement, range, value, component, suffix, parentElement != null ? false : true), SliderHandlerImpl.INSTANCE, Transformer.INSTANCE, BorderColorImpl.BLACK, SliderSGNode.INSTANCE, TextImpl.INSTANCE, TextEditorImpl.INSTANCE, TextFontImpl.DEFAULT, TextColorImpl.BLACK, SimpleElementLayers.INSTANCE, staticSliderSymbol ).newClassWith(extraHandlers); } }