/******************************************************************************* * Copyright (c) 2007- VTT Technical Research Centre of Finland. * 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.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.EnumSet; import java.util.Map; import javax.swing.table.TableModel; 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.FillColor; 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.Text; import org.simantics.g2d.element.handler.Transform; import org.simantics.g2d.element.handler.impl.BorderColorImpl; import org.simantics.g2d.element.handler.impl.FillColorImpl; import org.simantics.g2d.element.handler.impl.SimpleElementLayers; import org.simantics.g2d.element.handler.impl.StaticSymbolImpl; import org.simantics.g2d.element.handler.impl.TextEditorImpl; 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.g2d.utils.Alignment; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.spreadsheet.Model; 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; /** * @author Tuukka Lehtonen */ public class SheetClass { static final Font FONT = Font.decode("Helvetica 10"); public static final Key KEY_MODEL = new KeyOf(Model.class, "MODEL"); public static final Key KEY_TABLE_MODEL = new KeyOf(TableModel.class, "TABLE_MODEL"); public static final Key KEY_MONITOR_RESOURCE_PATH = new KeyOf(Collection.class, "MONITOR_RESOURCE_PATH"); public static final Key KEY_MONITOR_SUBSTITUTIONS = new KeyOf(Map.class, "MONITOR_SUBSTITUTIONS"); public static final Key KEY_MONITOR_GC = new KeyOf(Graphics2D.class, "MONITOR_GC"); public static final Key KEY_MONITOR_HEIGHT = new KeyOf(Double.class, "MONITOR_HEIGHT"); public static final Key KEY_MONITOR_FONT_METRICS = new KeyOf(FontMetrics.class, "MONITOR_FONT_METRICS"); public static final Key KEY_NUMBER_FORMAT = new KeyOf(String.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, "MONITOR_DIRECTION"); public static final Key KEY_SG_NODE = new SceneGraphNodeKey(Node.class, "MONITOR_SG_NODE"); final static BasicStroke STROKE = new BasicStroke(1.0f); public final static Alignment DEFAULT_HORIZONTAL_ALIGN = Alignment.CENTER; public final static Alignment DEFAULT_VERTICAL_ALIGN = Alignment.CENTER; public final static String DEFAULT_NUMBER_FORMAT = "0.0###"; public final static Color DEFAULT_FILL_COLOR = new Color(224, 224, 224); public final static Color DEFAULT_BORDER_COLOR = Color.BLACK; public final static double DEFAULT_HORIZONTAL_MARGIN = 5.0; public final static double DEFAULT_VERTICAL_MARGIN = 2.5; static Alignment getHorizontalAlignment(IElement e) { return ElementUtils.getHintOrDefault(e, ElementHints.KEY_HORIZONTAL_ALIGN, DEFAULT_HORIZONTAL_ALIGN); } static Alignment getVerticalAlignment(IElement e) { return ElementUtils.getHintOrDefault(e, ElementHints.KEY_VERTICAL_ALIGN, DEFAULT_VERTICAL_ALIGN); } static String getNumberFormat(IElement e) { return ElementUtils.getHintOrDefault(e, KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT); } static void setNumberFormat(IElement e, String format) { ElementUtils.setOrRemoveHint(e, KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT); } static double getHorizontalMargin(IElement e) { return DEFAULT_HORIZONTAL_MARGIN; } static double getVerticalMargin(IElement e) { return DEFAULT_VERTICAL_MARGIN; } static Font getFont(IElement e) { return ElementUtils.getHintOrDefault(e, ElementHints.KEY_FONT, FONT); } public static class MonitorHandlerImpl implements MonitorHandler { private static final long serialVersionUID = -4258875745321808416L; public static final MonitorHandler INSTANCE = new MonitorHandlerImpl(); } static class Initializer implements LifeCycle { private static final long serialVersionUID = 4404942036933073584L; IElement parentElement; Map substitutions; Collection path; boolean hack; Initializer(IElement parentElement, Map substitutions, Collection path, boolean hack) { this.parentElement = parentElement; this.substitutions = substitutions; this.path = path; 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(substitutions != null) e.setHint(KEY_MONITOR_SUBSTITUTIONS, substitutions); if(path != null) e.setHint(KEY_MONITOR_RESOURCE_PATH, path); e.setHint(KEY_DIRECTION, 0.0); e.setHint(KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT); //e.setHint(KEY_HORIZONTAL_ALIGN, Alignment.LEADING); //e.setHint(KEY_VERTICAL_ALIGN, Alignment.LEADING); } @Override public void onElementDeactivated(IDiagram d, IElement e) { } @Override public void onElementDestroyed(IElement e) { } }; static String finalText(IElement e) { Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class); assert(t != null); String text = t.getText(e); if(text == null) return null; Map substitutions = e.getHint(KEY_MONITOR_SUBSTITUTIONS); if(substitutions != null) { // TODO: slow as hell for(Map.Entry entry : substitutions.entrySet()) { if (entry.getValue() != null) { text = text.replace(entry.getKey(), entry.getValue()); } else { text = text.replace(entry.getKey(), ""); } } } return text; } public static void update(IElement e) { SheetSGNode node = e.getElementClass().getAtMostOneItemOfClass(SheetSGNode.class); node.update(e); } public static void cleanup(IElement e) { SheetSGNode node = e.getElementClass().getAtMostOneItemOfClass(SheetSGNode.class); node.cleanup(e); } public static String editText(IElement e) { Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class); assert(t != null); String text = "#v1"; if(text == null) return null; Map substitutions = e.getHint(KEY_MONITOR_SUBSTITUTIONS); if(substitutions != null) { // TODO: slow as hell for(Map.Entry entry : substitutions.entrySet()) { if (entry.getValue() != null) { text = text.replace(entry.getKey(), entry.getValue()); } else { text = text.replace(entry.getKey(), ""); } } } return text; } static final Rectangle2D DEFAULT_BOX = new Rectangle2D.Double(0, 0, 0, 0); static Shape createMonitor(IElement e) { Alignment hAlign = getHorizontalAlignment(e); Alignment vAlign = getVerticalAlignment(e); double hMargin = getHorizontalMargin(e); double vMargin = getVerticalMargin(e); String text = finalText(e); if(text == null) { return align(hMargin, vMargin, hAlign, vAlign, DEFAULT_BOX); } Graphics2D g = e.getHint(KEY_MONITOR_GC); if(g == null) { return align(hMargin, vMargin, hAlign, vAlign, DEFAULT_BOX); } Font f = getFont(e); FontMetrics fm = g.getFontMetrics(f); Rectangle2D rect = fm.getStringBounds(text, g); return align(hMargin, vMargin, hAlign, vAlign, rect); } static Shape align(double hMargin, double vMargin, Alignment hAlign, Alignment vAlign, Rectangle2D rect) { //System.out.println("align: " + hMargin + ", " + vMargin + ", " + hAlign + ", " + vAlign + ": " + rect); double tx = align(hMargin, hAlign, rect.getMinX(), rect.getMaxX()); double ty = align(vMargin, vAlign, rect.getMinY(), rect.getMaxY()); //System.out.println(" translate: " + tx + " " + ty); double nw = rect.getWidth() + 2*hMargin; double nh = rect.getHeight() + 2*vMargin; return makePath(tx + rect.getMinX(), ty + rect.getMinY(), nw, nh); } static double align(double margin, Alignment align, double min, double max) { double s = max - min; switch (align) { case LEADING: return -min; case TRAILING: return -s - 2 * margin - min; case CENTER: return -0.5 * s - margin - min; default: return 0; } } static Path2D makePath(double x, double y, double w, double h) { Path2D path = new Path2D.Double(); path.moveTo(x, y); path.lineTo(x+w, y); path.lineTo(x+w, y+h); path.lineTo(x, y+h); path.closePath(); return path; } public static final Shape BOX_SHAPE = new Rectangle(-1, -1, 2, 2); public static class SheetSGNode implements SceneGraph, InternalSize, Outline { private static final long serialVersionUID = -5823585015844593347L; static final SheetSGNode INSTANCE = new SheetSGNode(); @Override public void init(final IElement e, final G2DParentNode parent) { // Create node if it doesn't exist yet SheetNode node = (SheetNode)e.getHint(KEY_SG_NODE); if(node == null || node.getBounds() == null || node.getParent() != parent) { node = parent.addNode(ElementUtils.generateNodeId(e), SheetNode.class); e.setHint(KEY_SG_NODE, node); Model model = e.getHint(KEY_MODEL); TableModel tableModel = e.getHint(KEY_TABLE_MODEL); node.init(model, tableModel); System.out.println(parent); node.setSize(500, 220); } update(e); } public void update(IElement e) { // String value = null; final Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class); assert(t != null); String text = finalText(e); // System.out.println("monitorclass text = " + text); // // if(text != null) { // Map substitutions = e.getHint(KEY_MONITOR_SUBSTITUTIONS); // for(Map.Entry entry : substitutions.entrySet()) { // System.out.println("substitute " + entry.getKey() + " -> " + entry.getValue()); // text.replace(entry.getKey(), entry.getValue()); // } // } // key = text; // if(substitutions != null) { // value = substitutions.get("#v1"); // if(substitutions.containsKey("#u1")) { // value += substitutions.get("#u1"); // } // } // SheetNode node = (SheetNode) e.getHint(KEY_SG_NODE); // if (node != null && text != null) { // node.setText(text); // Collection path = e.getHint(KEY_MONITOR_RESOURCE_PATH); // if (path != null && !path.isEmpty()) { // node.setEditable(true); // } else { // node.setEditable(false); // } // } } @Override public void cleanup(IElement e) { SheetNode node = (SheetNode)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); SheetNode node = (SheetNode)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); SheetNode node = (SheetNode)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()); } // @Override // public void onElementActivated(IDiagram d, IElement e) { // } // // @Override // public void onElementCreated(IElement e) { // e.setHint(ElementHints.KEY_TRANSFORM, new AffineTransform()); // } // // @Override // public void onElementDeactivated(IDiagram d, IElement e) { // } // // @Override // public void onElementDestroyed(IElement e) { // List nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class); // for(SceneGraph n : nodeHandlers) { // System.out.println("element gone:"+e); // n.cleanup(e); // } // } } static class MonitorImageFactory implements IFactory { @Override public Image get() throws ProvisionException { return new AbstractImage() { Shape path = align(DEFAULT_HORIZONTAL_MARGIN, DEFAULT_VERTICAL_MARGIN, DEFAULT_HORIZONTAL_ALIGN, DEFAULT_VERTICAL_ALIGN, new Rectangle2D.Double(0, 0, 50, 22)); @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) { SheetNode node = parent.getOrCreateNode(""+hashCode(), SheetNode.class); // node.setText(""); // node.setSize(50, 22); node.setTransform(new AffineTransform()); return node; } }; } } static final IProvider MONITOR_IMAGE = ProviderUtils.reference( ProviderUtils.cache( ProviderUtils.rasterize( new MonitorImageFactory() ))); static final StaticSymbol MONITOR_SYMBOL = new StaticSymbolImpl( MONITOR_IMAGE.get() ); static final FillColor FILL_COLOR = new FillColorImpl(DEFAULT_FILL_COLOR); public static final ElementClass MONITOR_CLASS = ElementClass.compile( MonitorHandlerImpl.INSTANCE, Transformer.INSTANCE, BorderColorImpl.BLACK, FILL_COLOR, SheetSGNode.INSTANCE, TextImpl.INSTANCE, TextEditorImpl.INSTANCE, SimpleElementLayers.INSTANCE, MONITOR_SYMBOL ); // staticScale{X,Y} define the scale of the static monitor image public static ElementClass create(IElement parentElement, Map substitutions, Collection path, double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) { // Bit of a hack to be able to define the scale IProvider staticMonitorSymbolProvider = ProviderUtils.reference( ProviderUtils.cache( ProviderUtils .rasterize( new MonitorImageFactory()))); StaticSymbol staticMonitorSymbol = new StaticSymbolImpl( staticMonitorSymbolProvider.get() ); return ElementClass.compile( new Initializer(parentElement, substitutions, path, parentElement != null ? false : true), MonitorHandlerImpl.INSTANCE, Transformer.INSTANCE, BorderColorImpl.BLACK, FILL_COLOR, SheetSGNode.INSTANCE, TextImpl.INSTANCE, TextEditorImpl.INSTANCE, SimpleElementLayers.INSTANCE, staticMonitorSymbol ).newClassWith(extraHandlers); } }