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