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.rendering;
29  
30  import demo.view.DemoBase;
31  import y.geom.YRectangle;
32  import y.view.BevelNodePainter;
33  import y.view.GenericNodeRealizer;
34  import y.view.LineType;
35  import y.view.NodeLabel;
36  import y.view.NodePort;
37  import y.view.NodeRealizer;
38  import y.view.PortConfigurationAdapter;
39  import y.view.SelectionPortPainter;
40  import y.view.ShadowNodePainter;
41  import y.view.ShapeNodeRealizer;
42  import y.view.ShapePortConfiguration;
43  import y.view.ShinyPlateNodePainter;
44  import y.view.YLabel;
45  import y.view.YRenderingHints;
46  import y.view.hierarchy.DefaultGenericAutoBoundsFeature;
47  import y.view.hierarchy.DefaultHierarchyGraphFactory;
48  import y.view.hierarchy.GenericGroupNodeRealizer;
49  import y.view.hierarchy.GroupNodePainter;
50  import y.view.hierarchy.HierarchyManager;
51  
52  import java.awt.BorderLayout;
53  import java.awt.Color;
54  import java.awt.Component;
55  import java.awt.EventQueue;
56  import java.awt.GradientPaint;
57  import java.awt.Graphics2D;
58  import java.awt.GridBagConstraints;
59  import java.awt.GridBagLayout;
60  import java.awt.GridLayout;
61  import java.awt.Insets;
62  import java.awt.Paint;
63  import java.awt.RenderingHints;
64  import java.awt.Shape;
65  import java.awt.Stroke;
66  import java.awt.event.ActionEvent;
67  import java.awt.geom.Rectangle2D;
68  import java.awt.geom.RoundRectangle2D;
69  import java.util.HashMap;
70  import java.util.Locale;
71  import java.util.Map;
72  import javax.swing.AbstractAction;
73  import javax.swing.AbstractButton;
74  import javax.swing.Action;
75  import javax.swing.BorderFactory;
76  import javax.swing.ButtonGroup;
77  import javax.swing.JCheckBox;
78  import javax.swing.JComponent;
79  import javax.swing.JPanel;
80  import javax.swing.JRadioButton;
81  import javax.swing.JSplitPane;
82  
83  /**
84   * Demonstrates how to control the level of detail for graph visualization
85   * using rendering hints.
86   * The following hints can be set (or unset):
87   * <ul>
88   * <li>{@link YRenderingHints#KEY_GROUP_STATE_PAINTING}</li>
89   * <li>{@link YRenderingHints#KEY_EDGE_LABEL_PAINTING}</li>
90   * <li>{@link YRenderingHints#KEY_NODE_LABEL_PAINTING}</li>
91   * <li>{@link YRenderingHints#KEY_NODE_PORT_PAINTING}</li>
92   * <li>{@link YRenderingHints#KEY_GRADIENT_PAINTING}</li>
93   * <li>{@link YRenderingHints#KEY_SELECTION_PAINTING}</li>
94   * <li>{@link YRenderingHints#KEY_SHADOW_PAINTING}</li>
95   * <li>{@link ShapeNodeRealizer#KEY_SLOPPY_RECT_PAINTING}</li>
96   * <li>{@link YRenderingHints#KEY_SLOPPY_POLYLINE_PAINTING}</li>
97   * </ul>
98   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/mvc_view#rendering_hints" target="_blank">Section View Implementations</a> in the yFiles for Java Developer's Guide
99   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/realizers#cls_ShapeNodeRealizer" target="_blank">Section Bringing Graph Elements to Life: The Realizer Concept</a> in the yFiles for Java Developer's Guide
100  */
101 public class LevelOfDetailDemo extends DemoBase {
102   /**
103    * Painter implementation that demonstrates how to query rendering hints.
104    * The implementation respects the following rendering hints:
105    * <ul>
106    * <li>{@link YRenderingHints#KEY_NODE_LABEL_PAINTING}</li>
107    * <li>{@link YRenderingHints#KEY_NODE_PORT_PAINTING}</li>
108    * <li>{@link YRenderingHints#KEY_GRADIENT_PAINTING}</li>
109    * <li>{@link YRenderingHints#KEY_SELECTION_PAINTING}</li>
110    * <li>{@link ShapeNodeRealizer#KEY_SLOPPY_RECT_PAINTING}</li>
111    * </ul>
112    */
113   static final class HeaderNodePainter implements GenericNodeRealizer.Painter {
114     static final Color BACKGROUND = new Color(153, 204, 255);
115 
116     /**
117      * Checks whether or not sloppy painting should use "nice" shapes.
118      * @param graphics the graphics context to check for rendering hints.
119      * @return <code>true</code> if "nice" shapes should be used;
120      * <code>false</code> otherwise.
121      */
122     static boolean isSloppyShapePaintingEnabled( final Graphics2D graphics ) {
123       return ShapeNodeRealizer.VALUE_SLOPPY_RECT_PAINTING_OFF.equals(
124               graphics.getRenderingHint(ShapeNodeRealizer.KEY_SLOPPY_RECT_PAINTING));
125     }
126 
127     /**
128      * Checks whether or not node ports should be painted.
129      * The default behavior is to paint node ports for detail rendering but not
130      * for sloppy rendering.
131      * @param graphics the graphics context to check for rendering hints.
132      * @param sloppy the rendering mode.
133      * @return <code>true</code> if node ports should be painted;
134      * <code>false</code> otherwise.
135      */
136     static boolean shouldPaintPorts( final Graphics2D graphics, final boolean sloppy ) {
137       if (sloppy) {
138         return YRenderingHints.VALUE_NODE_PORT_PAINTING_ON.equals(
139                 graphics.getRenderingHint(YRenderingHints.KEY_NODE_PORT_PAINTING));
140       } else {
141         return !YRenderingHints.VALUE_NODE_PORT_PAINTING_OFF.equals(
142                 graphics.getRenderingHint(YRenderingHints.KEY_NODE_PORT_PAINTING));
143       }
144     }
145 
146     /**
147      * Checks whether or not node labels should be painted.
148      * The default behavior is to paint node labels for detail rendering but not
149      * for sloppy rendering.
150      * @param graphics the graphics context to check for rendering hints.
151      * @param sloppy the rendering mode.
152      * @return <code>true</code> if labels should be painted;
153      * <code>false</code> otherwise.
154      */
155     static boolean shouldPaintLabels( final Graphics2D graphics, final boolean sloppy ) {
156       if (sloppy) {
157         return YRenderingHints.VALUE_NODE_LABEL_PAINTING_ON.equals(
158                 graphics.getRenderingHint(YRenderingHints.KEY_NODE_LABEL_PAINTING));
159       } else {
160         return !YRenderingHints.VALUE_NODE_LABEL_PAINTING_OFF.equals(
161                 graphics.getRenderingHint(YRenderingHints.KEY_NODE_LABEL_PAINTING));
162       }
163     }
164 
165 
166     public void paint( final NodeRealizer context, final Graphics2D graphics ) {
167       paint(context, graphics, false);
168     }
169 
170     public void paintSloppy( final NodeRealizer context, final Graphics2D graphics ) {
171       paint(context, graphics, true);
172     }
173 
174     /**
175      * Paints the node in detail and sloppy rendering mode.
176      * @param context the node context.
177      * @param graphics the graphics context to paint upon.
178      * @param sloppy the rendering mode.
179      */
180     void paint(
181             final NodeRealizer context,
182             final Graphics2D graphics,
183             final boolean sloppy
184     ) {
185       if (!context.isVisible()) {
186         return;
187       }
188 
189       if (!sloppy &&
190           context.isSelected() &&
191           // check whether or not selection state should be respected for
192           // rendering
193           YRenderingHints.isSelectionPaintingEnabled(graphics)) {
194         context.paintHotSpots(graphics);
195       }
196 
197       final Shape shape = createShape(context, graphics, sloppy);
198       paintFilledShape(context, graphics, shape, sloppy);
199       paintShapeBorder(context, graphics, shape);
200 
201       // check whether or not node ports should be painted
202       if (shouldPaintPorts(graphics, sloppy)) {
203         context.paintPorts(graphics);
204       }
205 
206       // check whether or not node labels should be painted
207       if (shouldPaintLabels(graphics, sloppy)) {
208         context.paintText(graphics);
209       }
210     }
211 
212     /**
213      * Creates the shape that represents the node.
214      * @param context the node context. Provides geometry for the node's shape.
215      * @param graphics the graphics context to check for rendering hints.
216      * @param sloppy the rendering mode.
217      * @return the shape that represents the node.
218      */
219     Shape createShape(
220             final NodeRealizer context,
221             final Graphics2D graphics,
222             final boolean sloppy
223     ) {
224       // check whether or not a "nice" shape should be used for sloppy rendering
225       if (sloppy && !isSloppyShapePaintingEnabled(graphics)) {
226         // use "simply" shape
227         return new Rectangle2D.Double(
228                 context.getX(), context.getY(),
229                 context.getWidth(), context.getHeight());
230       } else {
231         // use "nice" shape
232         return new RoundRectangle2D.Double(
233                 context.getX(), context.getY(),
234                 context.getWidth(), context.getHeight(),
235                 8, 8);
236       }
237     }
238 
239     /**
240      * Paints the node interior.
241      * @param context the node context. Provides color/paint for the node's
242      * interior.
243      * @param graphics the graphics context to paint upon.
244      * @param shape the node's shape.
245      * @param sloppy the rendering mode.
246      */
247     void paintFilledShape(
248             final NodeRealizer context,
249             final Graphics2D graphics,
250             final Shape shape,
251             final boolean sloppy
252     ) {
253       if (context.isTransparent()) {
254         return;
255       }
256 
257       // check whether or not gradient paints should be used
258       if (sloppy || !YRenderingHints.isGradientPaintingEnabled(graphics)) {
259         // use flat colors (no gradient paints)
260         final Color fc = context.getFillColor();
261         if (fc != null) {
262           final Color oldColor = graphics.getColor();
263 
264           graphics.setColor(fc);
265           graphics.fill(shape);
266 
267           graphics.setColor(oldColor);
268         }
269       } else {
270         // use gradient paints
271         Color fc1 = context.getFillColor();
272         Color fc2 = context.getFillColor2();
273         if (fc1 != null || fc2 != null) {
274           final Paint oldPaint = graphics.getPaint();
275 
276           if (fc1 == null) {
277             fc1 = new Color(fc2.getRed(), fc2.getGreen(), fc2.getBlue(), 0);
278           }
279           if (fc2 == null) {
280             fc2 = new Color(fc1.getRed(), fc1.getGreen(), fc1.getBlue(), 0);
281           }
282 
283           final float y = (float) context.getY();
284           final double x = context.getX();
285           final double w = context.getWidth();
286           graphics.setPaint(new GradientPaint(
287                   (float) (x + w * 0.33), y, fc1,
288                   (float) (x + w), y, fc2));
289           graphics.fill(shape);
290 
291           graphics.setPaint(oldPaint);
292         }
293       }
294 
295       // paint the header
296       if (context.labelCount() > 0) {
297         final Shape oldClip = graphics.getClip();
298         final Color oldColor = graphics.getColor();
299 
300         final YRectangle r = context.getLabel().getBox();
301         if (oldClip == null) {
302           final double x = context.getX();
303           final double w = context.getWidth();
304           final double minX = Math.min(r.x, x);
305           final double maxX = Math.max(r.x + r.width, x + w);
306           graphics.clip(new Rectangle2D.Double(minX, r.getY(), maxX - minX, r.getHeight()));
307         } else {
308           final Rectangle2D cb = oldClip.getBounds2D();
309           graphics.clip(new Rectangle2D.Double(cb.getX(), r.getY(), cb.getWidth(), r.getHeight()));
310         }
311         graphics.setColor(BACKGROUND);
312         graphics.fill(shape);
313 
314         graphics.setColor(oldColor);
315         graphics.setClip(oldClip);
316       }
317     }
318 
319     /**
320      * Paints the node's border.
321      * @param context the node context. Provides color and stroke for the node's
322      * border.
323      * @param graphics the graphics context to paint upon.
324      * @param shape the node's shape.
325      */
326     void paintShapeBorder(
327             final NodeRealizer context,
328             final Graphics2D graphics,
329             final Shape shape
330     ) {
331       final Color lc = context.getLineColor();
332       if (lc != null) {
333         final LineType lt = context.getLineType();
334         if (lt != null) {
335           final Color oldColor = graphics.getColor();
336           final Stroke oldStroke = graphics.getStroke();
337 
338           graphics.setColor(lc);
339           graphics.setStroke(lt);
340           graphics.draw(shape);
341 
342           graphics.setStroke(oldStroke);
343           graphics.setColor(oldColor);
344         }
345       }
346     }
347   }
348 
349 
350 
351   /**
352    * {@link GenericNodeRealizer} configuration name for group nodes.
353    */
354   private static final String CONFIGURATION_GROUP = "GroupingDemo_GROUP_NODE";
355   /**
356    * {@link GenericNodeRealizer} configuration name for nodes with a bevel
357    * border.
358    */
359   private static final String CONFIGURATION_BEVEL = "LevelOfDetailDemo#BevelNode";
360   /**
361    * {@link NodePort} configuration name.
362    */
363   private static final String CONFIGURATION_PORT = "LevelOfDetailDemo#Port";
364 
365   /**
366    * {@link y.view.Graph2DView#getPaintDetailThreshold() paintDetailThreshold}
367    * value used in the demo
368    */
369   private static final double PAINT_DETAIL_THRESHOLD = 0.5;
370 
371   /**
372    * Store this setting for demo use in the DemoBrowser to be able to reset it when
373    * stopping the demo or starting the next demo.
374    */
375   private boolean fractionMetricsForSizeCalculationEnabled;
376 
377   public LevelOfDetailDemo() {
378     this(null);
379   }
380 
381   public LevelOfDetailDemo( final String helpFilePath ) {
382     configureDefaultGroupNodeRealizers();
383     configureDefaultPorts();
384 
385 
386     view.setPaintDetailThreshold(PAINT_DETAIL_THRESHOLD);
387     view.getRenderingHints().put(
388             RenderingHints.KEY_FRACTIONALMETRICS,
389             RenderingHints.VALUE_FRACTIONALMETRICS_ON);
390     // Stores the value to be able to reset it when running the demo in the DemoBrowser,
391     // so this setting cannot effect other demos.
392     fractionMetricsForSizeCalculationEnabled = YLabel.isFractionMetricsForSizeCalculationEnabled();
393     YLabel.setFractionMetricsForSizeCalculationEnabled(true);
394 
395 
396     // create gui
397     final GridBagConstraints gbc = new GridBagConstraints();
398     final JPanel controls = new JPanel(new GridBagLayout());
399     gbc.anchor = GridBagConstraints.NORTHWEST;
400     gbc.fill = GridBagConstraints.NONE;
401     gbc.gridx = 0;
402     gbc.gridy = 0;
403     gbc.weightx = 0;
404     gbc.weighty = 0;
405     controls.add(createOptionPane(), gbc);
406     gbc.fill = GridBagConstraints.BOTH;
407     ++gbc.gridy;
408     gbc.weightx = 1;
409     gbc.weighty = 1;
410     controls.add(new JPanel(), gbc);
411 
412     contentPane.remove(view);
413     contentPane.add(new JSplitPane(
414             JSplitPane.HORIZONTAL_SPLIT,
415             controls,
416             view), BorderLayout.CENTER);
417 
418     addHelpPane(helpFilePath);
419 
420 
421     loadInitialGraph();
422   }
423 
424   protected void initialize() {
425     // create hierarchy manager with root graph before undo manager
426     // is created (for view actions) to ensure undo/redo works for grouped graphs
427     new HierarchyManager(view.getGraph2D());
428   }
429 
430   /**
431    * Cleans up.
432    * This method is called by the demo browser when the demo is stopped or another demo starts.
433    */
434   public void dispose() {
435     YLabel.setFractionMetricsForSizeCalculationEnabled(fractionMetricsForSizeCalculationEnabled);
436   }
437 
438   /**
439    * Loads an initial sample graph.
440    */
441   protected void loadInitialGraph() {
442     loadGraph("resource/LevelOfDetailDemo.graphml");
443   }
444 
445   /**
446    * Creates a control to enable/disable various rendering hints.
447    * @return a control to enable/disable various rendering hints.
448    */
449   private Component createOptionPane() {
450     final GridBagConstraints gbc = new GridBagConstraints();
451     final JPanel optionPane = new JPanel(new GridBagLayout());
452     gbc.anchor = GridBagConstraints.CENTER;
453     gbc.fill = GridBagConstraints.HORIZONTAL;
454     gbc.gridx = 0;
455     gbc.gridy = 0;
456     gbc.insets = new Insets(0, 0, 10, 0);
457     gbc.weightx = 1;
458     gbc.weighty = 0;
459 
460     // control for coarse grained level of detail
461     final StateEditor se = new StateEditor();
462     se.controls[0].setText("Depends on zoom");
463     se.controls[1].setText("Always detailed");
464     se.controls[2].setText("Always sloppy");
465     se.setAction(new AbstractAction("Level of Detail") {
466       public void actionPerformed( final ActionEvent e ) {
467         if (Boolean.TRUE.equals(se.getState())) {
468           // always use realizer's paint method
469           view.setPaintDetailThreshold(0);
470         } else if (Boolean.FALSE.equals(se.getState())) {
471           // always use realizer's paintSloppy method
472           view.setPaintDetailThreshold(Double.MAX_VALUE);
473         } else {
474           // if zoom < PAINT_DETAIL_THRESHOLD use paintSloppy
475           // else use paint
476           view.setPaintDetailThreshold(PAINT_DETAIL_THRESHOLD);
477         }
478         view.updateView();
479       }
480     });
481     optionPane.add(se, gbc);
482 
483     gbc.insets = new Insets(0, 0, 0, 0);
484     ++gbc.gridy;
485     optionPane.add(createOnOffDefaultControl(
486             YRenderingHints.KEY_GROUP_STATE_PAINTING,
487             YRenderingHints.VALUE_GROUP_STATE_PAINTING_ON,
488             YRenderingHints.VALUE_GROUP_STATE_PAINTING_OFF
489     ), gbc);
490     ++gbc.gridy;
491     optionPane.add(createOnOffDefaultControl(
492             YRenderingHints.KEY_EDGE_LABEL_PAINTING,
493             YRenderingHints.VALUE_EDGE_LABEL_PAINTING_ON,
494             YRenderingHints.VALUE_EDGE_LABEL_PAINTING_OFF
495     ), gbc);
496     ++gbc.gridy;
497     optionPane.add(createOnOffDefaultControl(
498             YRenderingHints.KEY_NODE_LABEL_PAINTING,
499             YRenderingHints.VALUE_NODE_LABEL_PAINTING_ON,
500             YRenderingHints.VALUE_NODE_LABEL_PAINTING_OFF
501     ), gbc);
502     ++gbc.gridy;
503     optionPane.add(createOnOffDefaultControl(
504             YRenderingHints.KEY_NODE_PORT_PAINTING,
505             YRenderingHints.VALUE_NODE_PORT_PAINTING_ON,
506             YRenderingHints.VALUE_NODE_PORT_PAINTING_OFF
507     ), gbc);
508 
509     ++gbc.gridy;
510     optionPane.add(createOnOffControl(
511             YRenderingHints.KEY_GRADIENT_PAINTING,
512             YRenderingHints.VALUE_GRADIENT_PAINTING_OFF
513     ), gbc);
514     ++gbc.gridy;
515     optionPane.add(createOnOffControl(
516             YRenderingHints.KEY_SELECTION_PAINTING,
517             YRenderingHints.VALUE_SELECTION_PAINTING_OFF
518     ), gbc);
519     ++gbc.gridy;
520     optionPane.add(createOnOffControl(
521             YRenderingHints.KEY_SHADOW_PAINTING,
522             YRenderingHints.VALUE_SHADOW_PAINTING_OFF
523     ), gbc);
524     ++gbc.gridy;
525     optionPane.add(createOnOffControl(
526             ShapeNodeRealizer.KEY_SLOPPY_RECT_PAINTING,
527             ShapeNodeRealizer.VALUE_SLOPPY_RECT_PAINTING_OFF
528     ), gbc);
529     ++gbc.gridy;
530     optionPane.add(createOnOffControl(
531         YRenderingHints.KEY_SLOPPY_POLYLINE_PAINTING,
532         YRenderingHints.VALUE_SLOPPY_POLYLINE_PAINTING_OFF
533     ), gbc);
534 
535     return optionPane;
536   }
537 
538   /**
539    * Creates a control to set one of three states for a rendering hint,
540    * <code>on</code>, <code>off</code>, or not used.
541    * @param key the rendering hint key to set.
542    * @param valueOn the appropriate <code>on</code> value for the rendering
543    * hint.
544    * @param valueOff the appropriate <code>off</code> value for the rendering
545    * hint.
546    * @return a control to set one of three states for a rendering hint,
547    * <code>on</code>, <code>off</code>, or not used.
548    */
549   private Component createOnOffDefaultControl(
550           final RenderingHints.Key key,
551           final Object valueOn,
552           final Object valueOff
553   ) {
554     String name = key.toString();
555     if (name.endsWith(" key")) {
556       name = name.substring(0, name.length() - 4);
557     }
558     if (name.endsWith(" enable")) {
559       name = name.substring(0, name.length() - 7);
560     }
561 
562     final StateEditor se = new StateEditor();
563     se.setAction(new AbstractAction(name) {
564       public void actionPerformed( final ActionEvent e ) {
565         final RenderingHints hints = view.getRenderingHints();
566         if (Boolean.TRUE.equals(se.getState())) {
567           hints.put(key, valueOn);
568         } else if (Boolean.FALSE.equals(se.getState())) {
569           hints.put(key, valueOff);
570         } else {
571           hints.remove(key);
572         }
573         view.updateView();
574       }
575     });
576     return se;
577   }
578 
579   /**
580    * Creates a control to set or unset a rendering hint.
581    *
582    * @param key the rendering hint key to set.
583    * @param value the value to be set for the rendering hint.
584    * @return a control to set or unset a rendering hint.
585    */
586   private JComponent createOnOffControl(
587           final RenderingHints.Key key,
588           final Object value
589   ) {
590     String name = key.toString();
591     if (name.endsWith(" key")) {
592       name = name.substring(0, name.length() - 4);
593     }
594     if (name.endsWith(" enable")) {
595       name = name.substring(0, name.length() - 7);
596     }
597 
598     final JCheckBox jcb = new JCheckBox();
599     jcb.setSelected(true);
600     jcb.setAction(new AbstractAction(name) {
601       public void actionPerformed( final ActionEvent e ) {
602         final RenderingHints hints = view.getRenderingHints();
603         if (jcb.isSelected()) {
604           hints.remove(key);
605         } else {
606           hints.put(key, value);
607         }
608         view.updateView();
609       }
610     });
611 
612     return jcb;
613   }
614 
615   /**
616    * Overwritten to register a configuration for nodes with bevel borders.
617    * @see #CONFIGURATION_BEVEL
618    */
619   protected void configureDefaultRealizers() {
620     super.configureDefaultRealizers();
621 
622     final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
623     final Map map = factory.createDefaultConfigurationMap();
624     final BevelNodePainter painter = new BevelNodePainter();
625     painter.setDrawShadow(true);
626     map.put(GenericNodeRealizer.Painter.class, painter);
627     factory.addConfiguration(CONFIGURATION_BEVEL, map);
628   }
629 
630   /**
631    * Configures the default group (and folder) nodes.
632    * @see #CONFIGURATION_GROUP
633    */
634   private void configureDefaultGroupNodeRealizers() {
635     //Create additional configuration for default group node realizers
636     final Map map = GenericGroupNodeRealizer.createDefaultConfigurationMap();
637 
638     final GroupNodePainter gnp = new GroupNodePainter(new HeaderNodePainter());
639     map.put(GenericNodeRealizer.Painter.class, new ShadowNodePainter(gnp));
640     map.put(GenericNodeRealizer.ContainsTest.class, gnp);
641     map.put(GenericNodeRealizer.GenericMouseInputEditorProvider.class, gnp);
642     map.put(GenericNodeRealizer.Initializer.class, gnp);
643 
644     final DefaultGenericAutoBoundsFeature abf = new DefaultGenericAutoBoundsFeature();
645     abf.setConsiderNodeLabelSize(true);
646     map.put(GenericGroupNodeRealizer.GenericAutoBoundsFeature.class, abf);
647     map.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, abf);
648     map.put(GenericNodeRealizer.LabelBoundsChangedHandler.class, abf);
649 
650     final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
651     factory.addConfiguration(CONFIGURATION_GROUP, map);
652 
653 
654     final GenericGroupNodeRealizer gnr = new GenericGroupNodeRealizer();
655 
656     //Register first, since this will also configure the node label
657     gnr.setConfiguration(CONFIGURATION_GROUP);
658 
659     //Nicer colors
660     gnr.setFillColor(new Color(228, 245, 255));
661     gnr.setFillColor2(Color.WHITE);
662     gnr.setLineColor(new Color(102, 102, 153));
663     final NodeLabel label = gnr.getLabel();
664     label.setBackgroundColor(null);
665     label.setTextColor(Color.BLACK);
666     label.setFontSize(15);
667 
668 
669     //Set default group and folder node realizers
670     final DefaultHierarchyGraphFactory hgf = (DefaultHierarchyGraphFactory)
671             view.getGraph2D().getHierarchyManager().getGraphFactory();
672 
673     hgf.setProxyNodeRealizerEnabled(true);
674 
675     hgf.setDefaultGroupNodeRealizer(gnr.createCopy());
676     hgf.setDefaultFolderNodeRealizer(gnr.createCopy());
677   }
678 
679   /**
680    * Registers a configuration for node ports that are represented by a
681    * small blue ball.
682    * @see #CONFIGURATION_PORT
683    */
684   private void configureDefaultPorts() {
685     // there is only one very simple dedicated port painter/port contains test
686     // implementation available (ShapePortConfiguration)
687     // luckily node painters/node contains tests can be reused
688 
689     // step 1: create a node configuration to be used as port configuration
690     final ShinyPlateNodePainter impl = new ShinyPlateNodePainter();
691     impl.setDrawShadow(false);
692     final HashMap nodeImpls = new HashMap();
693     nodeImpls.put(GenericNodeRealizer.ContainsTest.class, impl);
694     nodeImpls.put(GenericNodeRealizer.Painter.class, impl);
695 
696     // step 2: create an adapter for the node configuration
697     final PortConfigurationAdapter adapter =
698             new PortConfigurationAdapter(nodeImpls);
699     adapter.setFillColor(new Color(51, 102, 255));
700     adapter.setFillColor2(null);
701     adapter.setLineColor(new Color(0, 102, 255));
702 
703     // step 3: register the adapted node configuration as a port configuration
704     final ShapePortConfiguration boundsProvider = new ShapePortConfiguration();
705     boundsProvider.setSize(16, 16);
706 
707     final NodePort.Factory factory = NodePort.getFactory();
708     final Map portImpls = factory.createDefaultConfigurationMap();
709     portImpls.put(NodePort.ContainsTest.class, adapter);
710     final SelectionPortPainter painter = new SelectionPortPainter(adapter);
711     painter.setStyle(SelectionPortPainter.STYLE_SMOOTHED);
712     portImpls.put(NodePort.Painter.class, painter);
713     portImpls.put(NodePort.BoundsProvider.class, boundsProvider);
714     factory.addConfiguration(CONFIGURATION_PORT, portImpls);
715   }
716 
717   public static void main( String[] args ) {
718     EventQueue.invokeLater(new Runnable() {
719       public void run() {
720         Locale.setDefault(Locale.ENGLISH);
721         initLnF();
722         (new LevelOfDetailDemo("resource/levelofdetailhelp.html")).start();
723       }
724     });
725   }
726 
727 
728   /**
729    * An implementation of a tri-state component.
730    */
731   private static final class StateEditor extends JComponent {
732     private final AbstractButton[] controls;
733     private Action action;
734     private Boolean state;
735     private String name;
736 
737     StateEditor() {
738       setLayout(new GridLayout(3, 1));
739       name = "";
740       final ButtonGroup group = new ButtonGroup();
741       controls = new AbstractButton[3];
742       for (int i = 0; i < controls.length; ++i) {
743         controls[i] = new JRadioButton();
744         group.add(controls[i]);
745         add(controls[i]);
746       }
747       controls[0].setSelected(true);
748       controls[0].setAction(new AbstractAction("Default") {
749         public void actionPerformed( final ActionEvent e ) {
750           state = null;
751           fireActionPerformed();
752         }
753       });
754       controls[1].setAction(new AbstractAction("On") {
755         public void actionPerformed( final ActionEvent e ) {
756           state = Boolean.TRUE;
757           fireActionPerformed();
758         }
759       });
760       controls[2].setAction(new AbstractAction("Off") {
761         public void actionPerformed( final ActionEvent e ) {
762           state = Boolean.FALSE;
763           fireActionPerformed();
764         }
765       });
766       setBorder(BorderFactory.createTitledBorder(""));
767     }
768 
769     /**
770      * Returns the state of the component.
771      * @return either {@link Boolean#TRUE}, {@link Boolean#FALSE}, or
772      * <code>null</code>.
773      */
774     public Boolean getState() {
775       return state;
776     }
777 
778     /**
779      * Sets the state of the component.
780      * @param state either {@link Boolean#TRUE}, {@link Boolean#FALSE}, or
781      * <code>null</code>.
782      */
783     public void setState( final Boolean state ) {
784       if (state == null) {
785         if (this.state != state) {
786           controls[0].setSelected(true);
787         }
788       } else if (Boolean.TRUE == state) {
789         if (this.state != state) {
790           controls[1].setSelected(true);
791         }
792       } else if (Boolean.FALSE == state) {
793         if (this.state != state) {
794           controls[2].setSelected(true);
795         }
796       }
797     }
798 
799     /**
800      * Returns the currently set <code>Action</code> that is triggered upon
801      * state changes or <code>null</code> if no <code>Action</code> is set.
802      * @return the currently set <code>Action</code> that is triggered upon
803      * state changes or <code>null</code> if no <code>Action</code> is set.
804      */
805     public Action getAction() {
806       return action;
807     }
808 
809     /**
810      * Sets the <code>Action</code> that is triggered upon state changes.
811      * @param action the <code>Action</code> that is triggered upon state
812      * changes.
813      */
814     public void setAction( final Action action ) {
815       this.action = action;
816       String name = "";
817       if (action != null) {
818         final Object value = action.getValue(Action.NAME);
819         if (value instanceof String) {
820           name = (String) value;
821         }
822       }
823       setBorder(BorderFactory.createTitledBorder(name));
824       this.name = name;
825     }
826 
827     private void fireActionPerformed() {
828       final Action action = this.action;
829       if (action != null) {
830         action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, name));
831       }
832     }
833   }
834 }
835