/******************************************************************************* * Copyright (c) 2012 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.template2d.ui.diagram.adapter; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicReference; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.databoard.annotations.Optional; import org.simantics.databoard.util.Bean; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.common.request.ResourceRead; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; import org.simantics.diagram.elements.TextNode; import org.simantics.diagram.function.All; import org.simantics.diagram.function.PredefinedVariables; import org.simantics.diagram.profile.ProfileKeys; import org.simantics.diagram.profile.StyleBase; import org.simantics.diagram.stubs.DiagramResource; 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.ElementAdapter; import org.simantics.g2d.element.handler.FillColor; import org.simantics.g2d.element.handler.InternalSize; import org.simantics.g2d.element.handler.Outline; import org.simantics.g2d.element.handler.OutlineColorSpec; import org.simantics.g2d.element.handler.SceneGraph; import org.simantics.g2d.element.handler.SelectionSpecification; import org.simantics.g2d.element.handler.StrokeSpec; import org.simantics.g2d.element.handler.Transform; import org.simantics.g2d.element.handler.impl.StaticObjectAdapter; import org.simantics.g2d.element.impl.Element; import org.simantics.g2d.elementclass.NonCopyable; import org.simantics.g2d.scenegraph.SceneGraphConstants; import org.simantics.layer0.Layer0; import org.simantics.modeling.template2d.ui.function.DrawingTemplateInfo; import org.simantics.modeling.template2d.ui.function.FlagInfo; import org.simantics.modeling.template2d.ui.function.FlagTableColumnInfo; import org.simantics.modeling.template2d.ui.function.FlagTableInfo; import org.simantics.modeling.template2d.ui.function.MonitorInfo; import org.simantics.modeling.template2d.ui.function.TranslateFlag; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.g2d.nodes.ShapeNode; import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode; import org.simantics.scenegraph.profile.DataNodeMap; import org.simantics.scenegraph.profile.EvaluationContext; import org.simantics.scenegraph.profile.common.ProfileVariables; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.ui.colors.Colors; import org.simantics.ui.fonts.Fonts; import org.simantics.utils.datastructures.hints.IHintContext.Key; public class DrawingFlagTableStyle extends StyleBase { private static final boolean DEBUG_PAINT_ELEMENTS = false; private static final String SLOT_TABLE_PREFIX = "slotTable<"; private static final String SLOT_TABLE_SEPARATOR = ">"; private static final String SLOT_TABLE_ELEMENTS = "slotTableElements"; private ElementClass rowElementClass; public DrawingFlagTableStyle(ReadGraph graph) { rowElementClass = ElementClass.compile( RowElementHandler.INSTANCE, RowElementSelectionHandler.INSTANCE, NonCopyable.INSTANCE, new StaticObjectAdapter(DiagramResource.getInstance(graph).Flag) ).setId(RowElementHandler.class.getSimpleName()); if (DEBUG_PAINT_ELEMENTS) rowElementClass = rowElementClass.newClassWith(TestSceneGraph.INSTANCE); } public static class StyleInfo extends Bean { public TreeMap name2table; public List flags; @Optional public Resource template = null; //public String digest = ""; }; public static class FlagInfos extends ResourceRead> { public FlagInfos(Resource diagram) { super(diagram); } @Override public ArrayList perform(ReadGraph g) throws DatabaseException { ArrayList flagInfos = new ArrayList(); try { Layer0 L0 = Layer0.getInstance(g); DiagramResource DIA = DiagramResource.getInstance(g); Collection children = g.getObjects(resource, L0.ConsistsOf); for (Resource child:children){ if (!g.isInstanceOf(child, DIA.Flag)) continue; Resource flag = child; String tableName = g.getPossibleRelatedValue(flag, DIA.Flag_HasIOTableBinding, Bindings.STRING); Integer rowIndex = g.getPossibleRelatedValue(flag, DIA.Flag_HasIOTableRowIndex, Bindings.INTEGER); FlagInfo flagInfo = new FlagInfo(); flagInfo.flag = flag; if (tableName != null && tableName.length() > 0) flagInfo.flagTableName = tableName; flagInfo.flagTableRowIndex = rowIndex; flagInfos.add(flagInfo); } } catch (Throwable e){ e.printStackTrace(System.err); } return flagInfos; } } @Override public StyleInfo calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource diagram, Variable activeComposite) throws DatabaseException { Resource template = All.getTemplate(graph, runtimeDiagram); if (template == null) return null; DiagramResource DIA = DiagramResource.getInstance(graph); Resource diagram2 = graph.getPossibleObject(runtimeDiagram, DIA.RuntimeDiagram_HasConfiguration); StyleInfo info = new StyleInfo(); DrawingTemplateInfo templateInfo = new DrawingTemplateInfo(template); info.template = template; info.name2table = graph.syncRequest(templateInfo, TransientCacheListener.> instance()); if (info.name2table == null) info.name2table = new TreeMap(); //info.digest = templateInfo.getDigest(); //System.err.println(templateInfo.getDigest()); info.flags = graph.syncRequest(new FlagInfos(diagram2), TransientCacheListener.> instance()); if (info.flags == null) info.flags = Collections.emptyList(); return info; } @Override public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, StyleInfo info) { if (info == null || info.flags == null || info.name2table == null) return; cleanupStyleForItem(evaluationContext, map, item); // render tables Iterator> it = info.name2table.entrySet().iterator(); while(it.hasNext()) { Entry entry = it.next(); String name = entry.getKey(); FlagTableInfo table = entry.getValue(); RenderTable com = new RenderTable(evaluationContext, table, name); com.perform(); // render header texts RenderHeaderTexts com3 = new RenderHeaderTexts(evaluationContext, table, name); com3.perform(); } // render texts of flags // for (FlagInfo flagInfo : info.flags) { // RenderFlagTexts com2 = new RenderFlagTexts(evaluationContext, flagInfo, info.name2table); // com2.perform(); // } } /** * This is just for debugging where the invisible IO row elements are. * Not intended to be enabled. */ private static class TestSceneGraph implements SceneGraph { private static final long serialVersionUID = 5749410674482131633L; public static final TestSceneGraph INSTANCE = new TestSceneGraph(); private static final Key KEY_SG = new SceneGraphNodeKey(INode.class, "DEBUG_SG_NODE"); @Override public void init(IElement e, G2DParentNode parent) { ShapeNode node = parent.getOrCreateNode("debug", ShapeNode.class); if (node != null) { node.setTransform(new AffineTransform(ElementUtils.getTransform(e))); node.setShape(ElementUtils.getElementBounds(e)); node.setFill(true); node.setColor(Color.CYAN); e.setHint(KEY_SG, node); } } @Override public void cleanup(IElement e) { ElementUtils.removePossibleNode(e, KEY_SG); } } static class RowElementSelectionHandler implements SelectionSpecification { /** * */ private static final long serialVersionUID = 2539536175525595035L; static final RowElementSelectionHandler INSTANCE = new RowElementSelectionHandler(); private RowElementSelectionHandler() { } @Override public Object getAdapter(Class adapter) { // outline color if (adapter.equals(OutlineColorSpec.class)){ return new OutlineColorSpec() { /** * */ private static final long serialVersionUID = 2372559366005877243L; @Override public void setColor(IElement e, Color c) { } @Override public Color getColor(IElement e) { return new Color(10,10,10,40); } }; } if (adapter.equals(FillColor.class)){ return new FillColor() { /** * */ private static final long serialVersionUID = 558080965120741509L; @Override public void setFillColor(IElement e, Color c) { // TODO Auto-generated method stub } @Override public Color getFillColor(IElement e) { return new Color(10,10,10,40); } }; } if (adapter.equals(Outline.class)){ return new Outline() { /** * */ private static final long serialVersionUID = 272200345438045483L; @Override public Shape getElementShape(IElement e) { return e.getHint(ElementHints.KEY_BOUNDS); } }; } if (adapter.equals(Transform.class)){ return new Transform() { /** * */ private static final long serialVersionUID = 7653122570884609688L; @Override public AffineTransform getTransform(IElement e) { return e.getHint(ElementHints.KEY_TRANSFORM); } @Override public void setTransform(IElement e, AffineTransform at) { } }; } if (adapter.equals(StrokeSpec.class)){ return new StrokeSpec() { /** * */ private static final long serialVersionUID = 1074910311375484373L; @Override public Stroke getStroke(IElement e) { return new BasicStroke(0.15f, BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE, 10.0f, null, 0.0f); } @Override public void setStroke(IElement e, Stroke at) { // TODO Auto-generated method stub } }; } // TODO Auto-generated method stub return null; } } static class RowElementHandler implements InternalSize, Transform, ElementAdapter { static final RowElementHandler INSTANCE = new RowElementHandler(); private static final long serialVersionUID = 829379327756475944L; private RowElementHandler() { } @Override public Rectangle2D getBounds(IElement e, Rectangle2D size) { if (size == null) size = new Rectangle2D.Double(); Rectangle2D r = e.getHint(ElementHints.KEY_BOUNDS); size.setFrame(r); return size; } @Override public AffineTransform getTransform(IElement e) { AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM); return at != null ? at : new AffineTransform(); } @Override public void setTransform(IElement e, AffineTransform at) { e.setHint(ElementHints.KEY_TRANSFORM, at.clone()); } @SuppressWarnings("unchecked") @Override public T adapt(IElement e, Class toClass) { if (toClass == Resource.class) { Wrapper ref = e.getHint(ElementHints.KEY_OBJECT); if (ref != null) return (T) ref.get(); } return null; } } public static class RenderHeaderTexts { private FlagTableInfo table = null; private String tableName = null; private EvaluationContext evaluationContext = null; public RenderHeaderTexts(EvaluationContext evaluationContext, FlagTableInfo table, String tableName){ this.table = table; this.tableName = tableName; this.evaluationContext = evaluationContext; } public void perform(){ if (table == null || evaluationContext == null || tableName == null) return; Session session = Simantics.peekSession(); if (session == null) return; G2DSceneGraph sg = evaluationContext.getSceneGraph(); if (sg == null) return; for (int k=0;k monitorInfos = slotcolumn.columnHeaders; if (monitorInfos == null) continue; int cellInx = 0; for (MonitorInfo slotColumnData:monitorInfos){ String cellNodeName = null; cellNodeName = cellLookupName(tableName, k, 0, cellInx); cellInx++; TextNode cellNode = (TextNode) NodeUtil.lookup(sg, cellNodeName); if (cellNode == null) continue; String txtValue = ""; if (slotColumnData != null && slotColumnData.getText() != null) txtValue = slotColumnData.getText(); final String text = txtValue; try { String value = ""; if (slotColumnData.getResource() != null) value = Simantics.getSession().sync(new EvaluatePath(slotColumnData.getResource(), text)); // , TransientCacheListener. instance() if (value != null) cellNode.setText(value.toString()); } catch (DatabaseException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } // public class RenderFlagTexts { // private FlagInfo flagInfo = null; // private TreeMap tables = null; // private EvaluationContext evaluationContext = null; // // public RenderFlagTexts(EvaluationContext evaluationContext, FlagInfo flagInfo, TreeMap tables){ // this.flagInfo = flagInfo; // this.tables = tables; // this.evaluationContext = evaluationContext; // } // // public void perform(){ // Session session = Simantics.peekSession(); // if (session == null) // return; // // FlagTableInfo table = tables.get(flagInfo.flagTableName); // if (table == null) // return; // // G2DSceneGraph sg = evaluationContext.getSceneGraph(); // if (sg == null) // return; // // INode tableNode = sg.lookupNode(tableLookupName(flagInfo.flagTableName)); // if (tableNode == null) // return; // // Rectangle2D rowBounds = new Rectangle2D.Double(0, table.getRowHeight()*flagInfo.flagTableRowIndex, table.getWidth(), table.getRowHeight()); // rowBounds = GeometryUtils.transformShape(rowBounds, table.affineTransform).getBounds2D(); // addFlagElement(evaluationContext, tableNode, rowBounds, flagInfo.flag); // // for (int k=0;k monitorInfos = slotcolumn.columnDatas; // if (monitorInfos == null) // continue; // // int cellInx = 0; // for (MonitorInfo slotColumnData:monitorInfos){ // String cellNodeName = cellLookupName(flagInfo.flagTableName, k, flagInfo.flagTableRowIndex, cellInx); // cellInx++; // TextNode cellNode = (TextNode) NodeUtil.lookup(sg, cellNodeName); // if (cellNode == null) // continue; // // String txtValue = ""; // if (slotColumnData != null && slotColumnData.getText() != null) // txtValue = slotColumnData.getText(); // final String text = txtValue; // // try { // String value = null; // if (slotcolumn.getType() != FlagTableColumnInfo.TYPE_ROW_NUMBERING) // value = Simantics.getSession().sync(new EvaluatePath(flagInfo.flag, text)); // , TransientCacheListener. instance() // if (value != null) // cellNode.setText(value.toString()); // } catch (DatabaseException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // } // } // } // } public static class EvaluatePath extends ResourceRead { private String path = null; public EvaluatePath(Resource res, String path) { super(res); this.path = path; // this.flagInfo = flagInfo; } @Override public String perform(ReadGraph g) throws DatabaseException { PredefinedVariables vars = PredefinedVariables.getInstance(); Variable resourceVariable = Variables.getVariable(g, resource); //v = g.adapt(this.flag, Variable.class); if (resourceVariable == null) return ""; Variable property = vars.getVariable(g, path, resource, resourceVariable); Object value = null; if (property == null) return ""; try { value = property.getValue(g); } catch (DatabaseException ex){ } if (value == null || !(value instanceof String)) return ""; return value.toString(); } } public static class RenderTable { private FlagTableInfo table = null; private String tableName = null; private EvaluationContext evaluationContext = null; public RenderTable(EvaluationContext evaluationContext, FlagTableInfo table, String tableName){ this.table = table; this.evaluationContext = evaluationContext; this.tableName = tableName; } public void perform(){ // render table G2DSceneGraph sg = evaluationContext.getSceneGraph(); if (sg == null) return; G2DParentNode nav = (G2DParentNode) sg.getNode(SceneGraphConstants.NAVIGATION_NODE_NAME); if (nav == null) return; String tableNodeName = tableLookupName(tableName); RTreeNode tableNode = (RTreeNode) sg.getNode(tableNodeName); if (tableNode == null){ tableNode = ProfileVariables.claimChild(evaluationContext.getSceneGraph(), SceneGraphConstants.NAVIGATION_NODE_NAME, tableNodeName, RTreeNode.class, //SingleElementNode.class, evaluationContext); } if (tableNode == null) return; tableNode.setTransform(new AffineTransform(table.affineTransform)); //tableNode.setVisible(false); tableNode.setLookupId(tableNodeName); // Initialize row node book-keeping for this table evaluationContext.setProperty(tableNode, SLOT_TABLE_ELEMENTS, new ArrayList()); Integer rowCount = table.getRowCount(); if (rowCount <= 0) return; // test if table is already generated String colNodeName = SLOT_TABLE_PREFIX + tableName + SLOT_TABLE_SEPARATOR + "0"; RTreeNode colNode = (RTreeNode) sg.getNode(colNodeName); if (colNode != null) return; tableNode.setZIndex(2); //if (table.columns.size()> 0){ // draw horisontal lines for (int k=-1;k 0) { for (FlagTableColumnInfo column : table.columns) { maxColumnHeaders = Math.max(maxColumnHeaders, column.columnHeaders.size()); } } // generate columns and cells String cellNodeName = null; TextNode cellNode = null; for (int k = 0;k monitorInfos = slotcolumn.columnDatas; if (lineInx < maxColumnHeaders) monitorInfos = slotcolumn.columnHeaders; int cellInx = 0; for (MonitorInfo slotColumnData:monitorInfos){ cellNodeName = cellLookupName(tableName, k, lineInx, cellInx); cellInx++; cellNode = ProfileVariables.claimChild(evaluationContext.getSceneGraph(), SceneGraphConstants.NAVIGATION_NODE_NAME+"."+tableNodeName+"."+colNodeName, cellNodeName, TextNode.class, evaluationContext); if (cellNode == null) continue; cellNode.setLookupId(cellNodeName); AffineTransform transform = new AffineTransform(); if (slotColumnData != null){ if (slotColumnData.getFont() != null) cellNode.setFont(Fonts.awt(slotColumnData.getFont())); if (slotColumnData.getColor() != null) cellNode.setColor(Colors.awt(slotColumnData.getColor())); if (slotColumnData.getTransform() != null) transform = new AffineTransform(slotColumnData.getTransform()); } cellNode.setVerticalAlignment((byte) 3); double cellY = table.getRowHeight()*lineInx+DrawingTemplateInfo.BASELINE_VERTICAL_OFFSET*table.getRowHeight(); //cellNode.setText("yÅcolumn_" + k); cellNode.setText(""); if (lineInx >= maxColumnHeaders && slotcolumn.getType() == FlagTableColumnInfo.TYPE_ROW_NUMBERING){ // set row number cellNode.setText(new Integer(lineInx-maxColumnHeaders+slotcolumn.getStartOffset()).toString()); cellNode.setHorizontalAlignment((byte) 0); } // FontMetrics metrics = new FontMetrics(cellNode.getFont()); // double dy = (metrics.getAscent() + metrics.getLeading())/2; cellNode.setBorderWidth(0.f); cellNode.setPadding(0.0, 0.0); Rectangle2D bounds = cellNode.getBoundsInLocal(); //double dy = bounds.getHeight()/2.0; transform.translate(0.0, cellY); cellNode.setTransform(transform); cellNode.setFixedWidth(cellWidth); } } } //tableNode.setVisible(true); } void addLine(String nodeId, int x1, int y1, int x2, int y2, int cap, RTreeNode parent){ Line2D line = new Line2D.Float(x1, y1, x2, y2); BasicStroke stroke = new BasicStroke(TranslateFlag.lineWidth, cap, BasicStroke.JOIN_MITER); ShapeNode shape = parent.addNode(nodeId, ShapeNode.class); // SingleElementNode shape.setShape(line); shape.setScaleStroke(false); shape.setStroke(stroke); shape.setFill(false); shape.setColor(new Color(0, 0, 0)); shape.setZIndex(10); } } protected void cleanupStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item) { INode root = evaluationContext.getSceneGraph(); if (root instanceof G2DSceneGraph) { G2DSceneGraph sg = (G2DSceneGraph) root; G2DParentNode nav = (G2DParentNode) sg.getNode(SceneGraphConstants.NAVIGATION_NODE_NAME); // ProfileVariables.denyChildren(nav, SLOT_TABLE_PREFIX); IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM); for (String childId : NodeUtil.filterDirectChildIds(nav, SLOT_TABLE_PREFIX)) { INode child = nav.getNode(childId); List elements = evaluationContext.setProperty(child, SLOT_TABLE_ELEMENTS, null); if (elements != null && diagram != null) for (IElement e : elements) cleanupElement(diagram, e); nav.removeNode(childId); } } } private void cleanupElement(IDiagram diagram, IElement e) { diagram.removeElement(e); } /** * @param tableName slot table name * @return lookup ID for the cell node */ private static String tableLookupName(String tableName) { return new StringBuilder() .append(SLOT_TABLE_PREFIX) .append(tableName).toString(); } /** * @param tableName slot table name * @param column 0..C-1, where C is the amount of columns * @param row 0..N, where 0 is header * @return lookup ID for the cell node */ private static String cellLookupName(String tableName, int column, int row, int index) { return new StringBuilder() .append(SLOT_TABLE_PREFIX) .append(tableName) .append(SLOT_TABLE_SEPARATOR) .append(column) .append(SLOT_TABLE_SEPARATOR) .append(row) .append(SLOT_TABLE_SEPARATOR) .append(index).toString(); } private IElement addFlagElement(EvaluationContext evaluationContext, INode tableNode, Rectangle2D bounds, Resource flag) { IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM); assert diagram != null; List elements = evaluationContext.getProperty(tableNode, SLOT_TABLE_ELEMENTS); assert elements != null; IElement e = newFlagElement(evaluationContext, bounds, flag); elements.add(e); diagram.addElement(e); return e; } private IElement newFlagElement(EvaluationContext evaluationContext, Rectangle2D bounds, Resource flag) { IElement e = Element.spawnNew(rowElementClass); e.setHint(ElementHints.KEY_BOUNDS, bounds.clone()); e.setHint(ElementHints.KEY_TRANSFORM, new AffineTransform()); // Just incase there's problems with same objects in multiple diagram elements //e.setHint(ElementHints.KEY_OBJECT, flag); // Didn't seem to help though. e.setHint(ElementHints.KEY_OBJECT, new Wrapper(flag)); return e; } @Override public String toString() { return "Flag table style"; } /** * IO table flag elements need a wrapper KEY_OBJECT whose toString() * produces something else besides {@link Resource#toString()}. */ public static class Wrapper extends AtomicReference { private static final long serialVersionUID = -4041629837352874410L; public Wrapper(Resource r) { super(r); } @Override public String toString() { return "IO-" + super.toString(); } } }