1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.realizer;
15  
16  import demo.view.DemoBase;
17  import demo.view.application.DragAndDropDemo;
18  
19  import y.geom.YDimension;
20  import y.view.AbstractCustomHotSpotPainter;
21  import y.view.AbstractCustomNodePainter;
22  import y.view.BevelNodePainter;
23  import y.view.EditMode;
24  import y.view.GeneralPathNodePainter;
25  import y.view.GenericNodeRealizer;
26  import y.view.ImageNodePainter;
27  import y.view.NodeLabel;
28  import y.view.NodeRealizer;
29  import y.view.ShadowNodePainter;
30  import y.view.ShapeNodePainter;
31  import y.view.ShinyPlateNodePainter;
32  import y.view.SimpleUserDataHandler;
33  import y.view.SmartNodeLabelModel;
34  import y.view.YRenderingHints;
35  
36  import java.awt.BasicStroke;
37  import java.awt.BorderLayout;
38  import java.awt.Color;
39  import java.awt.Component;
40  import java.awt.EventQueue;
41  import java.awt.GradientPaint;
42  import java.awt.Graphics;
43  import java.awt.Graphics2D;
44  import java.awt.Paint;
45  import java.awt.Shape;
46  import java.awt.Stroke;
47  import java.awt.geom.Area;
48  import java.awt.geom.Ellipse2D;
49  import java.awt.geom.GeneralPath;
50  import java.awt.geom.Rectangle2D;
51  import java.awt.geom.RectangularShape;
52  import java.awt.geom.RoundRectangle2D;
53  import java.net.URL;
54  import java.util.ArrayList;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Locale;
58  import java.util.Map;
59  
60  import javax.swing.Icon;
61  import javax.swing.ImageIcon;
62  import javax.swing.JList;
63  import javax.swing.JScrollPane;
64  
65  /**
66   * This class demonstrates various usages of the {@link y.view.GenericNodeRealizer} class.
67   *
68   * It shows how to create different configurations and also shows the usage of
69   * some custom {@link y.view.GenericNodeRealizer.Painter} and
70   * {@link y.view.GenericNodeRealizer.ContainsTest} implementations.
71   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/realizers.html#cls_GenericNodeRealizer">Section Bringing Graph Elements to Life: The Realizer Concept</a> in the yFiles for Java Developer's Guide
72   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/custom_realizers.html#customization_gnr">Section Writing Customized Realizers</a> in the yFiles for Java Developer's Guide
73   */
74  public class GenericNodeRealizerDemo extends DemoBase {
75  
76    /** Creates the GenericNodeRealizer demo. */
77    public GenericNodeRealizerDemo() {
78      super();
79  
80      //create several NodeRealizer Configurations
81      List configurations = createConfigurations();
82  
83      //create the drag and drop list filled with the available realizer configurations
84      JList realizerList = createDnDList(configurations);
85      realizerList.setBackground(Color.WHITE);
86  
87      //add the realizer list to the panel
88      contentPane.add(new JScrollPane(realizerList), BorderLayout.WEST);
89  
90      realizerList.setSelectedIndex(0);
91      GenericNodeRealizer gnr = (GenericNodeRealizer) realizerList.getSelectedValue();
92  
93      view.getGraph2D().setDefaultNodeRealizer(gnr.createCopy());
94  
95      //load an initial graph
96      loadGraph("resource/genericNodeRealizer.graphml");
97    }
98    
99    /**
100    * Creates a JList that contains GenericNodeRealizers that are configured with configuration names from the given
101    * List.
102    */
103   private JList createDnDList(List configurations) {
104     // create the list of NodeRealizer instances
105 
106     final List realizers = createRealizers(configurations);
107     // create the customized DnD support instance
108     return new DragAndDropDemo.DragAndDropSupport(realizers, view).getList();
109   }
110 
111   /**
112    * Creates GenericNodeRealizer configurations and registers them on the factory.
113    *
114    * @return the names of the registered configurations.
115    */
116   private List createConfigurations() {
117     List configNames = new ArrayList();
118 
119     // Get the factory to register custom styles/configurations.
120     GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
121 
122     // Add the simple rectangle configuration to the factory.
123     String configName = "Simple Rectangle";
124     factory.addConfiguration(configName, createSimpleRectangleConfiguration(factory));
125     configNames.add(configName);
126 
127     // Add the diamond configuration to the factory.
128     configName = "Diamond";
129     factory.addConfiguration(configName, createDiamondConfiguration(factory));
130     configNames.add(configName);
131 
132     // Add the elliptical configuration to the factory.
133     configName = "Ellipse";
134     factory.addConfiguration(configName, createEllipseConfiguration(factory));
135     configNames.add(configName);
136 
137     configName = "Circle";
138     factory.addConfiguration(configName, createCircleConfiguration(factory));
139     configNames.add(configName);
140 
141     // Add the bevel style configuration to the factory.
142     configName = "Bevel";
143     factory.addConfiguration(configName, createBevelNodeConfiguration(factory));
144     configNames.add(configName);
145 
146     // Add the shiny plate configuration to the factory.
147     configName = "Shiny Plate";
148     factory.addConfiguration(configName, createShinyPlateNodeConfiguration(factory));
149     configNames.add(configName);
150 
151     // Add the rounded rectangle configuration to the factory.
152     Map roundRectConfiguration = createRoundRectConfiguration(factory);
153     configName = "Round Rectangle";
154     factory.addConfiguration(configName, roundRectConfiguration);
155     configNames.add(configName);
156 
157     configName = "Note";
158     factory.addConfiguration(configName, createNoteNodeConfiguration(factory));
159     configNames.add(configName);
160 
161     //Add the butterfly configuration to the factory by reusing the round rect configuration and only overriding the painter
162     configName = "Butterfly";
163     factory.addConfiguration(configName, createButterflyConfiguration(roundRectConfiguration));
164     configNames.add(configName);
165 
166     // Add the flat button style configuration to the factory.
167     configName = "Flat Button";
168     factory.addConfiguration(configName, createFlatButtonConfiguration(factory));
169     configNames.add(configName);
170 
171     // Add the floating style configuration to the factory.
172     configName = "Floating";
173     factory.addConfiguration(configName, createFloatingConfiguration(factory));
174     configNames.add(configName);
175 
176     // Add the image style configuration to the factory.
177     configName = "Raster Graphics";
178     factory.addConfiguration(configName, createRasterGraphicsConfiguration(factory));
179     configNames.add(configName);
180 
181     /*
182     Note: Since SVGPainter is not part of the yFiles distribution, but part of the free yFiles extension ySVG this code
183     is commented out by default. For this code to work, ySVG must be included in the classpath.
184     For more information on ySVG have a look at: http://www.yworks.com/ysvg
185     */
186     // Add the vector graphics style configuration to the factory.
187     configName = "Vector Graphics";
188     Map vectorGraphicsConfig = createVectorGraphicsConfiguration(factory);
189     //todo: uncomment this to use svg images. Note: the ySVG package is needed for this to work
190 //    factory.addConfiguration(configName, vectorGraphicsConfig);
191 //    configNames.add(configName);
192 
193     // Add the a decorated rect style configuration to the factory.
194     configName = "Decorated Rect";
195     factory.addConfiguration(configName, createDecoratedRectPainterConfiguration(factory));
196     configNames.add(configName);
197 
198     return configNames;
199   }
200 
201   private List createRealizers(List configurations) {
202     List realizers = new ArrayList(configurations.size());
203     for (Iterator iterator = configurations.iterator(); iterator.hasNext();) {
204       String configName = String.valueOf(iterator.next());
205       GenericNodeRealizer nr = new GenericNodeRealizer(configName);
206       nr.setLabelText(configName);
207       nr.setWidth(120);
208       nr.setFillColor(Color.ORANGE);
209 
210       NodeLabel label = nr.getLabel();
211       SmartNodeLabelModel model = new SmartNodeLabelModel();
212       label.setLabelModel(model);
213       label.setModelParameter(model.getDefaultParameter());
214 
215       //make some custom configurations for some realizers
216       if ("Simple Rectangle".equals(configName)) {
217       } else if ("Diamond".equals(configName)) {
218       } else if ("Ellipse".equals(configName)) {
219         nr.setLineColor(Color.ORANGE);
220       } else if ("Bevel".equals(configName)) {
221         nr.setLineColor(Color.ORANGE);
222       } else if ("Shiny Plate".equals(configName)) {
223       } else if ("Round Rectangle".equals(configName)) {
224       } else if ("Butterfly".equals(configName)) {
225       } else if ("Flat Button".equals(configName)) {
226       } else if ("Floating".equals(configName)) {
227       } else if ("Raster Graphics".equals(configName)) {
228         nr.setLabelText("");
229       } else if ("Vector Graphics".equals(configName)) {
230         nr.setLabelText("");
231       } else if ("Decorated Rect".equals(configName)) {
232         label.setModelParameter(model.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_LEFT));
233       }
234 
235       realizers.add(nr);
236     }
237 
238     return realizers;
239   }
240 
241   /**
242    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
243    * will be a ShapeNodePainter that paints a rectangle.
244    *
245    * No GenericNodeRealizer.ContainsTest is set explicitly so that hit testing is done using the default NodeRealizer's
246    * hit test.
247    */
248   private Map createSimpleRectangleConfiguration(GenericNodeRealizer.Factory factory) {
249     // Retrieve a map that holds the default GenericNodeRealizer configuration.
250     // The implementations contained therein can be replaced one by one in order
251     // to create custom configurations...
252     Map implementationsMap = factory.createDefaultConfigurationMap();
253 
254     ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.RECT);
255     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
256 
257     return implementationsMap;
258   }
259 
260   /**
261    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
262    * will be a ShapeNodePainter that paints a diamond.
263    *
264    * Since ShapeNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains test.
265    * Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn shape.
266    */
267   private Map createDiamondConfiguration(GenericNodeRealizer.Factory factory) {
268     Map implementationsMap = factory.createDefaultConfigurationMap();
269 
270     ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.DIAMOND);
271     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
272 
273     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
274     return implementationsMap;
275   }
276 
277   /**
278    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
279    * will be a ShapeNodePainter that paints an ellipse. Also this painter is wrapped with a shadow painter that draws a
280    * nice drop shadow.
281    *
282    * Since ShapeNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains test.
283    * Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn shape.
284    *
285    * Finally a custom GenericNodeRealizer.HotSpotPainter is set, that is responsible for drawing the resize knobs and
286    * and register hits on them.
287    */
288   private Map createEllipseConfiguration(GenericNodeRealizer.Factory factory) {
289     Map implementationsMap = factory.createDefaultConfigurationMap();
290 
291     ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.ELLIPSE);
292     GenericNodeRealizer.Painter wrappedPainter = new ShadowNodePainter(painter);
293     implementationsMap.put(GenericNodeRealizer.Painter.class, wrappedPainter);
294     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
295 
296     // The node has four resize knobs, one at each of the node's corners. Both painting
297     // and hit-testing is done by this custom hot spot painter.
298     CustomHotSpotPainter chsp = new CustomHotSpotPainter(165, new Ellipse2D.Double(), null);
299     implementationsMap.put(GenericNodeRealizer.HotSpotPainter.class, chsp);
300     implementationsMap.put(GenericNodeRealizer.HotSpotHitTest.class, chsp);
301 
302     return implementationsMap;
303   }
304 
305   private Map createCircleConfiguration(GenericNodeRealizer.Factory factory) {
306     Map implementationsMap = factory.createDefaultConfigurationMap();
307 
308     CircleNodePainter painter = new CircleNodePainter();
309     GenericNodeRealizer.Painter wrappedPainter = new ShadowNodePainter(painter);
310     implementationsMap.put(GenericNodeRealizer.Painter.class, wrappedPainter);
311     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
312 
313     return implementationsMap;
314   }
315 
316   /**
317    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
318    * will be a {@link y.view.BevelNodePainter} that paints a node in a bevel like style.
319    *
320    * Since BevelNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains test.
321    * Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn shape.
322    *
323    * Finally a special GenericNodeRealizer.UserDataHandler is set, so that serialization/deserialization of user-defined
324    * data is taken care of.
325    */
326   private Map createBevelNodeConfiguration(GenericNodeRealizer.Factory factory) {
327     Map implementationsMap = factory.createDefaultConfigurationMap();
328 
329     BevelNodePainter painter = new BevelNodePainter();
330     //BevelNodePainter has an own option to draw a drop shadow that is more efficient than wrapping it with
331     // {@link y.view.ShadowNodePainter}
332     painter.setDrawShadow(true);
333     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
334     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
335 
336     // User-defined data objects that implement both the Cloneable and Serializable
337     // interfaces are taken care of (when serializing/deserializing the realizer).
338     implementationsMap.put(GenericNodeRealizer.UserDataHandler.class,
339         new SimpleUserDataHandler(SimpleUserDataHandler.REFERENCE_ON_FAILURE));
340 
341     return implementationsMap;
342   }
343 
344   /**
345    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
346    * will be a {@link y.view.ShinyPlateNodePainter} that paints a node like a shiny plate.
347    *
348    * Since ShinyPlateNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains
349    * test. Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn
350    * shape.
351    *
352    * Finally an {@link y.view.GenericNodeRealizer.GenericSizeConstraintProvider} is added to determine the minimum and
353    * maximum bounds of the node.
354    */
355   private Map createShinyPlateNodeConfiguration(GenericNodeRealizer.Factory factory) {
356     Map implementationsMap = factory.createDefaultConfigurationMap();
357 
358     ShinyPlateNodePainter painter = new ShinyPlateNodePainter();
359     //ShinyPlateNodePainter has an own option to draw a drop shadow that is more efficient than wrapping it with
360     // {@link y.view.ShadowNodePainter}
361     painter.setDrawShadow(true);
362     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
363     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
364 
365     GenericNodeRealizer.GenericSizeConstraintProvider scp = new GenericNodeRealizer.GenericSizeConstraintProvider() {
366       public YDimension getMinimumSize(NodeRealizer context) {
367         return new YDimension(15, 15);
368       }
369 
370       public YDimension getMaximumSize(NodeRealizer context) {
371         return new YDimension(250, 100);
372       }
373     };
374     implementationsMap.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, scp);
375 
376     return implementationsMap;
377   }
378 
379   /**
380    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
381    * will be an implementation of an own class {@link RectangularShapePainter} that paints a node with a given
382    * rectangular shape. This painter is wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow will be
383    * painted.
384    *
385    * Since RectangularShapePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains
386    * test. Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn
387    * shape.
388    *
389    * A custom HotSpotPainter is set as well as an own UserDataHandler.
390    */
391   private Map createRoundRectConfiguration(GenericNodeRealizer.Factory factory) {
392     Map implementationsMap = factory.createDefaultConfigurationMap();
393 
394     RectangularShapePainter painter = new RectangularShapePainter(new RoundRectangle2D.Double(50, 50, 50, 50, 15, 15));
395     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
396     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
397 
398     // User-defined data objects that implement both the Cloneable and Serializable
399     // interfaces are taken care of (when serializing/deserializing the realizer).
400     implementationsMap.put(GenericNodeRealizer.UserDataHandler.class,
401         new SimpleUserDataHandler(SimpleUserDataHandler.REFERENCE_ON_FAILURE));
402 
403     // The node has the maximum of eight resize knobs, one at each of the node's
404     // corners and also one at the middle of each side.
405     CustomHotSpotPainter chsp = new CustomHotSpotPainter(255, new Ellipse2D.Double(), Color.red);
406     implementationsMap.put(GenericNodeRealizer.HotSpotPainter.class, chsp);
407     implementationsMap.put(GenericNodeRealizer.HotSpotHitTest.class, chsp);
408 
409     return implementationsMap;
410   }
411 
412   private Map createNoteNodeConfiguration(GenericNodeRealizer.Factory factory) {
413     Map implementationsMap = factory.createDefaultConfigurationMap();
414 
415     final NoteNodePainter painter = new NoteNodePainter();
416     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
417     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
418 
419     return implementationsMap;
420   }
421 
422   /**
423    * Will use the given implementationMap and simply set another Painter implementation. The painter will be a
424    * y.view.GeneralPathNodePainter, which takes any GeneralPath and paints it as a node shape. This painter is wrapped
425    * with {@link y.view.ShadowNodePainter} so that a drop shadow will be painted.
426    *
427    * Since GeneralPathNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains
428    * test. Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn
429    * shape.
430    *
431    * Note: all other settings of the given configuration like for example the HotSpotPainter and HotSpotHitTest will
432    * remain untouched.
433    */
434   private Map createButterflyConfiguration(Map implementationsMap) {
435     //create the general path of the butterfly
436     GeneralPath gp = new GeneralPath();
437     gp.moveTo(1.0f, 0.5f);
438     gp.lineTo(0.0f, 1.0f);
439     gp.quadTo(0.0f, 0.5f, 0.3f, 0.5f);
440     gp.quadTo(0.0f, 0.5f, 0.0f, 0.0f);
441     gp.closePath();
442 
443     GeneralPathNodePainter painter = new GeneralPathNodePainter(gp);
444     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
445     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
446 
447     return implementationsMap;
448   }
449 
450   /**
451    * Will use the default configuration map from GenericNodeRealizer and set an own custom painter implementation of
452    * type {@link FlatButtonPainter}.
453    */
454   private Map createFlatButtonConfiguration(GenericNodeRealizer.Factory factory) {
455     Map implementationsMap = factory.createDefaultConfigurationMap();
456 
457     FlatButtonPainter painter = new FlatButtonPainter();
458     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
459 
460     return implementationsMap;
461   }
462 
463   /**
464    * Will use the default configuration map from GenericNodeRealizer and set an own custom painter implementation of
465    * type {@link FloatingPainter}.This painter is wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow
466    * will be painted.
467    *
468    * Since this painter also implements GenericNodeRealizer.ContainsTest it is also set accordingly.
469    */
470   private Map createFloatingConfiguration(GenericNodeRealizer.Factory factory) {
471     Map implementationsMap = factory.createDefaultConfigurationMap();
472 
473     FloatingPainter painter = new FloatingPainter();
474     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
475     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
476 
477     return implementationsMap;
478   }
479 
480   /**
481    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
482    * will be an {@link y.view.ImageNodePainter} that paints a node according to a given raster graphics image. This
483    * painter is wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow will be painted.
484    */
485   private Map createRasterGraphicsConfiguration(GenericNodeRealizer.Factory factory) {
486     Map implementationsMap = factory.createDefaultConfigurationMap();
487     ImageNodePainter painter = new ImageNodePainter(getClass().getResource("resource/yWorksNode.png"));
488     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
489     return implementationsMap;
490   }
491 
492   /**
493    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
494    * will be a yext.svg.view.SVGPainter that paints a node according to a given vector graphics image. This painter is
495    * wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow will be painted.
496    *
497    * Note: Since SVGPainter is not part of the yFiles distribution, but part of the free yFiles extension ySVG this code
498    * is commented out by default. For this code to work, ySVG must be included in the classpath. For more information on
499    * ySVG have a look at: http://www.yworks.com/ysvg
500    */
501   private Map createVectorGraphicsConfiguration(GenericNodeRealizer.Factory factory) {
502     Map implementationsMap = factory.createDefaultConfigurationMap();
503     //todo: uncomment this to use svg images. Note: the ySVG package is needed for this to work
504 //    URL resource = getClass().getResource("resource/yWorksNode.svg");
505 //    SVGPainter painter = new SVGPainter(resource);
506 //    implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
507     return implementationsMap;
508   }
509 
510   /**
511    * Creates a configuration where a painter will be used that decorates another painter with an icon.
512    */
513   private Map createDecoratedRectPainterConfiguration(GenericNodeRealizer.Factory factory) {
514     Map implementationsMap = factory.createDefaultConfigurationMap();
515     IconDecoratorPainter painter = new IconDecoratorPainter(new ShapeNodePainter());
516     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
517     return implementationsMap;
518   }
519 
520   protected EditMode createEditMode() {
521     EditMode editMode = new EditMode();
522     editMode.assignNodeLabel(false);
523     return editMode;
524   }
525   
526   /** Launcher method. Execute this class to see sample instantiations of {@link GenericNodeRealizer} in action. */
527   public static void main(String[] args) {
528     EventQueue.invokeLater(new Runnable() {
529       public void run() {
530         Locale.setDefault(Locale.ENGLISH);
531         initLnF();
532         (new GenericNodeRealizerDemo()).start("GenericNodeRealizer Demo");
533       }
534     });
535   }
536 
537   private static boolean useGradientStyle( final Graphics2D graphics ) {
538     return YRenderingHints.isGradientPaintingEnabled(graphics);
539   }
540 
541 
542   /**
543    * A custom HotSpotPainter implementation that uses the given shape and color to paint the resize knobs, a.k.a. hot
544    * spots. If the given color is <code>null</code>, then the node's fill color is used instead. <p> Note that his
545    * painter also provides support for hit-testing the resize knobs.
546    */
547   static final class CustomHotSpotPainter extends AbstractCustomHotSpotPainter {
548     private RectangularShape shape;
549     private Color color;
550 
551     CustomHotSpotPainter(int mask, RectangularShape shape, Color color) {
552       super(mask);
553       this.shape = shape;
554       this.color = color;
555     }
556 
557     protected void initGraphics(NodeRealizer context, Graphics2D g) {
558       super.initGraphics(context, g);
559       if (color == null) {
560         Color fc = context.getFillColor();
561         if (fc != null) {
562           g.setColor(fc);
563         }
564       } else {
565         g.setColor(color);
566       }
567     }
568 
569     protected void paint(byte hotSpot, double centerX, double centerY, Graphics2D graphics) {
570       shape.setFrame(centerX - 2, centerY - 2, 5, 5);
571       graphics.fill(shape);
572     }
573 
574     protected boolean isHit(byte hotSpot, double centerX, double centerY, double testX, double testY) {
575       return Math.abs(testX - centerX) < 3 && Math.abs(testY - centerY) < 3;
576     }
577   }
578 
579   /** A custom Painter and ContainsTest implementation that can be used with any kind of <code>RectangularShape</code>. */
580   public static final class RectangularShapePainter extends AbstractCustomNodePainter implements GenericNodeRealizer.ContainsTest {
581     private RectangularShape shape;
582 
583     public RectangularShapePainter(RectangularShape shape) {
584       this.shape = shape;
585     }
586 
587     /** Overrides the default fill color. */
588     protected Color getFillColor(NodeRealizer context, boolean selected) {
589       if (selected) {
590         return Color.red;
591       } else {
592         return super.getFillColor(context, selected);
593       }
594     }
595 
596     protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy) {
597       shape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
598       if (initializeFill(context, graphics)) {
599         graphics.fill(shape);
600       }
601       if (initializeLine(context, graphics)) {
602         graphics.draw(shape);
603       }
604     }
605 
606     public boolean contains(NodeRealizer context, double x, double y) {
607       shape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
608       return shape.contains(x, y);
609     }
610   }
611 
612   /**
613    * A custom GenericNodeRealizer.Painter implementation that will paint a node as a round rectangle surrounded with a
614    * small border.
615    *
616    * Also implements GenericNodeRealizer.ContainsTest. The test will mark coordinates as contained if they are
617    * <b>inside</b> the inner round rectangle. Thus for example edges will also be clipped there.
618    *
619    * This implementation also demonstrates how to use the {@link y.view.GenericNodeRealizer#getStyleProperty(String)}
620    * for retrieving instance specific state that cannot be determined from the
621    * {@link y.view.GenericNodeRealizer#getUserData() user data}.
622    */
623   public static class FloatingPainter extends AbstractCustomNodePainter implements GenericNodeRealizer.ContainsTest {
624     private final RoundRectangle2D innerShape;
625     private final RoundRectangle2D outerShape;
626     private RoundRectangle2D measureRect;
627     private double radius = 8;
628 
629     public FloatingPainter() {
630       this.innerShape = new RoundRectangle2D.Double(0, 0, -1, -1, radius, radius);
631       this.outerShape = new RoundRectangle2D.Double(0, 0, -1, -1, radius, radius);
632     }
633 
634     protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy) {
635       double inset = getInset(context);
636       innerShape.setFrame(context.getX() + inset, context.getY() + inset, context.getWidth() - 2 * inset,
637           context.getHeight() - 2 * inset);
638       if (initializeFill(context, graphics)) {
639         graphics.fill(innerShape);
640       }
641       outerShape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
642       if (initializeLine(context, graphics)) {
643         graphics.draw(outerShape);
644       }
645     }
646 
647     /**
648      * Callback method that retrieves the inset to use for the given context.
649      * This will use the {@link y.view.GenericNodeRealizer#getStyleProperty(String)}
650      * method to query a {@link Number} for its {@link Number#doubleValue()} as
651      * the inset.
652      * @param context The node realizer to obtain the inset for.
653      * @return The value of the "FloatingPainter.Inset" property as a double or 4.0d
654      * if none has been defined for the instance.
655      */
656     protected double getInset(NodeRealizer context) {
657       Object o = ((GenericNodeRealizer)context).getStyleProperty("FloatingPainter.Inset");
658       if (o instanceof Number){
659         return ((Number)o).doubleValue();
660       } else {
661         return 4;
662       }
663     }
664 
665     protected Paint getLinePaint(final NodeRealizer context, final boolean selected) {
666       return getFillPaint(context, selected);
667     }
668 
669     protected Color getLineColor( final NodeRealizer context, final boolean selected ) {
670       return getFillColor(context, selected);
671     }
672 
673     public boolean contains(NodeRealizer context, double x, double y) {
674       if (null == measureRect) {
675         measureRect = new RoundRectangle2D.Double();
676       }
677       double inset = getInset(context);
678       measureRect.setRoundRect(context.getX() + inset, context.getY() + inset, context.getWidth() - 2 * inset,
679           context.getHeight() - 2 * inset, radius, radius);
680       return measureRect.contains(x, y);
681     }
682   }
683 
684   /** A custom GenericNodeRealizer.Painter implementation that paints a node in a flat button like style. */
685   static class FlatButtonPainter extends AbstractCustomNodePainter {
686     protected void paintNode(NodeRealizer context, Graphics2D g, boolean sloppy) {
687       double x = context.getX();
688       double y = context.getY();
689       double w = context.getWidth();
690       double h = context.getHeight();
691 
692       Shape shape = new RoundRectangle2D.Double(x, y, w, h, 10, 10);
693       Color c1 = context.getFillColor();
694       paintBorder(g, c1, (float) 2, shape);
695       paintContent(g, shape, c1, 0, x, y, w, h);
696     }
697 
698     private void paintBorder(Graphics2D g, Color c1, float thick, Shape shape) {
699       float ratio = 0.75f;
700       g.setColor(mixColors(new Color(128, 128, 128, 64), c1, ratio));
701       g.setStroke(new BasicStroke(thick, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
702       g.draw(shape);
703 
704       g.setColor(mixColors(new Color(255, 255, 255, 196), c1, ratio));
705       g.setStroke(new BasicStroke(thick / 2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
706       g.translate(thick / 4, thick / 4);
707       g.draw(shape);
708 
709       g.setColor(mixColors(new Color(0, 0, 0, 64), c1, ratio));
710       g.setStroke(new BasicStroke(thick / 2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
711       g.translate(-thick / 2, -thick / 2);
712       g.draw(shape);
713       g.translate(thick / 4, thick / 4);
714     }
715 
716     private void paintContent(Graphics2D g, Shape shape, Color c1, int thick, double x, double y, double w, double h) {
717       Color c2 = Color.WHITE;
718       Color c3 = mixColors(c1, c2, 0.5f);
719       BasicStroke stroke = new BasicStroke(thick, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
720       g.setStroke(stroke);
721       g.setPaint(new GradientPaint((float) x, (float) y - thick - 1, c3, (float) x,
722           (float) (y + h + thick + 1), c1));
723 
724       Shape strokedShape = stroke.createStrokedShape(shape);
725       Area area = new Area(strokedShape);
726       area.add(new Area(shape));
727 
728       g.fill(area);
729 
730       Shape oldClip = g.getClip();
731 
732       g.clip(area);
733       g.clip(new Ellipse2D.Double(x - w, y - h * 0.5, w * 3, h * 0.75));
734 
735       if (useGradientStyle(g)) {
736         g.setPaint(new GradientPaint((float) x, (float) (y - 5), new Color(1.0f, 1.0f, 1.0f, 0.3f), (float) x,
737             (float) (y + h), new Color(1.0f, 1.0f, 1.0f, 0.0f)));
738       } else {
739         g.setColor(new Color(1.0f, 1.0f, 1.0f, 0.3f));
740       }
741 
742       g.fill(
743           new Rectangle2D.Double(x - thick - thick, y - thick, w + thick + thick + thick + thick, h + thick + thick));
744 
745       g.setClip(oldClip);
746     }
747 
748     private Color mixColors(Color c1, Color c2, float ratio) {
749       float b = 1 - ratio;
750       return new Color((c1.getRed() * ratio + c2.getRed() * b) / 255f,
751           (c1.getGreen() * ratio + c2.getGreen() * b) / 255f,
752           (c1.getBlue() * ratio + c2.getGreen() * b) / 255f, (c1.getAlpha() * ratio + c2.getAlpha() * b) / 255f);
753     }
754   }
755 
756   static class IconDecoratorPainter implements GenericNodeRealizer.Painter {
757     private final GenericNodeRealizer.Painter innerPainter;
758     private Icon icon;
759 
760     public IconDecoratorPainter(GenericNodeRealizer.Painter innerPainter) {
761       this.innerPainter = innerPainter;
762       icon = createIcon();
763     }
764 
765     public void paint( final NodeRealizer context, final Graphics2D graphics ) {
766       innerPainter.paint(context, graphics);
767       paintIcon(context, graphics);
768     }
769 
770     public void paintSloppy( final NodeRealizer context, final Graphics2D graphics ) {
771       innerPainter.paintSloppy(context, graphics);
772       paintIcon(context, graphics);
773     }
774 
775     private void paintIcon( final NodeRealizer context, final Graphics2D graphics ) {
776       int x = (int) (context.getX() + context.getWidth() - icon.getIconWidth() - 2);
777       int y = (int) (context.getY() + 2);
778       icon.paintIcon(null, graphics, x, y);
779     }
780 
781     protected Icon createIcon() {
782       URL imageURL = ClassLoader.getSystemResource("demo/view/resource/yicon.png");
783       final ImageIcon imageIcon = new ImageIcon(imageURL);
784       final double zoom = 16.0d / Math.min(imageIcon.getIconHeight(), imageIcon.getIconWidth());
785 
786       return new Icon() {
787         public void paintIcon(Component c, Graphics g, int x, int y) {
788           g.drawImage(imageIcon.getImage(), x, y, getIconWidth(), getIconHeight(), null);
789         }
790 
791         public int getIconWidth() {
792           return (int) (imageIcon.getIconWidth() * zoom);
793         }
794 
795         public int getIconHeight() {
796           return (int) (imageIcon.getIconHeight() * zoom);
797         }
798       };
799     }
800   }
801 
802   /**
803    * A custom GenericNodeRealizer.Painter implementation that paints a node as
804    * a circle (independent of the actual aspect ratio of node's bounding box).
805    */
806   static class CircleNodePainter
807           extends AbstractCustomNodePainter
808           implements GenericNodeRealizer.ContainsTest {
809     private final Ellipse2D.Double circle = new Ellipse2D.Double();
810 
811     protected void paintNode(
812             final NodeRealizer context,
813             final Graphics2D graphics,
814             final boolean sloppy
815     ) {
816       final double w = context.getWidth();
817       final double h = context.getHeight();
818       final double d = Math.min(w, h);
819       circle.setFrame(
820               context.getX() + (w - d) * 0.5,
821               context.getY() + (h - d) * 0.5, d, d);
822 
823       final boolean useSelectionStyle = useSelectionSyle(context, graphics);
824       final Color fc = getFillColor(context, useSelectionStyle);
825       if (fc != null) {
826         graphics.setColor(fc);
827         graphics.fill(circle);
828       }
829 
830       final Color lc = getLineColor(context, useSelectionStyle);
831       final Stroke ls = getLineStroke(context, useSelectionStyle);
832       if (lc != null && ls != null) {
833         graphics.setColor(lc);
834         graphics.setStroke(ls);
835         graphics.draw(circle);
836       }
837     }
838 
839     public boolean contains(
840             final NodeRealizer context,
841             final double x,
842             final double y
843     ) {
844       final double w = context.getWidth();
845       final double h = context.getHeight();
846       final double tx = context.getX() + w * 0.5 - x;
847       final double ty = context.getY() + h * 0.5 - y;
848       return Math.sqrt(tx*tx + ty*ty) <= Math.min(w, h) * 0.5;
849     }
850 
851     private static boolean useSelectionSyle(
852             final NodeRealizer context,
853             final Graphics2D graphics
854     ) {
855       return context.isSelected() &&
856              YRenderingHints.isSelectionPaintingEnabled(graphics);
857     }
858   }
859 
860   /**
861    * A custom GenericNodeRealizer.Painter implementation that paints a node as
862    * a dog-eared note similar to the notes that are used in UML diagrams.
863    */
864   static class NoteNodePainter
865           extends AbstractCustomNodePainter
866           implements GenericNodeRealizer.ContainsTest {
867     private static final double DOG_EAR_SIZE = 15;
868 
869     private final GeneralPath shape;
870     private final Rectangle2D.Double fallback;
871 
872     private Color dogEarColor;
873 
874     public NoteNodePainter() {
875       shape = new GeneralPath();
876       fallback = new Rectangle2D.Double();
877       dogEarColor = Color.LIGHT_GRAY;
878     }
879 
880     public Color getDogEarColor() {
881       return dogEarColor;
882     }
883 
884     public void setDogEarColor( final Color dogEarColor ) {
885       this.dogEarColor = dogEarColor;
886     }
887 
888     protected void paintNode(
889             final NodeRealizer context,
890             final Graphics2D graphics,
891             final boolean sloppy
892     ) {
893       final boolean useSelectionStyle = useSelectionSyle(context, graphics);
894 
895       final double w = context.getWidth();
896       final double h = context.getHeight();
897       if (w < DOG_EAR_SIZE + 5 && h < DOG_EAR_SIZE + 5) {
898         // node is "too small" for a dog ear - paint a simple rectangle instead
899         fallback.setFrame(context.getX(), context.getY(), w, h);
900         final Paint fp =
901                 useGradientStyle(graphics)
902                 ? getFillPaint(context, useSelectionStyle)
903                 : getFillColor(context, useSelectionStyle);
904         if (fp != null) {
905           graphics.setPaint(fp);
906           graphics.fill(fallback);
907         }
908 
909         final Color lc = getLineColor(context, useSelectionStyle);
910         final Stroke ls = getLineStroke(context, useSelectionStyle);
911         if (lc != null && ls != null) {
912           graphics.setColor(lc);
913           graphics.setStroke(ls);
914           graphics.draw(fallback);
915         }
916       } else {
917         // start with the basic shape of the node
918         shape.reset();
919         final double x = context.getX();
920         final double y = context.getY();
921         final double maxX = x + w;
922         final double maxY = y + h;
923 
924         shape.moveTo((float) x, (float) y);
925         shape.lineTo((float) (maxX - DOG_EAR_SIZE), (float) y);
926         shape.lineTo((float) maxX, (float) (y + DOG_EAR_SIZE));
927         shape.lineTo((float) maxX, (float) maxY);
928         shape.lineTo((float) x, (float) maxY);
929         shape.closePath();
930 
931         // fill the shape's interior
932         final Paint fp =
933                 useGradientStyle(graphics)
934                 ? getFillPaint(context, useSelectionStyle)
935                 : getFillColor(context, useSelectionStyle);
936         if (fp != null) {
937           graphics.setPaint(fp);
938           graphics.fill(shape);
939         }
940 
941         // draw the shape border
942         final Color lc = getLineColor(context, useSelectionStyle);
943         final Stroke ls = getLineStroke(context, useSelectionStyle);
944         if (lc != null && ls != null) {
945           graphics.setColor(lc);
946           graphics.setStroke(ls);
947           graphics.draw(shape);
948         }
949 
950         // now paint the dog ear interior
951         final Color dogEarColor = getDogEarColor(context);
952         if (dogEarColor != null) {
953           shape.reset();
954           shape.moveTo((float) (maxX - DOG_EAR_SIZE), (float) y);
955           shape.lineTo((float) (maxX - DOG_EAR_SIZE), (float) (y + DOG_EAR_SIZE));
956           shape.lineTo((float) maxX, (float) (y + DOG_EAR_SIZE));
957           shape.closePath();
958 
959           graphics.setColor(dogEarColor);
960           graphics.fill(shape);
961         }
962 
963         // and finally draw the dog ear border
964         if (lc != null && ls != null) {
965           shape.reset();
966           shape.moveTo((float) (maxX - DOG_EAR_SIZE), (float) y);
967           shape.lineTo((float) (maxX - DOG_EAR_SIZE), (float) (y + DOG_EAR_SIZE));
968           shape.lineTo((float) maxX, (float) (y + DOG_EAR_SIZE));
969 
970           graphics.setColor(lc);
971           graphics.setStroke(ls);
972           graphics.draw(shape);
973         }
974       }
975     }
976 
977     private Color getDogEarColor( final NodeRealizer context ) {
978       if (context instanceof GenericNodeRealizer) {
979         final Object property =
980                 ((GenericNodeRealizer) context).getStyleProperty("dogEarColor");
981         if (property instanceof Color) {
982           return (Color) property;
983         }
984       }
985 
986       return getDogEarColor();
987     }
988 
989     public boolean contains(
990             final NodeRealizer context,
991             final double tx,
992             final double ty
993     ) {
994       final double w = context.getWidth();
995        final double h = context.getHeight();
996        if (w < DOG_EAR_SIZE + 5 && h < DOG_EAR_SIZE + 5) {
997          fallback.setFrame(context.getX(), context.getY(), w, h);
998          return fallback.contains(tx, ty);
999        } else {
1000         shape.reset();
1001         final double x = context.getX();
1002         final double y = context.getY();
1003         shape.moveTo((float) x, (float) y);
1004         shape.lineTo((float) (x + w - DOG_EAR_SIZE), (float) y);
1005         shape.lineTo((float) (x + w), (float) (y + DOG_EAR_SIZE));
1006         shape.lineTo((float) (x + w), (float) (y + h));
1007         shape.lineTo((float) x, (float) (y + h));
1008         shape.closePath();
1009
1010         return shape.contains(tx, ty);
1011       }
1012    }
1013
1014    private static boolean useSelectionSyle(
1015            final NodeRealizer context,
1016            final Graphics2D graphics
1017    ) {
1018      return context.isSelected() &&
1019             YRenderingHints.isSelectionPaintingEnabled(graphics);
1020    }
1021  }
1022}