]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/MonitorClass.java
fd1498d0cac2d33e2fc78ee89c34634bb1f72acb
[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                 updatePrefixAndSuffixPositions(e);
555             }
556         }
557
558         public void updatePrefixAndSuffixPositions(IElement e) {
559             TextNode node = e.getHint(KEY_SG_NODE);
560             if (node != null) {
561                 TextNode prefix = e.getHint(KEY_SG_PREFIX_NODE);
562                 TextNode suffix = e.getHint(KEY_SG_SUFFIX_NODE);
563                 if (prefix != null || suffix != null) {
564                     AffineTransform at = ElementUtils.getTransform(e);
565                     Alignment vAlign = getVerticalAlignment(e);
566                     if (prefix != null) {
567                         AffineTransform at2 = new AffineTransform(at);
568                         Rectangle2D bounds = node.getBoundsInLocal();
569                         at2.translate(bounds.getMinX(), 0);
570                         prefix.setTransform(at2);
571                         prefix.setVerticalAlignment((byte) vAlign.ordinal());
572                     }
573
574                     if (suffix != null) {
575                         AffineTransform at2 = new AffineTransform(at);
576                         Rectangle2D bounds = node.getBoundsInLocal();
577                         at2.translate(bounds.getMaxX(), 0);
578                         suffix.setTransform(at2);
579                         suffix.setVerticalAlignment((byte) vAlign.ordinal());
580                     }
581                 }
582             }
583         }
584
585         @Override
586         public void cleanup(IElement e) {
587             TextNode node = (TextNode)e.removeHint(KEY_SG_NODE);
588             if (node != null)
589                 node.remove();
590         }
591
592         @Override
593         public Rectangle2D getBounds(IElement e, Rectangle2D size) {
594             TextNode node = (TextNode)e.getHint(KEY_SG_NODE);
595             if (node != null) {
596                 Rectangle2D bounds = node.getBoundsInLocal();
597                 if (bounds != null) {
598                     if (size == null)
599                         size = new Rectangle2D.Double(0, 0, 0, 0);
600                     size.setRect(bounds);
601                 }
602             }
603             TextNode prefixNode = (TextNode) e.getHint(KEY_SG_PREFIX_NODE);
604             if (prefixNode != null) {
605                 Rectangle2D bounds = node.parentToLocal(prefixNode.localToParent(prefixNode.getBoundsInLocal()));
606                 if (size == null)
607                     size = new Rectangle2D.Double(0, 0, 0, 0);
608                 size.add(bounds);
609             }
610             TextNode suffixNode = (TextNode) e.getHint(KEY_SG_SUFFIX_NODE);
611             if (suffixNode != null) {
612                 Rectangle2D bounds = node.parentToLocal(suffixNode.localToParent(suffixNode.getBoundsInLocal()));
613                 if (size == null)
614                     size = new Rectangle2D.Double(0, 0, 0, 0);
615                 size.add(bounds);
616             }
617             return size;
618         }
619
620         @Override
621         public Shape getElementShape(IElement e) {
622             Rectangle2D bounds = getBounds(e, null);
623             if (bounds != null) {
624                 return bounds;
625             } else {
626                 return new Rectangle2D.Double(0, 0, 0, 0);
627             }
628         }
629
630     }
631
632     public static class Transformer implements Transform, Move, Rotate, Scale, LifeCycle {
633
634         private static final long serialVersionUID = -3704887325602085677L;
635
636         public static final Transformer INSTANCE = new Transformer(null);
637
638         Double aspectRatio;
639
640         public Transformer() {
641             this(null);
642         }
643
644         public Transformer(Double aspectRatio) {
645             this.aspectRatio = aspectRatio;
646         }
647
648         @Override
649         public Double getFixedAspectRatio(IElement e) {
650             return aspectRatio;
651         }
652
653         @Override
654         public Point2D getScale(IElement e) {
655             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
656             return _getScale(at);
657         }
658
659         @Override
660         public void setScale(IElement e, Point2D newScale) {
661             // Doesn't work for monitors.
662             Point2D oldScale = getScale(e);
663             double sx = newScale.getX() / oldScale.getX();
664             double sy = newScale.getY() / oldScale.getY();
665             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
666             at = new AffineTransform(at);
667             at.scale(sx, sy);
668             e.setHint(ElementHints.KEY_TRANSFORM, at);
669         }
670
671         @Override
672         public Point2D getMaximumScale(IElement e) {
673             return null;
674         }
675
676         @Override
677         public Point2D getMinimumScale(IElement e) {
678             return null;
679         }
680
681         private static Point2D _getScale(AffineTransform at) {
682             double m00 = at.getScaleX();
683             double m11 = at.getScaleY();
684             double m10 = at.getShearY();
685             double m01 = at.getShearX();
686             // Project unit vector to canvas
687             double sx = Math.sqrt(m00 * m00 + m10 * m10);
688             double sy = Math.sqrt(m01 * m01 + m11 * m11);
689             return new Point2D.Double(sx, sy);
690         }
691
692         @Override
693         public void rotate(IElement e, double theta, Point2D origin) {
694             if (Double.isNaN(theta)) return;
695             theta = Math.toDegrees(theta);
696             Double angle = e.getHint(KEY_DIRECTION);
697             double newAngle = angle != null ? angle+theta : theta;
698             newAngle = Math.IEEEremainder(newAngle, 360.0);
699             e.setHint(KEY_DIRECTION, newAngle);
700         }
701
702         @Override
703         public double getAngle(IElement e) {
704             Double angle = e.getHint(KEY_DIRECTION);
705             return angle != null ? Math.toRadians(angle) : 0;
706         }
707         @Override
708         public Point2D getPosition(IElement e) {
709             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
710             Point2D p = new Point2D.Double(at.getTranslateX(), at.getTranslateY());
711             return p;
712         }
713
714         @Override
715         public void moveTo(IElement e, double x, double y) {
716             AffineTransform origAt = e.getHint(ElementHints.KEY_TRANSFORM);
717
718             AffineTransform result = new AffineTransform(origAt);
719             result.preConcatenate(AffineTransform.getTranslateInstance(x - origAt.getTranslateX(), y - origAt.getTranslateY()));
720             e.setHint(ElementHints.KEY_TRANSFORM, result);
721         }
722
723         @Override
724         public AffineTransform getTransform(IElement e) {
725             AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
726
727             IElement parentElement = e.getHint(ElementHints.KEY_PARENT_ELEMENT);
728             if (parentElement == null)
729                 return at;
730
731             Transform parentTransform = parentElement.getElementClass().getSingleItem(Transform.class);
732             assert(parentTransform!=null);
733
734             AffineTransform result = (AffineTransform)at.clone();
735             AffineTransform parentAT = parentTransform.getTransform(parentElement);
736             result.preConcatenate(AffineTransform.getTranslateInstance(parentAT.getTranslateX(), parentAT.getTranslateY()));
737
738             return result;
739         }
740
741         @Override
742         public void setTransform(IElement e, AffineTransform at) {
743             e.setHint(ElementHints.KEY_TRANSFORM, at.clone());
744         }
745
746         @Override
747         public void onElementActivated(IDiagram d, IElement e) {
748         }
749
750         @Override
751         public void onElementCreated(IElement e) {
752             e.setHint(ElementHints.KEY_TRANSFORM, new AffineTransform());
753         }
754
755         @Override
756         public void onElementDeactivated(IDiagram d, IElement e) {
757         }
758
759         @Override
760         public void onElementDestroyed(IElement e) {
761 //            List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
762 //            for(SceneGraph n : nodeHandlers) {
763 //                System.out.println("element gone:"+e);
764 //                n.cleanup(e);
765 //            }
766         }
767     }
768
769     static double getOrientationDelta(IElement e, AffineTransform tr) {
770         Double angle = e.getHint(KEY_DIRECTION);
771         if (angle == null || Double.isNaN(angle))
772             return Double.NaN;
773         double angrad = Math.toRadians(angle);
774
775         Vector2D forcedAxis = new Vector2D(Math.cos(angrad), Math.sin(angrad));
776         Vector2D x = new Vector2D(tr.getScaleX(), tr.getShearX()).normalize();
777         double cosa = forcedAxis.dotProduct(x);
778         double delta = Math.acos(cosa);
779         return delta;
780     }
781
782     static class MonitorImageFactory implements IFactory<Image> {
783         private double staticScaleX = 1, staticScaleY = 1;
784
785         public MonitorImageFactory(double staticScaleX, double staticScaleY) {
786             this.staticScaleX = staticScaleX;
787             this.staticScaleY = staticScaleY;
788         }
789
790         @Override
791         public Image get() throws ProvisionException {
792             return new Img();
793         }
794
795         public class Img extends AbstractImage {
796
797             Shape path = align(DEFAULT_HORIZONTAL_MARGIN, DEFAULT_VERTICAL_MARGIN, DEFAULT_HORIZONTAL_ALIGN, DEFAULT_VERTICAL_ALIGN,
798                     new Rectangle2D.Double(0, 0, 50*staticScaleX, 22*staticScaleY));
799
800             @Override
801             public int hashCode() {
802                 long temp = Double.doubleToLongBits(staticScaleX);
803                 int result = (int) (temp ^ (temp >>> 32));
804                 temp = Double.doubleToLongBits(staticScaleY);
805                 result = 31 * result + (int) (temp ^ (temp >>> 32));
806                 return result;
807             }
808
809             @Override
810             public boolean equals(Object obj) {
811                 if (this == obj)
812                     return true;
813                 if (obj == null)
814                     return false;
815                 if (getClass() != obj.getClass())
816                     return false;
817                 MonitorImageFactory other = (MonitorImageFactory) obj;
818                 return Double.doubleToLongBits(staticScaleX) == Double.doubleToLongBits(other.staticScaleX)
819                         && Double.doubleToLongBits(staticScaleY) != Double.doubleToLongBits(other.staticScaleY);
820             }
821
822             @Override
823             public Rectangle2D getBounds() {
824                 return path.getBounds2D();
825             }
826
827             @Override
828             public EnumSet<Feature> getFeatures() {
829                 return EnumSet.of(Feature.Vector);
830             }
831
832             @Override
833             public Shape getOutline() {
834                 return path;
835             }
836
837             @Override
838             public Node init(G2DParentNode parent) {
839                 TextNode node = parent.getOrCreateNode(""+hashCode(), TextNode.class);
840                 node.init("Drop Me", FONT, Color.BLACK, 0, 0, 0.2);
841                 //node.setSize(50, 22);
842                 node.setBorderWidth(1);
843                 node.setTransform(AffineTransform.getScaleInstance(staticScaleX, staticScaleY));
844                 node.setEditable(false);
845                 return node;
846             }
847         };
848     }
849
850     static final IProvider<Image> MONITOR_IMAGE =
851         ProviderUtils.reference(
852                 ProviderUtils.cache(
853                         ProviderUtils.rasterize(
854                                 new MonitorImageFactory(0.5, 0.5)
855                         )));
856
857     static final StaticSymbol MONITOR_SYMBOL = new StaticSymbolImpl( MONITOR_IMAGE.get() );
858
859     static final FillColor FILL_COLOR = new FillColorImpl(DEFAULT_FILL_COLOR);
860
861     static final ElementClass MONITOR_CLASS_BASE =
862         ElementClass.compile(
863                 MonitorHandlerImpl.INSTANCE,
864                 Transformer.INSTANCE,
865                 BorderColorImpl.BLACK,
866                 FILL_COLOR,
867                 MonitorSGNode.INSTANCE,
868 //                ClickableImpl.INSTANCE,
869                 TextImpl.INSTANCE,
870                 TextEditorImpl.INSTANCE,
871                 TextFontImpl.DEFAULT,
872                 TextColorImpl.BLACK,
873                 SimpleElementLayers.INSTANCE,
874                 ParentImpl.INSTANCE
875         );
876
877     // staticScale{X,Y} define the scale of the static monitor image
878     public static ElementClass create(double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) {
879         // Bit of a hack to be able to define the scale
880         IProvider<Image> staticMonitorSymbolProvider = ProviderUtils.reference(
881                 ProviderUtils.cache(
882                         ProviderUtils
883                         .rasterize(
884                                 new MonitorImageFactory(staticScaleX, staticScaleY))));
885         StaticSymbol staticMonitorSymbol = new StaticSymbolImpl( staticMonitorSymbolProvider.get() );
886         return MONITOR_CLASS_BASE.newClassWith(staticMonitorSymbol).newClassWith(extraHandlers);
887     }
888
889     // staticScale{X,Y} define the scale of the static monitor image
890     public static ElementClass create(IElement parentElement, Map<String, String> substitutions, Object component, String suffix, double staticScaleX, double staticScaleY, ElementHandler... extraHandlers) {
891         return create(staticScaleX, staticScaleY, extraHandlers)
892                 .newClassWith(
893                         new Initializer(parentElement, substitutions, component,
894                                 suffix, parentElement != null ? false : true));
895     }
896
897     public static Shape arrow(double length, double width, double space) {
898         Path2D.Double path = new Path2D.Double();
899         path.moveTo(-space, 0);
900         path.lineTo(-length - space, -width);
901         path.lineTo(-length - space, +width);
902         path.closePath();
903         return path;
904     }
905
906 }