]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/profile/TextGridStyle.java
Preliminary implementation to update only changed profile entries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / profile / TextGridStyle.java
1 package org.simantics.diagram.profile;
2
3 import java.awt.Color;
4 import java.awt.Font;
5 import java.awt.geom.AffineTransform;
6 import java.awt.geom.Point2D;
7 import java.awt.geom.Rectangle2D;
8 import java.util.Comparator;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.stream.Collectors;
13
14 import org.simantics.databoard.Bindings;
15 import org.simantics.datatypes.literal.Vec2d;
16 import org.simantics.db.ReadGraph;
17 import org.simantics.db.Resource;
18 import org.simantics.db.exception.DatabaseException;
19 import org.simantics.db.layer0.variable.RVI;
20 import org.simantics.db.layer0.variable.Variable;
21 import org.simantics.diagram.elements.ITextListener;
22 import org.simantics.diagram.elements.TextGridNode;
23 import org.simantics.diagram.elements.TextNode;
24 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
25 import org.simantics.g2d.utils.Alignment;
26 import org.simantics.modeling.ModelingResources;
27 import org.simantics.scenegraph.INode;
28 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
29 import org.simantics.scenegraph.profile.EvaluationContext;
30 import org.simantics.scenegraph.profile.common.ProfileVariables;
31 import org.simantics.scenegraph.utils.GeometryUtils;
32 import org.simantics.scenegraph.utils.NodeUtil;
33 import org.simantics.scl.runtime.function.Function1;
34 import org.simantics.ui.colors.Colors;
35 import org.simantics.utils.datastructures.Pair;
36
37 /**
38  * @author Antti Villberg
39  * @author Tuukka Lehtonen
40  */
41 public abstract class TextGridStyle extends StyleBase<MonitorTextGridResult> {
42
43         private final Font  FONT             = Font.decode("Arial 12");
44         private final Color BACKGROUND_COLOR = new Color(255, 255, 255, 192);
45         private static final Rectangle2D EMPTY_BOUNDS = new Rectangle2D.Double(0, 0, 0, 0);
46
47         protected double xOffset;
48         protected double yOffset;
49
50         public TextGridStyle(Resource r) {
51                 this(r, 0.0, 2.1);
52         }
53
54         public TextGridStyle(Resource r, double xOffset, double yOffset) {
55             super(r);
56                 this.xOffset = xOffset;
57                 this.yOffset = yOffset;
58         }
59
60         public Resource getPropertyRelation(ReadGraph g, Resource module) {
61         throw new Error("Fix this");
62         }
63
64         /**
65          * @return the name of the scene graph node to create to represent the text
66          *         element created by this style
67          */
68         public String getNodeName() {
69                 return getClass().getSimpleName();
70         }
71
72         /**
73          * Override to customize.
74          * 
75          * @param graph
76          * @param element
77          * @return
78          * @throws DatabaseException
79          */
80         protected Resource getConfigurationComponent(ReadGraph graph, Resource element) throws DatabaseException {
81                 ModelingResources mr = ModelingResources.getInstance(graph);
82                 Resource config = graph.getPossibleObject(element, mr.ElementToComponent);
83                 return config;
84         }
85
86         /**
87          * Override to customize.
88          * 
89          * @param graph
90          * @param element
91          * @return
92          * @throws DatabaseException
93          */
94         protected String getConfigurationComponentNameForElement(ReadGraph graph, Resource element) throws DatabaseException {
95                 Resource config = getConfigurationComponent(graph, element);
96                 if (config == null)
97                         return null;
98                 String name = graph.getPossibleRelatedValue(config, getPropertyRelation(graph,element), Bindings.STRING);
99                 return name;
100         }
101
102         public AffineTransform getTransform(INode node, AffineTransform parentTransform, Rectangle2D elementBounds, int location, boolean up) {
103                 return getTransform(parentTransform, elementBounds, location, up);
104         }
105
106         public AffineTransform getTransform(AffineTransform parentTransform, Rectangle2D elementBounds, int location, boolean up) {
107
108                 double scale = GeometryUtils.getScale(parentTransform);
109
110                 AffineTransform at = AffineTransform.getTranslateInstance(
111                                 parentTransform.getTranslateX() + elementBounds.getCenterX() * scale + xOffset,
112                                 parentTransform.getTranslateY() + elementBounds.getMinY() * scale + yOffset*(location-1) + (up ? 0.0 : (2.0*yOffset) + elementBounds.getHeight() * scale) 
113                                 );
114                 at.scale(0.15, 0.15);
115
116                 return at;
117
118         }
119
120         protected String rowId() {
121                 return "";
122         }
123
124         @Override
125         public MonitorTextGridResult calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource element, Variable configuration)
126                         throws DatabaseException {
127                 String name = getConfigurationComponentNameForElement(graph, element);
128                 if (name == null)
129                         return null;
130                 AffineTransform transform = DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, element);
131                 Vec2d offset = DiagramGraphUtil.getOffset(graph, element);
132                 boolean enabled = !DiagramGraphUtil.getProfileMonitorsHidden(graph, element);
133                 boolean up = DiagramGraphUtil.getProfileMonitorsUp(graph, element);
134                 double spacing = DiagramGraphUtil.getProfileMonitorSpacing(graph, element);
135                 return new MonitorTextGridResult(rowId(), name, "", "", enabled, up, spacing, null, null, null, transform, offset);
136         }
137
138         // Not to be modified
139         protected final static Point2D[] DEFAULT_CELL_OFFSETS = new Point2D[] {
140                         new Point2D.Double(-45, 0),
141                         new Point2D.Double(22, 0),
142                         new Point2D.Double(24, 0)
143         };
144
145         // Not to be modified
146         protected final static Point2D[] ZERO_CELL_OFFSETS = new Point2D[] {
147                         new Point2D.Double(0, 0),
148                         new Point2D.Double(0, 0),
149                         new Point2D.Double(0, 0)
150         };
151
152         protected Point2D[] getCellOffsets() {
153                 return DEFAULT_CELL_OFFSETS;
154         }
155
156         @Override
157         public void applyStyleForNode(EvaluationContext observer, INode _node, MonitorTextGridResult result) {
158                 String value = result != null ? result.getText1() : null;
159                 boolean enabled = result != null ? result.getEnabled() : false;
160
161                 if (value != null && enabled) {
162
163                         Map<String, Pair<TextGridStyle, MonitorTextGridResult>> rows = observer.getProperty(_node, "rows");
164                         if (rows == null) {
165                                 rows = new HashMap<String, Pair<TextGridStyle, MonitorTextGridResult>>();
166                                 observer.setProperty(_node, "rows", rows);
167                         }
168                         Pair<TextGridStyle, MonitorTextGridResult> oldResultPair = rows.get(result.getRowId());
169                         if (oldResultPair != null && oldResultPair.first == this && oldResultPair.second.sameStructure(result)) {
170                                 return;
171                         }
172
173                         rows.put(rowIdKey(), new Pair<TextGridStyle, MonitorTextGridResult>(this, result));
174
175                         // FIXME: Improve performance by calling refreshAll only once after all text grid style changes have been applied
176                         refreshAll(observer, _node);
177                 } else {
178                         cleanupStyleForNode(observer, _node);
179                 }
180         }
181
182         private static final Comparator<Pair<TextGridStyle, MonitorTextGridResult>> ROW_PRIORITY_COMPARATOR =
183                         (o1, o2) -> Double.compare(o1.first.getPriority(), o2.first.getPriority());
184
185         private static void refreshAll(EvaluationContext observer, INode _node) {
186                 final TextGridNode node = ProfileVariables.claimChild(_node, "", "TextGridStyle", TextGridNode.class, observer);
187                 if (node == null)
188                         return;
189
190                 int row = 0;
191                 Map<String, Pair<TextGridStyle, MonitorTextGridResult>> rows = observer.getProperty(_node, "rows");
192                 if (rows != null) {
193                         List<Pair<TextGridStyle, MonitorTextGridResult>> sortedRows = rows.values().stream()
194                                 .sorted(ROW_PRIORITY_COMPARATOR)
195                                 .collect(Collectors.toList());
196
197                         for (Pair<TextGridStyle, MonitorTextGridResult> resultPair : sortedRows) {
198                                 row++;
199                                 TextGridStyle style = resultPair.first;
200                                 MonitorTextGridResult result = resultPair.second;
201
202                                 String value = result != null ? result.getText1() : null;
203                                 String value2 = result != null ? result.getText2() : null;
204                                 String value3 = result != null ? result.getText3() : null;
205
206                                 double spacing = result.getSpacing();
207
208                                 final Function1<String, String> modifier = result != null ? result.getModifier() : null;
209                                 final Function1<String, String> validator = result != null ? result.getValidator() : null;
210                                 final Function1<Vec2d, Boolean> translator = result != null ? result.getTranslator() : null;
211                                 final RVI rvi = result != null ? result.getRVI() : null;
212
213                                 node.setRowId(row, result.getRowId());
214
215                                 //setCurrentRowNumber(observer, _node, result.getRowId(), row);
216
217                                 //observer.setTemporaryProperty(_node, "location", row + 1);
218
219                                 node.setText(2, row, value2);
220                                 node.setUp(result.getUp());
221
222                                 //MonitorTextGridResult cache = node.getCache(1, row);
223                                 //if(cache != null && cache.sameStructure(result)) return;
224
225                                 node.setCache(1, row, result);
226
227                                 boolean isConnection = _node instanceof ConnectionNode;
228
229                                 Rectangle2D elementBounds = isConnection ? EMPTY_BOUNDS : NodeUtil.getLocalElementBounds(_node);
230                                 if (elementBounds == null) {
231                                         new Exception("Cannot get local element bounds for node " + _node.toString()).printStackTrace();
232                                         // This is here for checking why getLocalElementBounds failed in the debugger.
233                                         NodeUtil.getLocalElementBounds(_node);
234                                         return;
235                                 }
236         
237                                 //System.err.println("elementBounds " + elementBounds);
238                                 //System.err.println("parentTransform " + result.getParentTransform());
239
240                                 AffineTransform at = style.getTransform(_node,result.getParentTransform(), elementBounds, row, result.getUp());
241                                 Vec2d offset = result.getOffset();
242
243                                 Point2D[] cellOffsets = style.getCellOffsets();
244
245                                 AffineTransform at1 = new AffineTransform(at);
246                                 at1.translate(cellOffsets[0].getX(),cellOffsets[0].getY());
247                                 AffineTransform at2 = new AffineTransform(at);
248                                 at2.translate(cellOffsets[1].getX()+spacing,cellOffsets[1].getY());
249                                 AffineTransform at3 = new AffineTransform(at);
250                                 at3.translate(cellOffsets[2].getX()+spacing,cellOffsets[2].getY());
251
252                                 at1.translate(offset.x, offset.y);
253                                 at2.translate(offset.x, offset.y);
254                                 at3.translate(offset.x, offset.y);
255
256                                 node.setTransform(1, row, at1);
257                                 node.setTransform(2, row, at2);
258                                 node.setTransform(3, row, at3);
259
260                                 Alignment[] alignments = result.getAlignments();
261                                 if(alignments != null) {
262                                         node.setHorizontalAlignment(1, row, (byte) alignments[0].ordinal());
263                                         node.setHorizontalAlignment(2, row, (byte) alignments[1].ordinal());
264                                         node.setHorizontalAlignment(3, row, (byte) alignments[2].ordinal());
265                                 } else {
266                                         node.setHorizontalAlignment(1, row, (byte) style.getAlignment(1).ordinal());
267                                         node.setHorizontalAlignment(2, row, (byte) style.getAlignment(2).ordinal());
268                                         node.setHorizontalAlignment(3, row, (byte) style.getAlignment(3).ordinal());
269                                 }
270
271                                 Alignment[] verticalAlignments = result.getVerticalAlignments();
272                                 if(verticalAlignments != null) {
273                                         node.setVerticalAlignment(1, row, (byte) verticalAlignments[0].ordinal());
274                                         node.setVerticalAlignment(2, row, (byte) verticalAlignments[1].ordinal());
275                                         node.setVerticalAlignment(3, row, (byte) verticalAlignments[2].ordinal());
276                                 } else {
277                                         node.setVerticalAlignment(1, row, (byte) style.getVerticalAlignment(1).ordinal());
278                                         node.setVerticalAlignment(2, row, (byte) style.getVerticalAlignment(2).ordinal());
279                                         node.setVerticalAlignment(3, row, (byte) style.getVerticalAlignment(3).ordinal());
280                                 }
281
282                                 node.setZIndex(3000);
283         
284                                 org.simantics.common.color.Color color = result.getColor();
285                                 Color awtColor = color != null ? Colors.awt(color) : Color.DARK_GRAY;
286                                 Color bgColor = style.getBackgroundColor();
287                                 Font font = style.getFont();
288                                 style.setTextNodeData(node, 1, row, value, font, awtColor, bgColor);
289                                 style.setTextNodeData(node, 2, row, value2, result.getPending(), font, awtColor, bgColor);
290                                 style.setTextNodeData(node, 3, row, value3, font, awtColor, bgColor);
291
292                                 node.setEditable(1, row, false);
293                                 node.setForceEventListening(2, row, true);
294                                 node.setEditable(2, row, modifier != null);
295                                 node.setEditable(3, row, false);
296
297                                 final int finalRow = row;
298
299                                 if (modifier != null) {
300                                         node.setTextListener(2, row, new ITextListener() {
301                                                 @Override
302                                                 public void textChanged() {}
303
304                                                 @Override
305                                                 public void textEditingStarted() {}
306
307                                                 @Override
308                                                 public void textEditingCancelled() {
309                                                 }
310
311                                                 @Override
312                                                 public void textEditingEnded() {
313
314                                                         TextNode t = node.get(2, finalRow);
315                                                         if (t == null)
316                                                                 return;
317
318                                                         if(!t.getText().equals(t.getTextBeforeEdit()))
319                                                                 modifier.apply(t.getText());
320
321                                                 }
322                                         });
323                                 } else {
324                                         node.setTextListener(2, row, null);
325                                 }
326
327                                 node.setInputValidator(2, row, validator);
328                                 node.setTranslator(translator);
329
330                                 node.setRVI(2, row, rvi);
331
332                                 style.postProcessNode(node, row);
333                         }
334                 }
335                 // remove excess rows
336                 int rowCount = node.computeRows();
337                 while (row < rowCount) {
338                         row++;
339                         node.removeRow(row);
340                 }
341         }
342
343         private void setTextNodeData(TextGridNode node, int x, int y, String text, Font font, Color fgColor, Color bgColor) {
344                 if (text != null) {
345                         node.setText(x, y, text);
346                         node.setFont(x, y, font);
347                         node.setColor(x, y, fgColor);
348                         node.setBackgroundColor(x, y, bgColor);
349                 } else {
350                         // Prevent rendering of the node.
351                         node.setFont(x, y, null);
352                 }
353         }
354
355         private void setTextNodeData(TextGridNode node, int x, int y, String text, boolean pending, Font font, Color fgColor, Color bgColor) {
356                 setTextNodeData(node, x, y, text, font, fgColor, bgColor);
357                 node.setPending(x, y, pending);
358         }
359
360         protected Font getFont() {
361                 return FONT;
362         }
363
364         protected Color getBackgroundColor() {
365                 return BACKGROUND_COLOR;
366         }
367
368         protected Alignment getAlignment(int column) {
369                 switch(column) {
370                 case 1: return Alignment.TRAILING;
371                 case 2: return Alignment.TRAILING;
372                 case 3: return Alignment.LEADING;
373                 default: return Alignment.LEADING;
374                 }
375         }
376
377         protected Alignment getVerticalAlignment(int column) {
378                 return Alignment.TRAILING;
379         }
380
381         @Override
382         protected void cleanupStyleForNode(EvaluationContext observer, INode _node) {
383                 Map<String, Pair<Double, MonitorTextGridResult>> rows = observer.getProperty(_node, "rows");
384                 if (rows != null) {
385                         rows.remove(rowIdKey());
386                         if (rows.isEmpty()) {
387                                 observer.setProperty(_node, "rows", null);
388                         }
389                 }
390                 refreshAll(observer, _node);
391         }
392
393         protected void postProcessNode(TextGridNode node, int row) {
394         }
395
396         private String rowIdKey() {
397                 return "style" + getIdentity().toString();
398         }
399
400 }