]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/MonitorClass.java
Apply updates to font and color of monitor's prefix and suffix nodes
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / MonitorClass.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.elements;
13
14 import java.awt.BasicStroke;
15 import java.awt.Color;
16 import java.awt.Font;
17 import java.awt.FontMetrics;
18 import java.awt.Graphics2D;
19 import java.awt.Rectangle;
20 import java.awt.Shape;
21 import java.awt.geom.AffineTransform;
22 import java.awt.geom.Path2D;
23 import java.awt.geom.Point2D;
24 import java.awt.geom.Rectangle2D;
25 import java.util.EnumSet;
26 import java.util.Map;
27
28 import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
29 import org.simantics.db.layer0.variable.RVI;
30 import org.simantics.g2d.diagram.IDiagram;
31 import org.simantics.g2d.element.ElementClass;
32 import org.simantics.g2d.element.ElementHints;
33 import org.simantics.g2d.element.ElementUtils;
34 import org.simantics.g2d.element.IElement;
35 import org.simantics.g2d.element.SceneGraphNodeKey;
36 import org.simantics.g2d.element.handler.ElementHandler;
37 import org.simantics.g2d.element.handler.FillColor;
38 import org.simantics.g2d.element.handler.InternalSize;
39 import org.simantics.g2d.element.handler.LifeCycle;
40 import org.simantics.g2d.element.handler.Move;
41 import org.simantics.g2d.element.handler.Outline;
42 import org.simantics.g2d.element.handler.Rotate;
43 import org.simantics.g2d.element.handler.Scale;
44 import org.simantics.g2d.element.handler.SceneGraph;
45 import org.simantics.g2d.element.handler.StaticSymbol;
46 import org.simantics.g2d.element.handler.Text;
47 import org.simantics.g2d.element.handler.TextEditor;
48 import org.simantics.g2d.element.handler.TextEditor.Modifier;
49 import org.simantics.g2d.element.handler.Transform;
50 import org.simantics.g2d.element.handler.impl.BorderColorImpl;
51 import org.simantics.g2d.element.handler.impl.FillColorImpl;
52 import org.simantics.g2d.element.handler.impl.ParentImpl;
53 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;
54 import org.simantics.g2d.element.handler.impl.StaticSymbolImpl;
55 import org.simantics.g2d.element.handler.impl.TextColorImpl;
56 import org.simantics.g2d.element.handler.impl.TextEditorImpl;
57 import org.simantics.g2d.element.handler.impl.TextFontImpl;
58 import org.simantics.g2d.element.handler.impl.TextImpl;
59 import org.simantics.g2d.elementclass.MonitorHandler;
60 import org.simantics.g2d.image.Image;
61 import org.simantics.g2d.image.ProviderUtils;
62 import org.simantics.g2d.image.impl.AbstractImage;
63 import org.simantics.g2d.utils.Alignment;
64 import org.simantics.scenegraph.Node;
65 import org.simantics.scenegraph.g2d.G2DParentNode;
66 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
67 import org.simantics.scl.runtime.function.Function1;
68 import org.simantics.utils.datastructures.cache.IFactory;
69 import org.simantics.utils.datastructures.cache.IProvider;
70 import org.simantics.utils.datastructures.cache.ProvisionException;
71 import org.simantics.utils.datastructures.hints.IHintContext.Key;
72 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
73 import org.simantics.utils.strings.format.MetricsFormat;
74 import org.simantics.utils.strings.format.MetricsFormatList;
75
76 /**
77  * @author Tuukka Lehtonen
78  */
79 public class MonitorClass {
80
81     static final Font        FONT                      = Font.decode("Helvetica 12");
82
83     /**
84      * Back-end specific object describing the monitored component.
85      */
86     public static final Key  KEY_MONITOR_COMPONENT     = new KeyOf(Object.class, "MONITOR_COMPONENT");
87
88     /**
89      * Existence of this hint indicates that the monitor is showing a value that
90      * does not originate from the owner diagram.
91      */
92     public static final Key  KEY_MONITOR_IS_EXTERNAL   = new KeyOf(Boolean.class, "MONITOR_IS_EXTERNAL");
93
94     /**
95      * The valuation suffix string describing the monitored variable of the
96      * component described by {@link #KEY_MONITOR_COMPONENT}.
97      */
98     public static final Key  KEY_MONITOR_SUFFIX        = new KeyOf(String.class, "MONITOR_SUFFIX");
99
100     public static final Key  KEY_MONITOR_SUBSTITUTIONS = new KeyOf(Map.class, "MONITOR_SUBSTITUTIONS");
101     public static final Key  KEY_MONITOR_GC            = new KeyOf(Graphics2D.class, "MONITOR_GC");
102     public static final Key  KEY_MONITOR_HEIGHT        = new KeyOf(Double.class, "MONITOR_HEIGHT");
103     public static final Key  KEY_NUMBER_FORMAT         = new KeyOf(MetricsFormat.class, "NUMBER_FORMAT");
104     public static final Key  KEY_TOOLTIP_TEXT          = new KeyOf(String.class, "TOOLTIP_TEXT");
105
106     public static final Key  KEY_EXPRESSION             = new KeyOf(String.class, "EXPRESSION");
107     public static final Key  KEY_INPUT_VALIDATOR             = new KeyOf(Object.class, "INPUT_VALIDATOR");
108     public static final Key  KEY_RVI             = new KeyOf(RVI.class, "RVI");
109
110     public static final Key KEY_PREFIX_TEXT = new KeyOf(String.class, "PREFIX_TEXT");
111     public static final Key KEY_SUFFIX_TEXT = new KeyOf(String.class, "SUFFIX_TEXT");
112
113     /**
114      * If this hint is defined, the monitor will force its x-axis to match this
115      * angle. If this hint doesn't exist, the monitor will not force x-axis
116      * orientation.
117      */
118     public static final Key  KEY_DIRECTION             = new KeyOf(Double.class, "MONITOR_DIRECTION");
119
120     public static final Key  KEY_BORDER_WIDTH          = new KeyOf(Double.class, "MONITOR_BORDER");
121
122     public static final Key  KEY_SG_NODE               = new SceneGraphNodeKey(TextNode.class, "MONITOR_SG_NODE");
123     public static final Key  KEY_SG_NODE2              = new SceneGraphNodeKey(ShapeNode.class, "MONITOR_SG_NODE2");
124     public static final Key KEY_SG_PREFIX_NODE         = new SceneGraphNodeKey(ShapeNode.class, "MONITOR_PREFIX_NODE");
125     public static final Key KEY_SG_SUFFIX_NODE         = new SceneGraphNodeKey(ShapeNode.class, "MONITOR_SUFFIX_NODE");
126
127     final static BasicStroke STROKE                    = new BasicStroke(1.0f);
128
129     public final static Alignment DEFAULT_HORIZONTAL_ALIGN  = Alignment.CENTER;
130     public final static Alignment DEFAULT_VERTICAL_ALIGN    = Alignment.CENTER;
131     public final static MetricsFormat DEFAULT_NUMBER_FORMAT = MetricsFormatList.METRICS_DECIMAL;
132
133     public final static Color     DEFAULT_FILL_COLOR        = new Color(224, 224, 224);
134     public final static Color     DEFAULT_BORDER_COLOR      = Color.BLACK;
135
136     public final static double    DEFAULT_HORIZONTAL_MARGIN = 5.0;
137     public final static double    DEFAULT_VERTICAL_MARGIN   = 2.5;
138
139     static Alignment getHorizontalAlignment(IElement e) {
140         return ElementUtils.getHintOrDefault(e, ElementHints.KEY_HORIZONTAL_ALIGN, DEFAULT_HORIZONTAL_ALIGN);
141     }
142
143     static Alignment getVerticalAlignment(IElement e) {
144         return ElementUtils.getHintOrDefault(e, ElementHints.KEY_VERTICAL_ALIGN, DEFAULT_VERTICAL_ALIGN);
145     }
146
147     static MetricsFormat getNumberFormat(IElement e) {
148         return ElementUtils.getHintOrDefault(e, KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);
149     }
150
151     static void setNumberFormat(IElement e, MetricsFormat f) {
152         ElementUtils.setOrRemoveHint(e, KEY_NUMBER_FORMAT, f);
153     }
154
155     static double getHorizontalMargin(IElement e) {
156         return DEFAULT_HORIZONTAL_MARGIN;
157     }
158
159     static double getVerticalMargin(IElement e) {
160         return DEFAULT_VERTICAL_MARGIN;
161     }
162
163     static Font getFont(IElement e) {
164         return ElementUtils.getHintOrDefault(e, ElementHints.KEY_FONT, FONT);
165     }
166
167     public static class MonitorHandlerImpl implements MonitorHandler {
168         private static final long          serialVersionUID = -4258875745321808416L;
169         public static final MonitorHandler INSTANCE         = new MonitorHandlerImpl();
170     }
171
172     static class Initializer implements LifeCycle {
173         private static final long serialVersionUID = 4404942036933073584L;
174
175         IElement parentElement;
176         Map<String, String> substitutions;
177         Object component;
178         String suffix;
179         boolean hack;
180
181         Initializer(IElement parentElement, Map<String, String> substitutions, Object component, String suffix, boolean hack) {
182             this.parentElement = parentElement;
183             this.substitutions = substitutions;
184             this.component = component;
185             this.suffix = suffix;
186             this.hack = hack;
187         }
188
189         @Override
190         public void onElementActivated(IDiagram d, IElement e) {
191             if(!hack) {
192                 hack = true;
193
194                 Point2D parentPos = ElementUtils.getPos(parentElement);
195                 Point2D thisPos = ElementUtils.getPos(e);
196
197                 Move move = e.getElementClass().getSingleItem(Move.class);
198                 move.moveTo(e, thisPos.getX() - parentPos.getX(), thisPos.getY() - parentPos.getY());
199             }
200         }
201         @Override
202         public void onElementCreated(IElement e) {
203             if(parentElement != null) e.setHint(ElementHints.KEY_PARENT_ELEMENT, parentElement);
204             if(substitutions != null) e.setHint(KEY_MONITOR_SUBSTITUTIONS, substitutions);
205             if(component != null) e.setHint(KEY_MONITOR_COMPONENT, component);
206             if(suffix != null) e.setHint(KEY_MONITOR_SUFFIX, suffix);
207             e.setHint(KEY_DIRECTION, 0.0);
208             e.setHint(KEY_NUMBER_FORMAT, DEFAULT_NUMBER_FORMAT);
209             //e.setHint(KEY_HORIZONTAL_ALIGN, Alignment.LEADING);
210             //e.setHint(KEY_VERTICAL_ALIGN, Alignment.LEADING);
211         }
212
213         @Override
214         public void onElementDestroyed(IElement e) {
215         }
216
217         @Override
218         public void onElementDeactivated(IDiagram d, IElement e) {
219         }
220     };
221
222     static String finalText(IElement e) {
223         String text = e.getElementClass().getSingleItem(Text.class).getText(e);
224         if (text == null)
225             return null;
226         return substitute(text, e);
227     }
228
229     public static String editText(IElement e) {
230         return substitute("#v1", e);
231     }
232
233     private static String formValue(IElement e) {
234         // TODO: consider using substitute
235         Map<String, String> substitutions = e.getHint(KEY_MONITOR_SUBSTITUTIONS);
236         if (substitutions != null) {
237             String value = substitutions.get("#v1");
238             if (substitutions.containsKey("#u1") && substitutions.get("#u1").length() > 0) {
239                 value += " " + substitutions.get("#u1");
240             }
241             return value;
242         } else {
243                 return ElementUtils.getText(e);
244         }
245     }
246
247     static String substitute(String text, IElement e) {
248         Map<String, String> substitutions = e.getHint(KEY_MONITOR_SUBSTITUTIONS);
249         return substitute(text, substitutions);
250     }
251
252     static String substitute(String text, Map<String, String> substitutions) {
253         if (substitutions != null) {
254             // TODO: slow as hell
255             for(Map.Entry<String, String> entry : substitutions.entrySet()) {
256                 if (entry.getValue() != null) {
257                     text = text.replace(entry.getKey(), entry.getValue());
258                 } else {
259                     text = text.replace(entry.getKey(), "<null>");
260                 }
261             }
262         }
263         return text;
264     }
265
266     public static void update(IElement e) {
267         MonitorSGNode node = e.getElementClass().getSingleItem(MonitorSGNode.class);
268         node.update(e);
269     }
270
271     public static void cleanup(IElement e) {
272         MonitorSGNode node = e.getElementClass().getSingleItem(MonitorSGNode.class);
273         node.cleanup(e);
274     }
275
276     public static boolean hasModifier(IElement e) { 
277         TextEditor editor = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);
278         Modifier modifier = editor != null ? editor.getModifier(e) : null;
279         return modifier != null;
280     }
281
282     static final Rectangle2D DEFAULT_BOX = new Rectangle2D.Double(0, 0, 0, 0);
283
284     static Shape createMonitor(IElement e) {
285         Alignment hAlign = getHorizontalAlignment(e);
286         Alignment vAlign = getVerticalAlignment(e);
287         double hMargin = getHorizontalMargin(e);
288         double vMargin = getVerticalMargin(e);
289
290         String text = finalText(e);
291         if(text == null) {
292             return align(hMargin, vMargin, hAlign, vAlign, DEFAULT_BOX);
293         }
294
295         Graphics2D g = e.getHint(KEY_MONITOR_GC);
296         if(g == null) {
297             return align(hMargin, vMargin, hAlign, vAlign, DEFAULT_BOX);
298         }
299
300         Font f = getFont(e);
301         FontMetrics fm   = g.getFontMetrics(f);
302         Rectangle2D rect = fm.getStringBounds(text, g);
303
304         return align(hMargin, vMargin, hAlign, vAlign, rect);
305     }
306
307     static Shape align(double hMargin, double vMargin, Alignment hAlign, Alignment vAlign, Rectangle2D rect) {
308         //System.out.println("align: " + hMargin + ", " + vMargin + ", " + hAlign + ", " + vAlign + ": " + rect);
309         double tx = align(hMargin, hAlign, rect.getMinX(), rect.getMaxX());
310         double ty = align(vMargin, vAlign, rect.getMinY(), rect.getMaxY());
311         //System.out.println("    translate: " + tx + " "  + ty);
312         double nw = rect.getWidth() + 2*hMargin;
313         double nh = rect.getHeight() + 2*vMargin;
314         return makePath(tx + rect.getMinX(), ty + rect.getMinY(), nw, nh);
315     }
316
317     static double align(double margin, Alignment align, double min, double max) {
318         double s = max - min;
319         switch (align) {
320             case LEADING:
321                 return -min;
322             case TRAILING:
323                 return -s - 2 * margin - min;
324             case CENTER:
325                 return -0.5 * s - margin - min;
326             default:
327                 return 0;
328         }
329     }
330
331     static Path2D makePath(double x, double y, double w, double h) {
332         Path2D path = new Path2D.Double();
333         path.moveTo(x, y);
334         path.lineTo(x+w, y);
335         path.lineTo(x+w, y+h);
336         path.lineTo(x, y+h);
337         path.closePath();
338         return path;
339     }
340
341     public static final Shape BOX_SHAPE = new Rectangle(-1, -1, 2, 2);
342
343     public static class MonitorSGNode implements SceneGraph, InternalSize, Outline {
344         private static final long serialVersionUID = -106278359626957687L;
345
346         static final MonitorSGNode INSTANCE = new MonitorSGNode();
347
348         @SuppressWarnings("unchecked")
349         @Override
350         public void init(final IElement e, final G2DParentNode parent) {
351             // Create node if it doesn't exist yet
352             TextNode node = e.getHint(KEY_SG_NODE);
353             String nodeId = null;
354             if(node == null || node.getBounds() == null || node.getParent() != parent) {
355                 nodeId = ElementUtils.generateNodeId(e);
356                 node = parent.addNode(nodeId, TextNode.class);
357                 e.setHint(KEY_SG_NODE, node);
358
359                 node.setTextListener(new ITextListener() {
360
361                     @Override
362                     public void textChanged() {
363                         updatePrefixAndSuffixPositions(e);
364                     }
365
366                     @Override
367                     public void textEditingStarted() {
368                         updatePrefixAndSuffixPositions(e);
369                     }
370
371                     @Override
372                     public void textEditingCancelled() {
373                         updatePrefixAndSuffixPositions(e);
374                     }
375
376                     boolean isEndingEdit = false;
377
378                     @Override
379                     public void textEditingEnded() {
380                         updatePrefixAndSuffixPositions(e);
381                         TextNode node = e.getHint(KEY_SG_NODE);
382                         if (node == null)
383                             return;
384
385                         // Prevent recursive execution which will happen
386                         // due to the endEdit(node) invocation at the end.
387                         if (isEndingEdit)
388                             return;
389                         isEndingEdit = true;
390
391                         try {
392                             TextEditor editor = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);
393                             if (editor != null) {
394                                 Modifier modifier = editor.getModifier(e);
395                                 if (modifier != null) {
396                                     String newValue = node.getText();
397                                     String error = modifier.isValid(e, newValue);
398                                     if (error == null) {
399                                         // Only modify if the modification was not
400                                         // cancelled and the value is valid.
401                                         modifier.modify(e, newValue);
402                                     } else {
403                                         // TODO: show error somehow, possibly through status bar
404
405                                         // Make sure that the monitor content gets
406                                         // reset to its previous value.
407                                         node.setText(formValue(e));
408                                     }
409                                 }
410                             }
411                         } finally {
412                             isEndingEdit = false;
413                         }
414                     }
415                 });
416
417                 Object validator = e.getHint(KEY_INPUT_VALIDATOR);
418                 if(validator != null) {
419                     node.setValidator((Function1<String, String>)validator);
420                 }
421                 
422                 RVI rvi = e.getHint(KEY_RVI);
423                 if(rvi != null) {
424                     node.setRVI(rvi);
425                 }
426
427                 Double border_width = (Double)e.getHint(KEY_BORDER_WIDTH);
428                 if(border_width == null) border_width = 0.1;
429
430                 node.setBorderWidth(border_width);
431
432 //                Rectangle2D bounds = (Rectangle2D)e.getHint(ElementHints.KEY_BOUNDS);
433 //                if(bounds != null) node.setBounds(bounds);
434                 Font font = ElementUtils.getTextFont(e);
435                 Color color = ElementUtils.getTextColor(e);
436                 String text = ElementUtils.getText(e);
437                 node.init(text, font, color, 0, 0, 1.0);
438             }
439
440             Boolean isExternal = e.getHint(KEY_MONITOR_IS_EXTERNAL);
441             if (Boolean.TRUE.equals(isExternal)) {
442                 ShapeNode shape = e.getHint(KEY_SG_NODE2);
443                 if (shape == null || shape.getParent() != parent) {
444                     if (nodeId == null)
445                         nodeId = ElementUtils.generateNodeId(e);
446                     nodeId += "-ext";
447                     shape = parent.addNode(nodeId, ShapeNode.class);
448                     shape.setZIndex(-1);
449                     shape.setColor(Color.BLACK);
450                     shape.setFill(true);
451                     shape.setStroke(null);
452                     shape.setShape( arrow(4, 2, 0) );
453                     e.setHint(KEY_SG_NODE2, shape);
454                 }
455             } else {
456                 ShapeNode shape = e.getHint(KEY_SG_NODE2);
457                 if (shape != null)
458                     shape.remove();
459             }
460
461             String prefixText = e.getHint(KEY_PREFIX_TEXT);
462             if (prefixText != null) {
463                 TextNode prefixNode = e.getHint(KEY_SG_PREFIX_NODE);
464                 if (prefixNode == null || prefixNode.getParent() != parent) {
465                     nodeId = ElementUtils.generateNodeId(e) + "-prefix";
466                     node = parent.addNode(nodeId, TextNode.class);
467                     e.setHint(KEY_SG_PREFIX_NODE, node);
468                     Font font = ElementUtils.getTextFont(e);
469                     Color color = ElementUtils.getTextColor(e);
470                     node.setEditable(false);
471                     node.setShowSelection(false);
472                     node.setHorizontalAlignment((byte) 1);
473                     node.init(prefixText, font, color, 0, 0, 1.0);
474                 }
475             } else {
476                 TextNode prefixNode = e.getHint(KEY_SG_PREFIX_NODE);
477                 if (prefixNode != null)
478                     prefixNode.remove();
479             }
480
481             String suffixText = e.getHint(KEY_SUFFIX_TEXT);
482             if (suffixText != null) {
483                 TextNode suffixNode = e.getHint(KEY_SG_SUFFIX_NODE);
484                 if (suffixNode == null || suffixNode.getParent() != parent) {
485                     nodeId = ElementUtils.generateNodeId(e) + "-suffix";
486                     node = parent.addNode(nodeId, TextNode.class);
487                     e.setHint(KEY_SG_SUFFIX_NODE, node);
488                     Font font = ElementUtils.getTextFont(e);
489                     Color color = ElementUtils.getTextColor(e);
490                     node.setEditable(false);
491                     node.setShowSelection(false);
492                     node.setHorizontalAlignment((byte) 0);
493                     node.init(suffixText, font, color, 0, 0, 1.0);
494                 }
495             } else {
496                 TextNode suffixNode = e.getHint(KEY_SG_SUFFIX_NODE);
497                 if (suffixNode != null)
498                     suffixNode.remove();
499             }
500
501             update(e);
502         }
503
504         public void update(IElement e) {
505             String value = null;
506
507             final Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);
508             assert(t != null);
509
510             value = formValue(e);
511
512             TextNode node = (TextNode) e.getHint(KEY_SG_NODE);
513             if (node != null && value != null) {
514
515                 node.setText(value);
516                 Object component = e.getHint(KEY_MONITOR_COMPONENT);
517                 if (component != null && hasModifier(e)) {
518                     node.setEditable(true);
519                 } else {
520                     node.setEditable(false);
521                 }
522
523                 // FIXME: set only if changed .. (but quickfix is not to clone)
524                 Font font = ElementUtils.getTextFont(e);
525                 if (node.getFont() != font) { // Don't update if we have a same object
526                     node.setFont(font);
527                 }
528                 Color color = ElementUtils.getTextColor(e);
529                 node.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
530
531                 AffineTransform at = ElementUtils.getTransform(e);
532                 if(at != null)
533                     node.setTransform(at);
534
535                 Alignment halign = e.getHint(ElementHints.KEY_HORIZONTAL_ALIGN);
536                 if (halign != null)
537                     node.setHorizontalAlignment((byte) halign.ordinal());
538                 Alignment valign = e.getHint(ElementHints.KEY_VERTICAL_ALIGN);
539                 if (valign != null)
540                     node.setVerticalAlignment((byte) valign.ordinal());
541
542 //                String tt = (String) e.getHint(KEY_TOOLTIP_TEXT);
543 //                if (tt != null)
544 //                    node.setToolTipText(new String(tt));
545
546                 ShapeNode shape = e.getHint(KEY_SG_NODE2);
547                 if (shape != null) {
548                     AffineTransform at2 = new AffineTransform(at);
549                     Rectangle2D bounds = node.getBoundsInLocal();
550                     at2.translate(bounds.getMinX(), bounds.getCenterY());
551                     shape.setTransform(at2);
552                 }
553
554                 TextNode prefixNode = e.getHint(KEY_SG_PREFIX_NODE);
555                 if (prefixNode != null) {
556                     if (prefixNode.getFont() != font) {
557                         prefixNode.setFont(font);
558                     }
559                     prefixNode.setColor(color);
560                 }
561                 TextNode suffixNode = e.getHint(KEY_SG_SUFFIX_NODE);
562                 if (suffixNode != null) {
563                     if (suffixNode.getFont() != font) {
564                         suffixNode.setFont(font);
565                     }
566                     suffixNode.setColor(color);
567                 }
568
569                 updatePrefixAndSuffixPositions(e);
570             }
571         }
572
573         public void updatePrefixAndSuffixPositions(IElement e) {
574             TextNode node = e.getHint(KEY_SG_NODE);
575             if (node != null) {
576                 TextNode prefix = e.getHint(KEY_SG_PREFIX_NODE);
577                 TextNode suffix = e.getHint(KEY_SG_SUFFIX_NODE);
578                 if (prefix != null || suffix != null) {
579                     AffineTransform at = ElementUtils.getTransform(e);
580                     Alignment vAlign = getVerticalAlignment(e);
581                     if (prefix != null) {
582                         AffineTransform at2 = new AffineTransform(at);
583                         Rectangle2D bounds = node.getBoundsInLocal();
584                         at2.translate(bounds.getMinX(), 0);
585                         prefix.setTransform(at2);
586                         prefix.setVerticalAlignment((byte) vAlign.ordinal());
587                     }
588
589                     if (suffix != null) {
590                         AffineTransform at2 = new AffineTransform(at);
591                         Rectangle2D bounds = node.getBoundsInLocal();
592                         at2.translate(bounds.getMaxX(), 0);
593                         suffix.setTransform(at2);
594                         suffix.setVerticalAlignment((byte) vAlign.ordinal());
595                     }
596                 }
597             }
598         }
599
600         @Override
601         public void cleanup(IElement e) {
602             TextNode node = (TextNode)e.removeHint(KEY_SG_NODE);
603             if (node != null)
604                 node.remove();
605         }
606
607         @Override
608         public Rectangle2D getBounds(IElement e, Rectangle2D size) {
609             TextNode node = (TextNode)e.getHint(KEY_SG_NODE);
610             if (node != null) {
611                 Rectangle2D bounds = node.getBoundsInLocal();
612                 if (bounds != null) {
613                     if (size == null)
614                         size = new Rectangle2D.Double(0, 0, 0, 0);
615                     size.setRect(bounds);
616                 }
617             }
618             TextNode prefixNode = (TextNode) e.getHint(KEY_SG_PREFIX_NODE);
619             if (prefixNode != null) {
620                 Rectangle2D bounds = node.parentToLocal(prefixNode.localToParent(prefixNode.getBoundsInLocal()));
621                 if (size == null)
622                     size = new Rectangle2D.Double(0, 0, 0, 0);
623                 size.add(bounds);
624             }
625             TextNode suffixNode = (TextNode) e.getHint(KEY_SG_SUFFIX_NODE);
626             if (suffixNode != null) {
627                 Rectangle2D bounds = node.parentToLocal(suffixNode.localToParent(suffixNode.getBoundsInLocal()));
628                 if (size == null)
629                     size = new Rectangle2D.Double(0, 0, 0, 0);
630                 size.add(bounds);
631             }
632             return size;
633         }
634
635         @Override
636         public Shape getElementShape(IElement e) {
637             Rectangle2D bounds = getBounds(e, null);
638             if (bounds != null) {
639                 return bounds;
640             } else {
641                 return new Rectangle2D.Double(0, 0, 0, 0);
642             }
643         }
644
645     }
646
647     public static class Transformer implements Transform, Move, Rotate, Scale, LifeCycle {
648
649         private static final long serialVersionUID = -3704887325602085677L;
650
651         public static final Transformer INSTANCE = new Transformer(null);
652
653         Double aspectRatio;
654
655         public Transformer() {
656             this(null);
657         }
658
659         public Transformer(Double aspectRatio) {
660             this.aspectRatio = aspectRatio;
661         }
662
663         @Override
664         public Double getFixedAspectRatio(IElement e) {
665             return aspectRatio;
666         }
667
668         @Override
669         public Point2D getScale(IElement e) {
670             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
671             return _getScale(at);
672         }
673
674         @Override
675         public void setScale(IElement e, Point2D newScale) {
676             // Doesn't work for monitors.
677             Point2D oldScale = getScale(e);
678             double sx = newScale.getX() / oldScale.getX();
679             double sy = newScale.getY() / oldScale.getY();
680             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
681             at = new AffineTransform(at);
682             at.scale(sx, sy);
683             e.setHint(ElementHints.KEY_TRANSFORM, at);
684         }
685
686         @Override
687         public Point2D getMaximumScale(IElement e) {
688             return null;
689         }
690
691         @Override
692         public Point2D getMinimumScale(IElement e) {
693             return null;
694         }
695
696         private static Point2D _getScale(AffineTransform at) {
697             double m00 = at.getScaleX();
698             double m11 = at.getScaleY();
699             double m10 = at.getShearY();
700             double m01 = at.getShearX();
701             // Project unit vector to canvas
702             double sx = Math.sqrt(m00 * m00 + m10 * m10);
703             double sy = Math.sqrt(m01 * m01 + m11 * m11);
704             return new Point2D.Double(sx, sy);
705         }
706
707         @Override
708         public void rotate(IElement e, double theta, Point2D origin) {
709             if (Double.isNaN(theta)) return;
710             theta = Math.toDegrees(theta);
711             Double angle = e.getHint(KEY_DIRECTION);
712             double newAngle = angle != null ? angle+theta : theta;
713             newAngle = Math.IEEEremainder(newAngle, 360.0);
714             e.setHint(KEY_DIRECTION, newAngle);
715         }
716
717         @Override
718         public double getAngle(IElement e) {
719             Double angle = e.getHint(KEY_DIRECTION);
720             return angle != null ? Math.toRadians(angle) : 0;
721         }
722         @Override
723         public Point2D getPosition(IElement e) {
724             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
725             Point2D p = new Point2D.Double(at.getTranslateX(), at.getTranslateY());
726             return p;
727         }
728
729         @Override
730         public void moveTo(IElement e, double x, double y) {
731             AffineTransform origAt = e.getHint(ElementHints.KEY_TRANSFORM);
732
733             AffineTransform result = new AffineTransform(origAt);
734             result.preConcatenate(AffineTransform.getTranslateInstance(x - origAt.getTranslateX(), y - origAt.getTranslateY()));
735             e.setHint(ElementHints.KEY_TRANSFORM, result);
736         }
737
738         @Override
739         public AffineTransform getTransform(IElement e) {
740             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
741
742             IElement parentElement = e.getHint(ElementHints.KEY_PARENT_ELEMENT);
743             if (parentElement == null)
744                 return at;
745
746             Transform parentTransform = parentElement.getElementClass().getSingleItem(Transform.class);
747             assert(parentTransform!=null);
748
749             AffineTransform result = (AffineTransform)at.clone();
750             AffineTransform parentAT = parentTransform.getTransform(parentElement);
751             result.preConcatenate(AffineTransform.getTranslateInstance(parentAT.getTranslateX(), parentAT.getTranslateY()));
752
753             return result;
754         }
755
756         @Override
757         public void setTransform(IElement e, AffineTransform at) {
758             e.setHint(ElementHints.KEY_TRANSFORM, at.clone());
759         }
760
761         @Override
762         public void onElementActivated(IDiagram d, IElement e) {
763         }
764
765         @Override
766         public void onElementCreated(IElement e) {
767             e.setHint(ElementHints.KEY_TRANSFORM, new AffineTransform());
768         }
769
770         @Override
771         public void onElementDeactivated(IDiagram d, IElement e) {
772         }
773
774         @Override
775         public void onElementDestroyed(IElement e) {
776 //            List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
777 //            for(SceneGraph n : nodeHandlers) {
778 //                System.out.println("element gone:"+e);
779 //                n.cleanup(e);
780 //            }
781         }
782     }
783
784     static double getOrientationDelta(IElement e, AffineTransform tr) {
785         Double angle = e.getHint(KEY_DIRECTION);
786         if (angle == null || Double.isNaN(angle))
787             return Double.NaN;
788         double angrad = Math.toRadians(angle);
789
790         Vector2D forcedAxis = new Vector2D(Math.cos(angrad), Math.sin(angrad));
791         Vector2D x = new Vector2D(tr.getScaleX(), tr.getShearX()).normalize();
792         double cosa = forcedAxis.dotProduct(x);
793         double delta = Math.acos(cosa);
794         return delta;
795     }
796
797     static class MonitorImageFactory implements IFactory<Image> {
798         private double staticScaleX = 1, staticScaleY = 1;
799
800         public MonitorImageFactory(double staticScaleX, double staticScaleY) {
801             this.staticScaleX = staticScaleX;
802             this.staticScaleY = staticScaleY;
803         }
804
805         @Override
806         public Image get() throws ProvisionException {
807             return new Img();
808         }
809
810         public class Img extends AbstractImage {
811
812             Shape path = align(DEFAULT_HORIZONTAL_MARGIN, DEFAULT_VERTICAL_MARGIN, DEFAULT_HORIZONTAL_ALIGN, DEFAULT_VERTICAL_ALIGN,
813                     new Rectangle2D.Double(0, 0, 50*staticScaleX, 22*staticScaleY));
814
815             @Override
816             public int hashCode() {
817                 long temp = Double.doubleToLongBits(staticScaleX);
818                 int result = (int) (temp ^ (temp >>> 32));
819                 temp = Double.doubleToLongBits(staticScaleY);
820                 result = 31 * result + (int) (temp ^ (temp >>> 32));
821                 return result;
822             }
823
824             @Override
825             public boolean equals(Object obj) {
826                 if (this == obj)
827                     return true;
828                 if (obj == null)
829                     return false;
830                 if (getClass() != obj.getClass())
831                     return false;
832                 MonitorImageFactory other = (MonitorImageFactory) obj;
833                 return Double.doubleToLongBits(staticScaleX) == Double.doubleToLongBits(other.staticScaleX)
834                         && Double.doubleToLongBits(staticScaleY) != Double.doubleToLongBits(other.staticScaleY);
835             }
836
837             @Override
838             public Rectangle2D getBounds() {
839                 return path.getBounds2D();
840             }
841
842             @Override
843             public EnumSet<Feature> getFeatures() {
844                 return EnumSet.of(Feature.Vector);
845             }
846
847             @Override
848             public Shape getOutline() {
849                 return path;
850             }
851
852             @Override
853             public Node init(G2DParentNode parent) {
854                 TextNode node = parent.getOrCreateNode(""+hashCode(), TextNode.class);
855                 node.init("Drop Me", FONT, Color.BLACK, 0, 0, 0.2);
856                 //node.setSize(50, 22);
857                 node.setBorderWidth(1);
858                 node.setTransform(AffineTransform.getScaleInstance(staticScaleX, staticScaleY));
859                 node.setEditable(false);
860                 return node;
861             }
862         };
863     }
864
865     static final IProvider<Image> MONITOR_IMAGE =
866         ProviderUtils.reference(
867                 ProviderUtils.cache(
868                         ProviderUtils.rasterize(
869                                 new MonitorImageFactory(0.5, 0.5)
870                         )));
871
872     static final StaticSymbol MONITOR_SYMBOL = new StaticSymbolImpl( MONITOR_IMAGE.get() );
873
874     static final FillColor FILL_COLOR = new FillColorImpl(DEFAULT_FILL_COLOR);
875
876     static final ElementClass MONITOR_CLASS_BASE =
877         ElementClass.compile(
878                 MonitorHandlerImpl.INSTANCE,
879                 Transformer.INSTANCE,
880                 BorderColorImpl.BLACK,
881                 FILL_COLOR,
882                 MonitorSGNode.INSTANCE,
883 //                ClickableImpl.INSTANCE,
884                 TextImpl.INSTANCE,
885                 TextEditorImpl.INSTANCE,
886                 TextFontImpl.DEFAULT,
887                 TextColorImpl.BLACK,
888                 SimpleElementLayers.INSTANCE,
889                 ParentImpl.INSTANCE
890         );
891
892     // staticScale{X,Y} define the scale of the static monitor image
893     public static ElementClass create(double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) {
894         // Bit of a hack to be able to define the scale
895         IProvider<Image> staticMonitorSymbolProvider = ProviderUtils.reference(
896                 ProviderUtils.cache(
897                         ProviderUtils
898                         .rasterize(
899                                 new MonitorImageFactory(staticScaleX, staticScaleY))));
900         StaticSymbol staticMonitorSymbol = new StaticSymbolImpl( staticMonitorSymbolProvider.get() );
901         return MONITOR_CLASS_BASE.newClassWith(staticMonitorSymbol).newClassWith(extraHandlers);
902     }
903
904     // staticScale{X,Y} define the scale of the static monitor image
905     public static ElementClass create(IElement parentElement, Map<String, String> substitutions, Object component, String suffix, double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) {
906         return create(staticScaleX, staticScaleY, extraHandlers)
907                 .newClassWith(
908                         new Initializer(parentElement, substitutions, component,
909                                 suffix, parentElement != null ? false : true));
910     }
911
912     public static Shape arrow(double length, double width, double space) {
913         Path2D.Double path = new Path2D.Double();
914         path.moveTo(-space, 0);
915         path.lineTo(-length - space, -width);
916         path.lineTo(-length - space, +width);
917         path.closePath();
918         return path;
919     }
920
921 }