package org.simantics.diagram.profile; import java.awt.Color; import java.awt.Font; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.simantics.databoard.Bindings; import org.simantics.datatypes.literal.Vec2d; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.RVI; import org.simantics.db.layer0.variable.Variable; import org.simantics.diagram.elements.ITextListener; import org.simantics.diagram.elements.TextGridNode; import org.simantics.diagram.elements.TextNode; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.g2d.utils.Alignment; import org.simantics.modeling.ModelingResources; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.nodes.ConnectionNode; import org.simantics.scenegraph.profile.EvaluationContext; import org.simantics.scenegraph.profile.common.ProfileVariables; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.scl.runtime.function.Function1; import org.simantics.ui.colors.Colors; import org.simantics.utils.datastructures.Pair; /** * @author Antti Villberg * @author Tuukka Lehtonen */ public abstract class TextGridStyle extends StyleBase { private final Font FONT = Font.decode("Arial 12"); private final Color BACKGROUND_COLOR = new Color(255, 255, 255, 192); private static final Rectangle2D EMPTY_BOUNDS = new Rectangle2D.Double(0, 0, 0, 0); protected double xOffset; protected double yOffset; public TextGridStyle(Resource r) { this(r, 0.0, 2.1); } public TextGridStyle(Resource r, double xOffset, double yOffset) { super(r); this.xOffset = xOffset; this.yOffset = yOffset; } public Resource getPropertyRelation(ReadGraph g, Resource module) { throw new Error("Fix this"); } /** * @return the name of the scene graph node to create to represent the text * element created by this style */ public String getNodeName() { return getClass().getSimpleName(); } /** * Override to customize. * * @param graph * @param element * @return * @throws DatabaseException */ protected Resource getConfigurationComponent(ReadGraph graph, Resource element) throws DatabaseException { ModelingResources mr = ModelingResources.getInstance(graph); Resource config = graph.getPossibleObject(element, mr.ElementToComponent); return config; } /** * Override to customize. * * @param graph * @param element * @return * @throws DatabaseException */ protected String getConfigurationComponentNameForElement(ReadGraph graph, Resource element) throws DatabaseException { Resource config = getConfigurationComponent(graph, element); if (config == null) return null; String name = graph.getPossibleRelatedValue(config, getPropertyRelation(graph,element), Bindings.STRING); return name; } public AffineTransform getTransform(INode node, AffineTransform parentTransform, Rectangle2D elementBounds, int location, boolean up) { return getTransform(parentTransform, elementBounds, location, up); } public AffineTransform getTransform(AffineTransform parentTransform, Rectangle2D elementBounds, int location, boolean up) { double scale = GeometryUtils.getScale(parentTransform); AffineTransform at = AffineTransform.getTranslateInstance( parentTransform.getTranslateX() + elementBounds.getCenterX() * scale + xOffset, parentTransform.getTranslateY() + elementBounds.getMinY() * scale + yOffset*(location-1) + (up ? 0.0 : (2.0*yOffset) + elementBounds.getHeight() * scale) ); at.scale(0.15, 0.15); return at; } protected String rowId() { return ""; } @Override public MonitorTextGridResult calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource element, Variable configuration) throws DatabaseException { String name = getConfigurationComponentNameForElement(graph, element); if (name == null) return null; AffineTransform transform = DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, element); Vec2d offset = DiagramGraphUtil.getOffset(graph, element); boolean enabled = !DiagramGraphUtil.getProfileMonitorsHidden(graph, element); boolean up = DiagramGraphUtil.getProfileMonitorsUp(graph, element); double spacing = DiagramGraphUtil.getProfileMonitorSpacing(graph, element); return new MonitorTextGridResult(rowId(), name, "", "", enabled, up, spacing, null, null, null, transform, offset); } // Not to be modified protected final static Point2D[] DEFAULT_CELL_OFFSETS = new Point2D[] { new Point2D.Double(-45, 0), new Point2D.Double(22, 0), new Point2D.Double(24, 0) }; // Not to be modified protected final static Point2D[] ZERO_CELL_OFFSETS = new Point2D[] { new Point2D.Double(0, 0), new Point2D.Double(0, 0), new Point2D.Double(0, 0) }; protected Point2D[] getCellOffsets() { return DEFAULT_CELL_OFFSETS; } @Override public void applyStyleForNode(EvaluationContext observer, INode _node, MonitorTextGridResult result) { String value = result != null ? result.getText1() : null; boolean enabled = result != null ? result.getEnabled() : false; if (value != null && enabled) { Map> rows = observer.getProperty(_node, "rows"); if (rows == null) { rows = new HashMap>(); observer.setProperty(_node, "rows", rows); } Pair oldResultPair = rows.get(result.getRowId()); if (oldResultPair != null && oldResultPair.first == this && oldResultPair.second.sameStructure(result)) { return; } rows.put(rowIdKey(), new Pair(this, result)); // FIXME: Improve performance by calling refreshAll only once after all text grid style changes have been applied refreshAll(observer, _node); } else { cleanupStyleForNode(observer, _node); } } private static final Comparator> ROW_PRIORITY_COMPARATOR = (o1, o2) -> Double.compare(o1.first.getPriority(), o2.first.getPriority()); private static void refreshAll(EvaluationContext observer, INode _node) { final TextGridNode node = ProfileVariables.claimChild(_node, "", "TextGridStyle", TextGridNode.class, observer); if (node == null) return; int row = 0; Map> rows = observer.getProperty(_node, "rows"); if (rows != null) { List> sortedRows = rows.values().stream() .sorted(ROW_PRIORITY_COMPARATOR) .collect(Collectors.toList()); for (Pair resultPair : sortedRows) { row++; TextGridStyle style = resultPair.first; MonitorTextGridResult result = resultPair.second; String value = result != null ? result.getText1() : null; String value2 = result != null ? result.getText2() : null; String value3 = result != null ? result.getText3() : null; double spacing = result.getSpacing(); final Function1 modifier = result != null ? result.getModifier() : null; final Function1 validator = result != null ? result.getValidator() : null; final Function1 translator = result != null ? result.getTranslator() : null; final RVI rvi = result != null ? result.getRVI() : null; node.setRowId(row, result.getRowId()); //setCurrentRowNumber(observer, _node, result.getRowId(), row); //observer.setTemporaryProperty(_node, "location", row + 1); node.setText(2, row, value2); node.setUp(result.getUp()); //MonitorTextGridResult cache = node.getCache(1, row); //if(cache != null && cache.sameStructure(result)) return; node.setCache(1, row, result); boolean isConnection = _node instanceof ConnectionNode; Rectangle2D elementBounds = isConnection ? EMPTY_BOUNDS : NodeUtil.getLocalElementBounds(_node); if (elementBounds == null) { new Exception("Cannot get local element bounds for node " + _node.toString()).printStackTrace(); // This is here for checking why getLocalElementBounds failed in the debugger. NodeUtil.getLocalElementBounds(_node); return; } //System.err.println("elementBounds " + elementBounds); //System.err.println("parentTransform " + result.getParentTransform()); AffineTransform at = style.getTransform(_node,result.getParentTransform(), elementBounds, row, result.getUp()); Vec2d offset = result.getOffset(); Point2D[] cellOffsets = style.getCellOffsets(); AffineTransform at1 = new AffineTransform(at); at1.translate(cellOffsets[0].getX(),cellOffsets[0].getY()); AffineTransform at2 = new AffineTransform(at); at2.translate(cellOffsets[1].getX()+spacing,cellOffsets[1].getY()); AffineTransform at3 = new AffineTransform(at); at3.translate(cellOffsets[2].getX()+spacing,cellOffsets[2].getY()); at1.translate(offset.x, offset.y); at2.translate(offset.x, offset.y); at3.translate(offset.x, offset.y); node.setTransform(1, row, at1); node.setTransform(2, row, at2); node.setTransform(3, row, at3); Alignment[] alignments = result.getAlignments(); if(alignments != null) { node.setHorizontalAlignment(1, row, (byte) alignments[0].ordinal()); node.setHorizontalAlignment(2, row, (byte) alignments[1].ordinal()); node.setHorizontalAlignment(3, row, (byte) alignments[2].ordinal()); } else { node.setHorizontalAlignment(1, row, (byte) style.getAlignment(1).ordinal()); node.setHorizontalAlignment(2, row, (byte) style.getAlignment(2).ordinal()); node.setHorizontalAlignment(3, row, (byte) style.getAlignment(3).ordinal()); } Alignment[] verticalAlignments = result.getVerticalAlignments(); if(verticalAlignments != null) { node.setVerticalAlignment(1, row, (byte) verticalAlignments[0].ordinal()); node.setVerticalAlignment(2, row, (byte) verticalAlignments[1].ordinal()); node.setVerticalAlignment(3, row, (byte) verticalAlignments[2].ordinal()); } else { node.setVerticalAlignment(1, row, (byte) style.getVerticalAlignment(1).ordinal()); node.setVerticalAlignment(2, row, (byte) style.getVerticalAlignment(2).ordinal()); node.setVerticalAlignment(3, row, (byte) style.getVerticalAlignment(3).ordinal()); } node.setZIndex(3000); org.simantics.common.color.Color color = result.getColor(); Color awtColor = color != null ? Colors.awt(color) : Color.DARK_GRAY; Color bgColor = style.getBackgroundColor(); Font font = style.getFont(); style.setTextNodeData(node, 1, row, value, font, awtColor, bgColor); style.setTextNodeData(node, 2, row, value2, result.getPending(), font, awtColor, bgColor); style.setTextNodeData(node, 3, row, value3, font, awtColor, bgColor); node.setEditable(1, row, false); node.setForceEventListening(2, row, true); node.setEditable(2, row, modifier != null); node.setEditable(3, row, false); final int finalRow = row; if (modifier != null) { node.setTextListener(2, row, new ITextListener() { @Override public void textChanged() {} @Override public void textEditingStarted() {} @Override public void textEditingCancelled() { } @Override public void textEditingEnded() { TextNode t = node.get(2, finalRow); if (t == null) return; if(!t.getText().equals(t.getTextBeforeEdit())) modifier.apply(t.getText()); } }); } else { node.setTextListener(2, row, null); } node.setInputValidator(2, row, validator); node.setTranslator(translator); node.setRVI(2, row, rvi); style.postProcessNode(node, row); } } // remove excess rows int rowCount = node.computeRows(); while (row < rowCount) { row++; node.removeRow(row); } } private void setTextNodeData(TextGridNode node, int x, int y, String text, Font font, Color fgColor, Color bgColor) { if (text != null) { node.setText(x, y, text); node.setFont(x, y, font); node.setColor(x, y, fgColor); node.setBackgroundColor(x, y, bgColor); } else { // Prevent rendering of the node. node.setFont(x, y, null); } } private void setTextNodeData(TextGridNode node, int x, int y, String text, boolean pending, Font font, Color fgColor, Color bgColor) { setTextNodeData(node, x, y, text, font, fgColor, bgColor); node.setPending(x, y, pending); } protected Font getFont() { return FONT; } protected Color getBackgroundColor() { return BACKGROUND_COLOR; } protected Alignment getAlignment(int column) { switch(column) { case 1: return Alignment.TRAILING; case 2: return Alignment.TRAILING; case 3: return Alignment.LEADING; default: return Alignment.LEADING; } } protected Alignment getVerticalAlignment(int column) { return Alignment.TRAILING; } @Override protected void cleanupStyleForNode(EvaluationContext observer, INode _node) { Map> rows = observer.getProperty(_node, "rows"); if (rows != null) { rows.remove(rowIdKey()); if (rows.isEmpty()) { observer.setProperty(_node, "rows", null); } } refreshAll(observer, _node); } protected void postProcessNode(TextGridNode node, int row) { } private String rowIdKey() { return "style" + getIdentity().toString(); } }