1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.6. 
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-2008 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.advanced.DragAndDropDemo;
18  import y.geom.YDimension;
19  import y.view.AbstractCustomHotSpotPainter;
20  import y.view.AbstractCustomNodePainter;
21  import y.view.BevelNodePainter;
22  import y.view.EditMode;
23  import y.view.GeneralPathNodePainter;
24  import y.view.GenericNodeRealizer;
25  import y.view.ImageNodePainter;
26  import y.view.NodeLabel;
27  import y.view.NodeRealizer;
28  import y.view.ShadowNodePainter;
29  import y.view.ShapeNodePainter;
30  import y.view.ShinyPlateNodePainter;
31  import y.view.SimpleUserDataHandler;
32  
33  import javax.swing.AbstractAction;
34  import javax.swing.Icon;
35  import javax.swing.ImageIcon;
36  import javax.swing.JList;
37  import javax.swing.JScrollPane;
38  import javax.swing.JToggleButton;
39  import javax.swing.JToolBar;
40  import java.awt.BasicStroke;
41  import java.awt.BorderLayout;
42  import java.awt.Color;
43  import java.awt.GradientPaint;
44  import java.awt.Graphics2D;
45  import java.awt.Insets;
46  import java.awt.Paint;
47  import java.awt.Shape;
48  import java.awt.event.ActionEvent;
49  import java.awt.geom.Area;
50  import java.awt.geom.Ellipse2D;
51  import java.awt.geom.GeneralPath;
52  import java.awt.geom.Rectangle2D;
53  import java.awt.geom.RectangularShape;
54  import java.awt.geom.RoundRectangle2D;
55  import java.net.URL;
56  import java.util.ArrayList;
57  import java.util.Iterator;
58  import java.util.LinkedHashMap;
59  import java.util.List;
60  import java.util.Map;
61  
62  /**
63   * This class demonstrates various usages of the {@link y.view.GenericNodeRealizer} class.
64   *
65   * It shows how to create different configurations and also shows the usage of some custom {@link
66   * GenericNodeRealizer.Painter} and {@link y.view.GenericNodeRealizer.ContainsTest} implementations.
67   */
68  public class GenericNodeRealizerDemo extends DemoBase {
69    private static final boolean INITIAL_ANTIALIASING_STATE = true;
70  
71    /** Creates the GenericNodeRealizer demo. */
72    public GenericNodeRealizerDemo() {
73      super();
74  
75      view.setAntialiasedPainting(INITIAL_ANTIALIASING_STATE);
76  
77      //create several NodeRealizer Configurations
78      List configurations = createConfigurations();
79  
80      //create the drag and drop list filled with the available realizer configurations
81      JList realizerList = createDnDList(configurations);
82      realizerList.setBackground(Color.WHITE);
83  
84      //add the realizer list to the panel
85      contentPane.add(new JScrollPane(realizerList), BorderLayout.WEST);
86  
87      //create a realizer
88      GenericNodeRealizer gnr = new GenericNodeRealizer();
89      //set one of the configurations
90      gnr.setConfiguration("Bevel");
91      //set user data
92      gnr.setUserData("This is my own userData object.");
93      // Set this node realizer as the default node realizer for this graph.
94      view.getGraph2D().setDefaultNodeRealizer(gnr);
95  
96      //load an initial graph
97      loadGraph("resource/genericNodeRealizer.gml");
98    }
99  
100   /**
101    * Creates a JList that contains GenericNodeRealizers that are configured with configuration names from the given
102    * List.
103    */
104   private JList createDnDList(List configurations) {
105     // create two maps, mapping NodeRealizer instances to strings...
106     final Map stringToNodeRealizerMap = createStringToRealizerMap(configurations);
107     final Map inverse = new LinkedHashMap();
108     for (Iterator it = stringToNodeRealizerMap.entrySet().iterator(); it.hasNext();) {
109       Map.Entry entry = (Map.Entry) it.next();
110       inverse.put(entry.getValue(), entry.getKey());
111     }
112 
113     // create the customized DnD support instance
114     final DragAndDropDemo.DragAndDropSupport dndSupport = new DragAndDropDemo.DragAndDropSupport(inverse.keySet(),
115         view) {
116       protected String getTextValue(NodeRealizer selected) {
117         return (String) inverse.get(selected);
118       }
119 
120       protected NodeRealizer createNodeRealizerFromTextValue(String s) {
121         return (NodeRealizer) stringToNodeRealizerMap.get(s);
122       }
123     };
124     return dndSupport.getList();
125   }
126 
127   /**
128    * Creates GenericNodeRealizer configurations and registers them on the factory.
129    *
130    * @return the names of the registered configurations.
131    */
132   private List createConfigurations() {
133     List configNames = new ArrayList();
134 
135     // Get the factory to register custom styles/configurations.
136     GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
137 
138     // Add the simple rectangle configuration to the factory.
139     String configName = "Simple Rectangle";
140     factory.addConfiguration(configName, createSimpleRectangleConfiguration(factory));
141     configNames.add(configName);
142 
143     // Add the diamond configuration to the factory.
144     configName = "Diamond";
145     factory.addConfiguration(configName, createDiamondConfiguration(factory));
146     configNames.add(configName);
147 
148     // Add the elliptical configuration to the factory.
149     configName = "Ellipse";
150     factory.addConfiguration(configName, createEllipseConfiguration(factory));
151     configNames.add(configName);
152 
153     // Add the bevel style configuration to the factory.
154     configName = "Bevel";
155     factory.addConfiguration(configName, createBevelNodeConfiguration(factory));
156     configNames.add(configName);
157 
158     // Add the shiny plate configuration to the factory.
159     configName = "Shiny Plate";
160     factory.addConfiguration(configName, createShinyPlateNodeConfiguration(factory));
161     configNames.add(configName);
162 
163     // Add the rounded rectangle configuration to the factory.
164     Map roundRectConfiguration = createRoundRectConfiguration(factory);
165     configName = "Round Rectangle";
166     factory.addConfiguration(configName, roundRectConfiguration);
167     configNames.add(configName);
168 
169     //Add the butterfly configuration to the factory by reusing the round rect configuration and only overriding the painter
170     configName = "Butterfly";
171     factory.addConfiguration(configName, createButterflyConfiguration(roundRectConfiguration));
172     configNames.add(configName);
173 
174     // Add the flat button style configuration to the factory.
175     configName = "Flat Button";
176     factory.addConfiguration(configName, createFlatButtonConfiguration(factory));
177     configNames.add(configName);
178 
179     // Add the floating style configuration to the factory.
180     configName = "Floating";
181     factory.addConfiguration(configName, createFloatingConfiguration(factory));
182     configNames.add(configName);
183 
184     // Add the image style configuration to the factory.
185     configName = "Raster Graphics";
186     factory.addConfiguration(configName, createRasterGraphicsConfiguration(factory));
187     configNames.add(configName);
188 
189     /*
190     Note: Since SVGPainter is not part of the yFiles distribution, but part of the free yFiles extension ySVG this code
191     is commented out by default. For this code to work, ySVG must be included in the classpath.
192     For more information on ySVG have a look at: http://www.yworks.com/ysvg
193     */
194     // Add the vector graphics style configuration to the factory.
195     configName = "Vector Graphics";
196     Map vectorGraphicsConfig = createVectorGraphicsConfiguration(factory);
197     //todo: uncomment this to use svg images. Note: the ySVG package is needed for this to work
198 //    factory.addConfiguration(configName, vectorGraphicsConfig);
199 //    configNames.add(configName);
200 
201     // Add the a decorated rect style configuration to the factory.
202     configName = "Decorated Rect";
203     factory.addConfiguration(configName, createDecoratedRectPainterConfiguration(factory));
204     configNames.add(configName);
205 
206     return configNames;
207   }
208 
209   private Map createStringToRealizerMap(List configurations) {
210     LinkedHashMap map = new LinkedHashMap(configurations.size());
211     for (Iterator iterator = configurations.iterator(); iterator.hasNext();) {
212       String configName = String.valueOf(iterator.next());
213       GenericNodeRealizer nr = new GenericNodeRealizer(configName);
214       nr.setLabelText(configName);
215       nr.setWidth(120);
216       nr.setFillColor(Color.ORANGE);
217 
218       //make some custom configurations for some realizers
219       if ("Simple Rectangle".equals(configName)) {
220       } else if ("Diamond".equals(configName)) {
221       } else if ("Ellipse".equals(configName)) {
222         nr.setLineColor(Color.ORANGE);
223       } else if ("Bevel".equals(configName)) {
224         nr.setLineColor(Color.ORANGE);
225       } else if ("Shiny Plate".equals(configName)) {
226       } else if ("Round Rectangle".equals(configName)) {
227       } else if ("Butterfly".equals(configName)) {
228       } else if ("Flat Button".equals(configName)) {
229       } else if ("Floating".equals(configName)) {
230       } else if ("Raster Graphics".equals(configName)) {
231         nr.setLabelText("");
232       } else if ("Vector Graphics".equals(configName)) {
233         nr.setLabelText("");
234       } else if ("Decorated Rect".equals(configName)) {
235         nr.getLabel().setPosition(NodeLabel.LEFT);
236       }
237 
238       map.put(configName, nr);
239     }
240 
241     return map;
242   }
243 
244   /**
245    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
246    * will be a ShapeNodePainter that paints a rectangle.
247    *
248    * No GenericNodeRealizer.ContainsTest is set explicitly so that hit testing is done using the default NodeRealizer's
249    * hit test.
250    */
251   private Map createSimpleRectangleConfiguration(GenericNodeRealizer.Factory factory) {
252     // Retrieve a map that holds the default GenericNodeRealizer configuration.
253     // The implementations contained therein can be replaced one by one in order
254     // to create custom configurations...
255     Map implementationsMap = factory.createDefaultConfigurationMap();
256 
257     ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.RECT);
258     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
259 
260     return implementationsMap;
261   }
262 
263   /**
264    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
265    * will be a ShapeNodePainter that paints a diamond.
266    *
267    * Since ShapeNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains test.
268    * Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn shape.
269    */
270   private Map createDiamondConfiguration(GenericNodeRealizer.Factory factory) {
271     Map implementationsMap = factory.createDefaultConfigurationMap();
272 
273     ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.DIAMOND);
274     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
275 
276     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
277     return implementationsMap;
278   }
279 
280   /**
281    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
282    * will be a ShapeNodePainter that paints an ellipse. Also this painter is wrapped with a shadow painter that draws a
283    * nice drop shadow.
284    *
285    * Since ShapeNodepainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains test.
286    * Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn shape.
287    *
288    * Finally a custom GenericNodeRealizer.HotSpotPainter is set, that is responsible for drawing the resize knobs and
289    * and register hits on them.
290    */
291   private Map createEllipseConfiguration(GenericNodeRealizer.Factory factory) {
292     Map implementationsMap = factory.createDefaultConfigurationMap();
293 
294     ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.ELLIPSE);
295     GenericNodeRealizer.Painter wrappedPainter = new ShadowNodePainter(painter);
296     implementationsMap.put(GenericNodeRealizer.Painter.class, wrappedPainter);
297     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
298 
299     // The node has four resize knobs, one at each of the node's corners. Both painting
300     // and hit-testing is done by this custom hot spot painter.
301     CustomHotSpotPainter chsp = new CustomHotSpotPainter(165, new Ellipse2D.Double(), null);
302     implementationsMap.put(GenericNodeRealizer.HotSpotPainter.class, chsp);
303     implementationsMap.put(GenericNodeRealizer.HotSpotHitTest.class, chsp);
304 
305     return implementationsMap;
306   }
307 
308   /**
309    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
310    * will be a {@link y.view.BevelNodePainter} that paints a node in a bevel like style.
311    *
312    * Since BevelNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains test.
313    * Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn shape.
314    *
315    * Finally a special GenericNodeRealizer.UserDataHandler is set, so that serialization/deserialization of user-defined
316    * data is taken care of.
317    */
318   private Map createBevelNodeConfiguration(GenericNodeRealizer.Factory factory) {
319     Map implementationsMap = factory.createDefaultConfigurationMap();
320 
321     BevelNodePainter painter = new BevelNodePainter();
322     //BevelNodePainter has an own option to draw a drop shadow that is more efficient than wrapping it with
323     // {@link y.view.ShadowNodePainter}
324     painter.setDrawShadow(true);
325     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
326     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
327 
328     // User-defined data objects that implement both the Cloneable and Serializable
329     // interfaces are taken care of (when serializing/deserializing the realizer).
330     implementationsMap.put(GenericNodeRealizer.UserDataHandler.class,
331         new SimpleUserDataHandler(SimpleUserDataHandler.REFERENCE_ON_FAILURE));
332 
333     return implementationsMap;
334   }
335 
336   /**
337    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
338    * will be a {@link y.view.ShinyPlateNodePainter} that paints a node like a shiny plate.
339    *
340    * Since ShinyPlateNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains
341    * test. Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn
342    * shape.
343    *
344    * Finally an {@link y.view.GenericNodeRealizer.GenericSizeConstraintProvider} is added to determine the minimum and
345    * maximum bounds of the node.
346    */
347   private Map createShinyPlateNodeConfiguration(GenericNodeRealizer.Factory factory) {
348     Map implementationsMap = factory.createDefaultConfigurationMap();
349 
350     ShinyPlateNodePainter painter = new ShinyPlateNodePainter();
351     //ShinyPlateNodePainter has an own option to draw a drop shadow that is more efficient than wrapping it with
352     // {@link y.view.ShadowNodePainter}
353     painter.setDrawShadow(true);
354     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
355     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
356 
357     GenericNodeRealizer.GenericSizeConstraintProvider scp = new GenericNodeRealizer.GenericSizeConstraintProvider() {
358       public YDimension getMinimumSize(NodeRealizer context) {
359         return new YDimension(15, 15);
360       }
361 
362       public YDimension getMaximumSize(NodeRealizer context) {
363         return new YDimension(250, 100);
364       }
365     };
366     implementationsMap.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, scp);
367 
368     return implementationsMap;
369   }
370 
371   /**
372    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
373    * will be an implementation of an own class {@link RectangularShapePainter} that paints a node with a given
374    * rectangular shape. This painter is wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow will be
375    * painted.
376    *
377    * Since RectangularShapePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains
378    * test. Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn
379    * shape.
380    *
381    * A custom HotSpotPainter is set as well as an own UserDataHandler.
382    */
383   private Map createRoundRectConfiguration(GenericNodeRealizer.Factory factory) {
384     Map implementationsMap = factory.createDefaultConfigurationMap();
385 
386     RectangularShapePainter painter = new RectangularShapePainter(new RoundRectangle2D.Double(50, 50, 50, 50, 15, 15));
387     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
388     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
389 
390     // User-defined data objects that implement both the Cloneable and Serializable
391     // interfaces are taken care of (when serializing/deserializing the realizer).
392     implementationsMap.put(GenericNodeRealizer.UserDataHandler.class,
393         new SimpleUserDataHandler(SimpleUserDataHandler.REFERENCE_ON_FAILURE));
394 
395     // The node has the maximum of eight resize knobs, one at each of the node's
396     // corners and also one at the middle of each side.
397     CustomHotSpotPainter chsp = new CustomHotSpotPainter(255, new Ellipse2D.Double(), Color.red);
398     implementationsMap.put(GenericNodeRealizer.HotSpotPainter.class, chsp);
399     implementationsMap.put(GenericNodeRealizer.HotSpotHitTest.class, chsp);
400 
401     return implementationsMap;
402   }
403 
404   /**
405    * Will use the given implementationMap and simply set another Painter implementation. The painter will be a
406    * y.view.GeneralPathNodePainter, which takes any GeneralPath and paints it as a node shape. This painter is wrapped
407    * with {@link y.view.ShadowNodePainter} so that a drop shadow will be painted.
408    *
409    * Since GeneralPathNodePainter does also implement GenericNodeRealizer.ContainsTest, it is also set as the contains
410    * test. Thus, hit tests are not performed on the rectangular bounding box of the node, but really on the drawn
411    * shape.
412    *
413    * Note: all other settings of the given configuration like for example the HotSpotPainter and HotSpotHitTest will
414    * remain untouched.
415    */
416   private Map createButterflyConfiguration(Map implementationsMap) {
417     //create the general path of the butterfly
418     GeneralPath gp = new GeneralPath();
419     gp.moveTo(1.0f, 0.5f);
420     gp.lineTo(0.0f, 1.0f);
421     gp.quadTo(0.0f, 0.5f, 0.3f, 0.5f);
422     gp.quadTo(0.0f, 0.5f, 0.0f, 0.0f);
423     gp.closePath();
424 
425     GeneralPathNodePainter painter = new GeneralPathNodePainter(gp);
426     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
427     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
428 
429     return implementationsMap;
430   }
431 
432   /**
433    * Will use the default configuration map from GenericNodeRealizer and set an own custom painter implementation of
434    * type {@link FlatButtonPainter}.
435    */
436   private Map createFlatButtonConfiguration(GenericNodeRealizer.Factory factory) {
437     Map implementationsMap = factory.createDefaultConfigurationMap();
438 
439     FlatButtonPainter painter = new FlatButtonPainter();
440     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
441 
442     return implementationsMap;
443   }
444 
445   /**
446    * Will use the default configuration map from GenericNodeRealizer and set an own custom painter implementation of
447    * type {@link FloatingPainter}.This painter is wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow
448    * will be painted.
449    *
450    * Since this painter also implements GenericNodeRealizer.ContainsTest it is also set accordingly.
451    */
452   private Map createFloatingConfiguration(GenericNodeRealizer.Factory factory) {
453     Map implementationsMap = factory.createDefaultConfigurationMap();
454 
455     FloatingPainter painter = new FloatingPainter();
456     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
457     implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
458 
459     return implementationsMap;
460   }
461 
462   /**
463    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
464    * will be an {@link y.view.ImageNodePainter} that paints a node according to a given raster graphics image. This
465    * painter is wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow will be painted.
466    */
467   private Map createRasterGraphicsConfiguration(GenericNodeRealizer.Factory factory) {
468     Map implementationsMap = factory.createDefaultConfigurationMap();
469     ImageNodePainter painter = new ImageNodePainter(getClass().getResource("resource/yworksNode.png"));
470     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
471     return implementationsMap;
472   }
473 
474   /**
475    * Will use the default configuration map from GenericNodeRealizer and set an own painter implementation. The painter
476    * will be a yext.svg.view.SVGPainter that paints a node according to a given vector graphics image. This painter is
477    * wrapped with {@link y.view.ShadowNodePainter} so that a drop shadow will be painted.
478    *
479    * Note: Since SVGPainter is not part of the yFiles distribution, but part of the free yFiles extension ySVG this code
480    * is commented out by default. For this code to work, ySVG must be included in the classpath. For more information on
481    * ySVG have a look at: http://www.yworks.com/ysvg
482    */
483   private Map createVectorGraphicsConfiguration(GenericNodeRealizer.Factory factory) {
484     Map implementationsMap = factory.createDefaultConfigurationMap();
485     //todo: uncomment this to use svg images. Note: the ySVG package is needed for this to work
486 //    URL resource = getClass().getResource("resource/yWorksNode.svg");
487 //    SVGPainter painter = new SVGPainter(resource);
488 //    implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
489     return implementationsMap;
490   }
491 
492   /**
493    * Creates a configuration where a painter will be used that decorates another painter with an icon.
494    */
495   private Map createDecoratedRectPainterConfiguration(GenericNodeRealizer.Factory factory) {
496     Map implementationsMap = factory.createDefaultConfigurationMap();
497     IconDecoratorPainter painter = new IconDecoratorPainter(new ShapeNodePainter());
498     implementationsMap.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(painter));
499     return implementationsMap;
500   }
501 
502   protected EditMode createEditMode() {
503     EditMode editMode = new EditMode();
504     editMode.assignNodeLabel(false);
505     editMode.showNodeTips(true);
506     return editMode;
507   }
508 
509   protected JToolBar createToolBar() {
510     JToolBar toolBar = super.createToolBar();
511 
512     final URL iconUrl = getClass().getResource("resource/antialiasing.png");
513     final JToggleButton toggleAa = new JToggleButton(new AbstractAction("AA") {
514       {
515         if (iconUrl != null) {
516           putValue(AbstractAction.SMALL_ICON, new ImageIcon(iconUrl));
517         }
518         putValue(AbstractAction.SHORT_DESCRIPTION, "Toggle Anti-Aliasing");
519       }
520 
521       public void actionPerformed(ActionEvent e) {
522         final boolean newAaState = !view.isAntialiasedPainting();
523         view.setAntialiasedPainting(newAaState);
524         view.updateView();
525       }
526     });
527     if (iconUrl != null) {
528       toggleAa.setText("");
529       toggleAa.setMargin(new Insets(0, 0, 0, 0));
530     }
531     toggleAa.setSelected(INITIAL_ANTIALIASING_STATE);
532     toolBar.addSeparator();
533     toolBar.add(toggleAa);
534     return toolBar;
535   }
536 
537   /** Launcher method. Execute this class to see sample instantiations of {@link GenericNodeRealizer} in action. */
538   public static void main(String[] args) {
539     initLnF();
540     new GenericNodeRealizerDemo().start("GenericNodeRealizer Demo");
541   }
542 
543   /**
544    * A custom HotSpotPainter implementation that uses the given shape and color to paint the resize knobs, a.k.a. hot
545    * spots. If the given color is <code>null</code>, then the node's fill color is used instead. <p> Note that his
546    * painter also provides support for hit-testing the resize knobs.
547    */
548   static final class CustomHotSpotPainter extends AbstractCustomHotSpotPainter {
549     private RectangularShape shape;
550     private Color color;
551 
552     CustomHotSpotPainter(int mask, RectangularShape shape, Color color) {
553       super(mask);
554       this.shape = shape;
555       this.color = color;
556     }
557 
558     protected void initGraphics(NodeRealizer context, Graphics2D g) {
559       super.initGraphics(context, g);
560       if (color == null) {
561         Color fc = context.getFillColor();
562         if (fc != null) {
563           g.setColor(fc);
564         }
565       } else {
566         g.setColor(color);
567       }
568     }
569 
570     protected void paint(byte hotSpot, double centerX, double centerY, Graphics2D graphics) {
571       shape.setFrame(centerX - 2, centerY - 2, 5, 5);
572       graphics.fill(shape);
573     }
574 
575     protected boolean isHit(byte hotSpot, double centerX, double centerY, double testX, double testY) {
576       return Math.abs(testX - centerX) < 3 && Math.abs(testY - centerY) < 3;
577     }
578   }
579 
580   /** A custom Painter and ContainsTest implementation that can be used with any kind of <code>RectangularShape</code>. */
581   public static final class RectangularShapePainter extends AbstractCustomNodePainter implements GenericNodeRealizer.ContainsTest {
582     private RectangularShape shape;
583 
584     public RectangularShapePainter(RectangularShape shape) {
585       this.shape = shape;
586     }
587 
588     /** Overrides the default fill color. */
589     protected Color getFillColor(NodeRealizer context, boolean selected) {
590       if (selected) {
591         return Color.red;
592       } else {
593         return super.getFillColor(context, selected);
594       }
595     }
596 
597     protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy) {
598       shape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
599       if (initializeFill(context, graphics)) {
600         graphics.fill(shape);
601       }
602       if (initializeLine(context, graphics)) {
603         graphics.draw(shape);
604       }
605     }
606 
607     public boolean contains(NodeRealizer context, double x, double y) {
608       shape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
609       return shape.contains(x, y);
610     }
611   }
612 
613   /**
614    * A custom GenericNodeRealizer.Painter implementation that will paint a node as a round rectangle surrounded with a
615    * small border.
616    *
617    * Also implements GenericNodeRealizer.ContainsTest. The test will mark coordinates as contained if they are
618    * <b>inside</b> the inner round rectangle. Thus for example edges will also be clipped there.
619    */
620   public static class FloatingPainter extends AbstractCustomNodePainter implements GenericNodeRealizer.ContainsTest {
621     private final RoundRectangle2D innerShape;
622     private final RoundRectangle2D outerShape;
623     private RoundRectangle2D measureRect;
624     private double radius = 8;
625 
626     public FloatingPainter() {
627       this.innerShape = new RoundRectangle2D.Double(0, 0, -1, -1, radius, radius);
628       this.outerShape = new RoundRectangle2D.Double(0, 0, -1, -1, radius, radius);
629     }
630 
631     protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy) {
632       double inset = 4;
633       innerShape.setFrame(context.getX() + inset, context.getY() + inset, context.getWidth() - 2 * inset,
634           context.getHeight() - 2 * inset);
635       if (initializeFill(context, graphics)) {
636         graphics.fill(innerShape);
637       }
638       outerShape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
639       if (initializeLine(context, graphics)) {
640         graphics.draw(outerShape);
641       }
642     }
643 
644     protected Paint getLinePaint(final NodeRealizer context, final boolean selected) {
645       return getFillPaint(context, selected);
646     }
647 
648     public boolean contains(NodeRealizer context, double x, double y) {
649       if (null == measureRect) {
650         measureRect = new RoundRectangle2D.Double();
651       }
652       double inset = 4;
653       measureRect.setRoundRect(context.getX() + inset, context.getY() + inset, context.getWidth() - 2 * inset,
654           context.getHeight() - 2 * inset, radius, radius);
655       return measureRect.contains(x, y);
656     }
657   }
658 
659   /** A custom GenericNodeRealizer.Painter implementation that paints a node in a flat button like style. */
660   static class FlatButtonPainter extends AbstractCustomNodePainter {
661     protected void paintNode(NodeRealizer context, Graphics2D g, boolean sloppy) {
662       double x = context.getX();
663       double y = context.getY();
664       double w = context.getWidth();
665       double h = context.getHeight();
666 
667       Shape shape = new RoundRectangle2D.Double(x, y, w, h, 10, 10);
668       Color c1 = context.getFillColor();
669       paintBorder(g, c1, (float) 2, shape);
670       paintContent(g, shape, c1, 0, x, y, w, h);
671     }
672 
673     private void paintBorder(Graphics2D g, Color c1, float thick, Shape shape) {
674       float ratio = 0.75f;
675       g.setColor(mixColors(new Color(128, 128, 128, 64), c1, ratio));
676       g.setStroke(new BasicStroke(thick, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
677       g.draw(shape);
678 
679       g.setColor(mixColors(new Color(255, 255, 255, 196), c1, ratio));
680       g.setStroke(new BasicStroke(thick / 2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
681       g.translate(thick / 4, thick / 4);
682       g.draw(shape);
683 
684       g.setColor(mixColors(new Color(0, 0, 0, 64), c1, ratio));
685       g.setStroke(new BasicStroke(thick / 2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
686       g.translate(-thick / 2, -thick / 2);
687       g.draw(shape);
688       g.translate(thick / 4, thick / 4);
689     }
690 
691     private void paintContent(Graphics2D g, Shape shape, Color c1, int thick, double x, double y, double w, double h) {
692       Color c2 = Color.WHITE;
693       Color c3 = mixColors(c1, c2, 0.5f);
694       BasicStroke stroke = new BasicStroke(thick, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
695       g.setStroke(stroke);
696       g.setPaint(new GradientPaint((float) x, (float) y - thick - 1, c3, (float) x,
697           (float) (y + h + thick + 1), c1));
698 
699       Shape strokedShape = stroke.createStrokedShape(shape);
700       Area area = new Area(strokedShape);
701       area.add(new Area(shape));
702 
703       g.fill(area);
704 
705       Shape oldClip = g.getClip();
706 
707       g.clip(area);
708       g.clip(new Ellipse2D.Double(x - w, y - h * 0.5, w * 3, h * 0.75));
709 
710       g.setPaint(new GradientPaint((float) x, (float) (y - 5), new Color(1.0f, 1.0f, 1.0f, 0.3f), (float) x,
711           (float) (y + h), new Color(1.0f, 1.0f, 1.0f, 0.0f)));
712 
713       g.fill(
714           new Rectangle2D.Double(x - thick - thick, y - thick, w + thick + thick + thick + thick, h + thick + thick));
715 
716       g.setClip(oldClip);
717     }
718 
719     private Color mixColors(Color c1, Color c2, float ratio) {
720       float b = 1 - ratio;
721       return new Color((c1.getRed() * ratio + c2.getRed() * b) / 255f,
722           (c1.getGreen() * ratio + c2.getGreen() * b) / 255f,
723           (c1.getBlue() * ratio + c2.getGreen() * b) / 255f, (c1.getAlpha() * ratio + c2.getAlpha() * b) / 255f);
724     }
725   }
726 
727   static class IconDecoratorPainter extends AbstractCustomNodePainter{
728     private final GenericNodeRealizer.Painter innerPainter;
729     private Icon icon;
730 
731     public IconDecoratorPainter(GenericNodeRealizer.Painter innerPainter) {
732       this.innerPainter = innerPainter;
733       icon = createIcon();
734     }
735 
736     protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy) {
737       innerPainter.paint(context, graphics);
738       int x = (int) (context.getX() + context.getWidth() - icon.getIconWidth());
739       int y = (int) (context.getY());
740       icon.paintIcon(null, graphics, x, y);
741     }
742 
743     protected Icon createIcon() {
744       URL imageURL = ClassLoader.getSystemResource("demo/view/realizer/resource/about24.gif");
745       return new ImageIcon(imageURL);
746     }
747   }
748 }