1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.realizer;
15  
16  import demo.view.DemoBase;
17  
18  import y.base.Edge;
19  import y.base.EdgeMap;
20  import y.base.Node;
21  import y.geom.YInsets;
22  import y.layout.LayoutOrientation;
23  import y.layout.PortConstraint;
24  import y.layout.PortConstraintKeys;
25  import y.layout.hierarchic.IncrementalHierarchicLayouter;
26  import y.layout.hierarchic.incremental.LayerConstraintFactory;
27  import y.view.AbstractCustomNodePainter;
28  import y.view.EditMode;
29  import y.view.GenericNodeRealizer;
30  import y.view.Graph2D;
31  import y.view.Graph2DLayoutExecutor;
32  import y.view.LineType;
33  import y.view.MultiplexingNodeEditor;
34  import y.view.NodeLabel;
35  import y.view.NodeRealizer;
36  import y.view.ShapeNodePainter;
37  import y.view.YRenderingHints;
38  import y.view.hierarchy.HierarchyManager;
39  import y.view.tabular.TableGroupNodeRealizer;
40  import y.view.tabular.TableGroupNodeRealizer.Column;
41  import y.view.tabular.TableGroupNodeRealizer.Row;
42  import y.view.tabular.TableGroupNodeRealizer.Table;
43  import y.view.tabular.TableNodePainter;
44  import y.view.tabular.TableSelectionEditor;
45  import y.view.tabular.TableStyle;
46  
47  import java.awt.Color;
48  import java.awt.EventQueue;
49  import java.awt.GradientPaint;
50  import java.awt.Graphics2D;
51  import java.awt.Paint;
52  import java.awt.Shape;
53  import java.awt.Stroke;
54  import java.awt.event.ActionEvent;
55  import java.awt.geom.AffineTransform;
56  import java.awt.geom.Rectangle2D;
57  import java.util.HashMap;
58  import java.util.Iterator;
59  import java.util.List;
60  import java.util.Locale;
61  import java.util.Map;
62  import javax.swing.AbstractAction;
63  import javax.swing.JMenu;
64  import javax.swing.JMenuBar;
65  
66  /**
67   * Demonstrates different visual styles for table groups and their content.
68   *
69   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/tabular_data_table_structure.html#tabular_data_rows_columns">Section Table Structure Model</a> in the yFiles for Java Developer's Guide
70   */
71  public class TableStyleDemo extends DemoBase {
72    private static final String CONFIGURATION_POOL_GRADIENT = "POOL_GRADIENT";
73    private static final String CONFIGURATION_POOL_ALTERNATING = "POOL_ALTERNATING";
74    private static final String CONFIGURATION_POOL_GENERIC = "POOL_GENERIC";
75    private static final String CONFIGURATION_POOL_BPMN_STYLE = "POOL_BPMN_STYLE";
76    private static final String CONFIGURATION_GRADIENT_RECT = "GRADIENT_RECT";
77    private static final String CONFIGURATION_GRADIENT_ROUNDRECT = "GRADIENT_ROUNDRECT";
78    private static final String CONFIGURATION_GRADIENT_DIAMOND = "GRADIENT_DIAMOND";
79    private static final String CONFIGURATION_GRADIENT_ELLIPSE = "GRADIENT_ELLIPSE";
80    private static final String CONFIGURATION_SIMPLE_ROUNDRECT = "SIMPLE_ROUNDRECT";
81    private static final String CONFIGURATION_SIMPLE_DIAMOND = "SIMPLE_DIAMOND";
82    private static final String CONFIGURATION_SIMPLE_ELLIPSE = "SIMPLE_ELLIPSE";
83  
84    private static final Color MAGENTA = new Color(253, 0, 127);
85    private static final Color ORANGE = new Color(249, 134, 5);
86    private static final Color DARK_GRAY = new Color(132, 131, 129);
87    private static final Color GREEN = new Color(156, 210, 60);
88    private static final Color PASTEL_YELLOW = new Color(254, 254, 212);
89    private static final Color PASTEL_GREEN = new Color(212, 254, 228);
90    private static final Color PASTEL_BLUE = new Color(212, 228, 254);
91    private static final Color LIGHT_BLUE = new Color(161, 188, 255);
92    private static final Color DARK_GREEN = new Color(98, 167, 22);
93    private static final Color BLOOD_RED = new Color(153, 0, 0);
94  
95    static {
96      initConfigurations();
97    }
98  
99    public TableStyleDemo() {
100     createBpmnStyleSample(view.getGraph2D());
101     view.fitContent();
102   }
103 
104   protected void initialize() {
105     new HierarchyManager(view.getGraph2D());
106   }
107 
108   /**
109    * Creates an almost view-only edit mode. Almost view-only because
110    * nodes may be selected and table nodes, columns, and rows may be resized.
111    */
112   protected EditMode createEditMode() {
113     final EditMode editMode = new EditMode();
114     editMode.allowBendCreation(false);
115     editMode.allowEdgeCreation(false);
116     editMode.allowLabelSelection(false);
117     editMode.allowMoveLabels(false);
118     editMode.allowMovePorts(false);
119     editMode.allowMoveSelection(false);
120     editMode.allowMoving(false);
121     editMode.allowMovingWithPopup(false);
122     editMode.allowNodeCreation(false);
123     editMode.allowNodeEditing(false);
124     editMode.allowResizeNodes(false);
125 
126     // activate node specific user interaction
127     // e.g. TableGroupNodeRealizer usually is configured to recognize mouse
128     // gestures for selecting and resizing tables, columns, and rows as well
129     // as reordering columns and rows
130     editMode.getMouseInputMode().setNodeSearchingEnabled(true);
131     return editMode;
132   }
133 
134   /**
135    * Creates a sample graph depicting a table node that uses
136    * {@link demo.view.realizer.TableStyleDemo.GradientRowPainter} as a custom
137    * row painter.
138    * @param graph   the graph to configure.
139    */
140   private void createGradientSample( final Graph2D graph ) {
141     graph.clear();
142 
143 
144     // create the realizer for the table node
145     final TableGroupNodeRealizer tgnr = new TableGroupNodeRealizer();
146     tgnr.setConfiguration(CONFIGURATION_POOL_GRADIENT);
147     tgnr.setLocation(0, 0);
148     tgnr.setAutoResize(true);
149 
150     tgnr.setFillColor(Color.LIGHT_GRAY);
151     // this color is used by GradientRowPainter together with a row specific
152     // color to create a gradient fill for each row
153     tgnr.setFillColor2(Color.WHITE);
154 
155     tgnr.setDefaultColumnInsets(new YInsets(0, 5, 0, 5));
156     tgnr.setDefaultColumnWidth(400);
157     tgnr.setDefaultRowHeight(100);
158     tgnr.setDefaultRowInsets(new YInsets(5, 20, 5, 0));
159 
160 
161     final Table dt = tgnr.getTable();
162 
163     // create one labeled row for each of the four colors
164     final Color[] colors = {GREEN, DARK_GRAY, ORANGE, MAGENTA};
165     final double rowSizeAdjustment = 10;
166     final Map row2color = new HashMap();
167     for (int i = 0; i < colors.length; ++i) {
168       final Row row;
169       if (i == 0) {
170         row = dt.getRow(0);
171       } else {
172         row = dt.addRow();
173       }
174       row2color.put(row, colors[i]);
175 
176       final NodeLabel label = tgnr.createNodeLabel();
177       label.setText("Lane " + (i + 1));
178       final double minHeight = label.getWidth() + rowSizeAdjustment;
179       row.setMinimumHeight(minHeight);
180 
181       // associate the label to the row
182       // the ratio value of 0 means the label will be left-aligned (regarding
183       // the row) and rotated 90 degress counter clockwise
184       tgnr.configureRowLabel(label, row, true, 0);
185 
186       // row labels are normal node labels and have to be explicitly added
187       // to the realizer as usual
188       tgnr.addLabel(label);
189     }
190 
191     // sets the style property that is used by GradientRowPainter to determine
192     // the color that defines the gradient fill for each row
193     tgnr.setStyleProperty(
194             GradientRowPainter.STYLE_ROW_COLOR_MAP,
195             new GradientRowPainter.RowColorMap() {
196       public Color getColor( final Row row ) {
197         return (Color) row2color.get(row);
198       }
199     });
200 
201     // ensure that the table has the correct (sufficiently large) size
202     tgnr.updateTableBounds();
203 
204 
205     final HierarchyManager hm = HierarchyManager.getInstance(graph);
206 
207     // create a group node ..
208     final Node pool = hm.createGroupNode(graph);
209     // .. and assign it the previously created table realizer
210     graph.setRealizer(pool, tgnr);
211 
212     // prototype realizer for rectangular child nodes
213     final GenericNodeRealizer rectangle = new GenericNodeRealizer();
214     rectangle.setConfiguration(CONFIGURATION_GRADIENT_RECT);
215     rectangle.setSize(80, 60);
216     rectangle.setCenter(tgnr.getCenterX(), tgnr.getCenterY());
217     rectangle.setFillColor(Color.WHITE);
218 
219     // prototype realizer for rectangular child nodes with rounded corners
220     final GenericNodeRealizer roundRect = new GenericNodeRealizer();
221     roundRect.setConfiguration(CONFIGURATION_GRADIENT_ROUNDRECT);
222     roundRect.setSize(80, 60);
223     roundRect.setCenter(tgnr.getCenterX(), tgnr.getCenterY());
224     roundRect.setFillColor(Color.WHITE);
225     roundRect.setFillColor2(PASTEL_BLUE);
226 
227     // prototype realizer for circular child nodes
228     final GenericNodeRealizer ellipse = new GenericNodeRealizer();
229     ellipse.setConfiguration(CONFIGURATION_GRADIENT_ELLIPSE);
230     ellipse.setSize(60, 60);
231     ellipse.setCenter(tgnr.getCenterX(), tgnr.getCenterY());
232     ellipse.setFillColor(Color.WHITE);
233     ellipse.setFillColor2(PASTEL_YELLOW);
234 
235     // prototype realizer for diamond-shaped child nodes
236     final GenericNodeRealizer diamond = new GenericNodeRealizer();
237     diamond.setConfiguration(CONFIGURATION_GRADIENT_DIAMOND);
238     diamond.setSize(60, 60);
239     diamond.setCenter(tgnr.getCenterX(), tgnr.getCenterY());
240     diamond.setFillColor(Color.WHITE);
241     diamond.setFillColor2(PASTEL_GREEN);
242 
243 
244     // create some nodes with different realizers
245     final Node[] nodes = new Node[22];
246     for (int i = 0; i < nodes.length; ++i) {
247       if (i < 5 ) {
248         if (i % 2 == 0) {
249           nodes[i] = graph.createNode(ellipse.createCopy());
250         } else {
251           nodes[i] = graph.createNode(rectangle.createCopy());
252         }
253       } else if (i == 6 || i == 11 || i == 12) {
254         nodes[i] = graph.createNode(diamond.createCopy());
255       } else if (i == 5 || i == 19 || (i > 8 && i < 18)) {
256         nodes[i] = graph.createNode(roundRect.createCopy());
257       } else {
258         nodes[i] = graph.createNode(rectangle.createCopy());
259       }
260     }
261 
262 
263     // label the recently created nodes and assign them to the previously
264     // created group node
265     for (int i = 0; i < nodes.length; ++i) {
266       graph.getRealizer(nodes[i]).setLabelText(Integer.toString(i + 1));
267 
268       // important: assign the node to the table group
269       hm.setParentNode(nodes[i], pool);
270 
271       // assign the child nodes to different rows
272       if (i < 5) {
273         dt.moveToRow(nodes[i], dt.getRow(0));
274       } else if (i < 9) {
275         dt.moveToRow(nodes[i], dt.getRow(1));
276       } else if (i < 18) {
277         dt.moveToRow(nodes[i], dt.getRow(2));
278       } else {
279         dt.moveToRow(nodes[i], dt.getRow(3));
280       }
281     }
282 
283 
284     // create some edges
285     final Edge[] edges = {
286       graph.createEdge(nodes[0], nodes[5]),
287       graph.createEdge(nodes[2], nodes[14]),
288       graph.createEdge(nodes[4], nodes[17]),
289 
290       graph.createEdge(nodes[5], nodes[6]),
291       graph.createEdge(nodes[6], nodes[9]),
292       graph.createEdge(nodes[6], nodes[10]),
293       graph.createEdge(nodes[6], nodes[11]),
294 
295       graph.createEdge(nodes[9], nodes[12]),
296       graph.createEdge(nodes[10], nodes[12]),
297       graph.createEdge(nodes[11], nodes[12]),
298       graph.createEdge(nodes[12], nodes[19]),
299       graph.createEdge(nodes[13], nodes[19]),
300 
301       graph.createEdge(nodes[19], nodes[20]),
302       graph.createEdge(nodes[19], nodes[21]),
303     };
304 //    for (int i = 0; i < edges.length; ++i) {
305 //      graph.getRealizer(edges[i]).setLabelText(Integer.toString(i));
306 //    }
307 
308 
309     // setup port constraints for diamond-shaped child nodes
310 
311     // create the corresponding port constraints ...
312     final EdgeMap srcPc = graph.createEdgeMap();
313     srcPc.set(edges[4], PortConstraint.create(PortConstraint.WEST));
314     srcPc.set(edges[5], PortConstraint.create(PortConstraint.EAST));
315     final EdgeMap tgtPc = graph.createEdgeMap();
316     tgtPc.set(edges[7], PortConstraint.create(PortConstraint.WEST));
317     tgtPc.set(edges[8], PortConstraint.create(PortConstraint.EAST));
318 
319     // ... and register the port constraints
320     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, srcPc);
321     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, tgtPc);
322 
323 
324     try {
325       layout(graph, true);
326     } finally {
327 
328       graph.removeDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
329       graph.removeDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
330       graph.disposeEdgeMap(tgtPc);
331       graph.disposeEdgeMap(srcPc);
332     }
333   }
334 
335   /**
336    * Creates a sample graph depicting a table node that uses alternating
337    * colors to paint its columns.
338    * @param graph   the graph to configure.
339    */
340   private void createAlternatingSample( final Graph2D graph ) {
341     graph.clear();
342 
343 
344     // create the realizer for the table node
345     final TableGroupNodeRealizer tgnr = new TableGroupNodeRealizer();
346     tgnr.setConfiguration(CONFIGURATION_POOL_ALTERNATING);
347     tgnr.setLocation(0, 0);
348     tgnr.setAutoResize(true);
349 
350     tgnr.setDefaultColumnInsets(new YInsets(20, 5, 0, 5));
351     tgnr.setDefaultColumnWidth(100);
352     tgnr.setDefaultRowHeight(100);
353     tgnr.setDefaultRowInsets(new YInsets(30, 0, 10, 0));
354 
355 
356     final Table dt = tgnr.getTable();
357     dt.setInsets(new YInsets(30, 5, 5, 5));
358 
359     // create couple of columns in the table model
360     final List cols = dt.getColumns();
361     dt.addColumn();
362     dt.addColumn();
363     dt.addColumn();
364     final Column[] columns = new Column[cols.size()];
365     cols.toArray(columns);
366 
367     final Row row0 = dt.getRow(0);
368 
369 
370     final NodeLabel label = tgnr.getLabel();
371     label.setText("Pool");
372     label.setPosition(NodeLabel.TOP);
373 
374     // configure the columns:
375     //  - assign each column a label
376     //  - set a suitable minimum size (width) to each column
377     final double columnSizeAdjustment = 10;
378     int i = 1;
379     for (Iterator it = cols.iterator(); it.hasNext(); ++i) {
380       final Column column = (Column) it.next();
381       final NodeLabel columnLabel = tgnr.createNodeLabel();
382       columnLabel.setText("Lane " + i);
383       final double minWidth = columnLabel.getWidth() + columnSizeAdjustment;
384       column.setMinimumWidth(minWidth);
385 
386       // associate the label to the column
387       tgnr.configureColumnLabel(columnLabel, column, true, 0);
388 
389       // column labels are normal node labels and have to be explicitly added
390       // to the realizer as usual
391       tgnr.addLabel(columnLabel);
392     }
393 
394     // ensure that the table has the correct (sufficiently large) size
395     tgnr.updateTableBounds();
396 
397 
398     final HierarchyManager hm = HierarchyManager.getInstance(graph);
399 
400 
401     // create a group node ..
402     final Node pool = hm.createGroupNode(graph);
403     // .. and assign it the previously created table realizer
404     graph.setRealizer(pool, tgnr);
405 
406 
407     // prototype realizer for child nodes
408     final GenericNodeRealizer prototype = new GenericNodeRealizer();
409     prototype.setConfiguration(CONFIGURATION_GRADIENT_RECT);
410     prototype.setSize(90, 60);
411     prototype.setCenter(
412             columns[0].calculateBounds().getCenterX(),
413             row0.calculateBounds().getCenterY());
414     prototype.setFillColor(Color.WHITE);
415     prototype.setFillColor2(PASTEL_BLUE);
416 
417     // create a couple of child nodes with different shapes
418     final String[] configurations = {
419             CONFIGURATION_GRADIENT_RECT,
420             CONFIGURATION_GRADIENT_DIAMOND,
421             CONFIGURATION_GRADIENT_DIAMOND,
422             CONFIGURATION_GRADIENT_DIAMOND,
423             CONFIGURATION_GRADIENT_RECT,
424             CONFIGURATION_GRADIENT_RECT,
425             CONFIGURATION_GRADIENT_RECT,
426             CONFIGURATION_GRADIENT_RECT,
427     };
428     final Node[] nodes = new Node[configurations.length];
429     for (int j = 0; j < nodes.length; ++j) {
430       final GenericNodeRealizer nr = new GenericNodeRealizer(prototype);
431       nr.setConfiguration(configurations[j]);
432       nr.setLabelText(Integer.toString(j + 1));
433       nodes[j] = graph.createNode(nr);
434 
435       // important: assign the node to the table group
436       hm.setParentNode(nodes[j], pool);
437 
438       // move the new node into (the first and only row of the) the table
439       dt.moveToRow(nodes[j], row0);
440     }
441 
442     // distribute the child nodes over the table
443     dt.moveToColumn(nodes[0], columns[3]);
444     dt.moveToColumn(nodes[1], columns[2]);
445     dt.moveToColumn(nodes[2], columns[1]);
446     dt.moveToColumn(nodes[4], columns[2]);
447     dt.moveToColumn(nodes[7], columns[3]);
448 
449 
450     // couple of edges
451     final Edge[] edges = {
452       graph.createEdge(nodes[0], nodes[1]),
453       graph.createEdge(nodes[1], nodes[0]),
454       graph.createEdge(nodes[1], nodes[2]),
455       graph.createEdge(nodes[2], nodes[3]),
456       graph.createEdge(nodes[2], nodes[4]),
457       graph.createEdge(nodes[3], nodes[5]),
458       graph.createEdge(nodes[3], nodes[6]),
459       graph.createEdge(nodes[4], nodes[6]),
460       graph.createEdge(nodes[4], nodes[7]),
461       graph.createEdge(nodes[6], nodes[7]),
462     };
463 //    for (int j = 0; j < edges.length; ++j) {
464 //      graph.getRealizer(edges[j]).setLabelText(Integer.toString(j));
465 //    }
466 
467 
468     // setup port constraints for diamond-shaped child nodes
469 
470     // create the corresponding port constraints ...
471     final EdgeMap srcPc = graph.createEdgeMap();
472     srcPc.set(edges[1], PortConstraint.create(PortConstraint.EAST));
473     srcPc.set(edges[3], PortConstraint.create(PortConstraint.WEST));
474     srcPc.set(edges[4], PortConstraint.create(PortConstraint.EAST));
475     srcPc.set(edges[5], PortConstraint.create(PortConstraint.WEST));
476     srcPc.set(edges[6], PortConstraint.create(PortConstraint.EAST));
477     final EdgeMap tgtPc = graph.createEdgeMap();
478     tgtPc.set(edges[0], PortConstraint.create(PortConstraint.NORTH));
479 
480     // ... and register the port constraints
481     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, srcPc);
482     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, tgtPc);
483 
484 
485     try {
486       layout(graph, true);
487     } finally {
488 
489       graph.removeDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
490       graph.removeDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
491       graph.disposeEdgeMap(tgtPc);
492       graph.disposeEdgeMap(srcPc);
493     }
494   }
495 
496   /**
497    * Creates a sample graph depicting a table node that uses the default
498    * painter configuration with custom row styles.
499    * @param graph   the graph to configure.
500    */
501   private void createGenericSample( final Graph2D graph ) {
502     graph.clear();
503 
504 
505     final TableGroupNodeRealizer tgnr = new TableGroupNodeRealizer();
506     tgnr.setConfiguration(CONFIGURATION_POOL_GENERIC);
507     tgnr.setLocation(0, 0);
508     tgnr.setAutoResize(true);
509 
510     // file color 2 is used by RowStyle as the row fill color
511     tgnr.setFillColor2(LIGHT_BLUE);
512     // register RowStyle as the style to be used when painting rows
513     tgnr.setStyleProperty(TableNodePainter.ROW_STYLE_ID, new RowStyle(false));
514     tgnr.setStyleProperty(TableNodePainter.ROW_SELECTION_STYLE_ID, new RowStyle(true));
515 
516     tgnr.setDefaultColumnInsets(new YInsets(0, 5, 0, 10));
517     tgnr.setDefaultColumnWidth(100);
518     tgnr.setDefaultRowInsets(new YInsets(5, 25, 5, 0));
519     tgnr.setDefaultRowHeight(80);
520 
521     final Table table = tgnr.getTable();
522     table.setInsets(new YInsets(10, 25, 10, 10));
523 
524     final Column column0 = table.getColumn(0);
525     final Row row0 = table.getRow(0);
526     final Row row1 = table.addRow();
527     table.addRow();
528 
529     final NodeLabel label = tgnr.getLabel();
530     label.setText("Pool");
531     label.setPosition(NodeLabel.LEFT);
532     label.setRotationAngle(270);
533 
534     // configure the rows:
535     //  - assign each row a label
536     //  - set a suitable minimum size (height) to each row
537     final double rowSizeAdjustment = 10;
538     int i = 1;
539     for (Iterator it = table.getRows().iterator(); it.hasNext(); ++i) {
540       final Row row = (Row) it.next();
541       final NodeLabel rowLabel = tgnr.createNodeLabel();
542       rowLabel.setText("Lane " + i);
543       final double minHeight = rowLabel.getWidth() + rowSizeAdjustment;
544       row.setMinimumHeight(minHeight);
545 
546       // associate the label to the row
547       // the ratio value of 0 means the label will be left-aligned (regarding
548       // the row) and rotated 90 degress counter clockwise
549       tgnr.configureRowLabel(rowLabel, row, true, 0);
550 
551       // row labels are normal node labels and have to be explicitly added
552       // to the realizer as usual
553       tgnr.addLabel(rowLabel);
554     }
555 
556     // ensure that the table has the correct (sufficiently large) size
557     tgnr.updateTableBounds();
558 
559 
560     final HierarchyManager hm = HierarchyManager.getInstance(graph);
561 
562 
563     // create a group node ..
564     final Node pool = hm.createGroupNode(graph);
565     // .. and assign it the previously created table realizer
566     graph.setRealizer(pool, tgnr);
567 
568 
569     // prototype realizer for child nodes
570     final GenericNodeRealizer prototype = new GenericNodeRealizer();
571     prototype.setConfiguration(CONFIGURATION_GRADIENT_RECT);
572     prototype.setSize(90, 60);
573     prototype.setCenter(
574             column0.calculateBounds().getCenterX(),
575             row0.calculateBounds().getCenterY());
576     prototype.setFillColor(Color.WHITE);
577     prototype.setFillColor2(PASTEL_BLUE);
578 
579     // create a couple of child nodes
580     final Node[] nodes = new Node[6];
581     for (int j = 0; j < nodes.length; ++j) {
582       final NodeRealizer nr = prototype.createCopy();
583       nr.setLabelText(Integer.toString(j + 1));
584       nodes[j] = graph.createNode(nr);
585 
586       // important: assign the node to the table group
587       hm.setParentNode(nodes[j], pool);
588 
589       // move the row into the table's second row
590       table.moveToRow(nodes[j], row1);
591     }
592     // move the first and last child to different rows
593     table.moveToRow(nodes[0], row0);
594     table.moveToRow(nodes[5], table.getRow(table.rowCount() - 1));
595 
596 
597     // change the shape of one of the child nodes
598     ((GenericNodeRealizer) graph.getRealizer(nodes[2]))
599             .setConfiguration(CONFIGURATION_GRADIENT_DIAMOND);
600 
601     // create a couple of edges
602     final Edge[] edges = {
603       graph.createEdge(nodes[0], nodes[1]),
604       graph.createEdge(nodes[1], nodes[2]),
605       graph.createEdge(nodes[2], nodes[3]),
606       graph.createEdge(nodes[2], nodes[4]),
607       graph.createEdge(nodes[4], nodes[5]),
608     };
609 //    for (int j = 0; j < edges.length; ++j) {
610 //      graph.getRealizer(edges[j]).setLabelText(Integer.toString(j));
611 //    }
612 
613 
614     // setup port constraints for diamond-shaped child nodes
615 
616     // create the corresponding port constraints ...
617     final EdgeMap srcPc = graph.createEdgeMap();
618     srcPc.set(edges[2], PortConstraint.create(PortConstraint.NORTH));
619     srcPc.set(edges[3], PortConstraint.create(PortConstraint.EAST));
620 
621     // ... and register the port constraints
622     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, srcPc);
623 
624     try {
625       layout(graph, false);
626     } finally {
627       graph.removeDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
628       graph.disposeEdgeMap(srcPc);
629     }
630   }
631 
632   /**
633    * Creates a sample graph depicting a BPMN style diagram.
634    * @param graph   the graph to configure.
635    */
636   private void createBpmnStyleSample( final Graph2D graph ) {
637     graph.clear();
638 
639 
640     final TableGroupNodeRealizer tgnr = new TableGroupNodeRealizer();
641     tgnr.setConfiguration(CONFIGURATION_POOL_BPMN_STYLE);
642     tgnr.setAutoResize(true);
643     tgnr.setDefaultColumnInsets(new YInsets(0, 5, 0, 5));
644     tgnr.setDefaultRowInsets(new YInsets(0, 25, 0, 0));
645 
646     // set up the table
647     final Table table = tgnr.getTable();
648     table.setInsets(new YInsets(20, 25, 5, 10));
649     table.addColumn();
650     table.addColumn();
651     table.addRow();
652     table.addRow();
653 
654     final NodeLabel label = tgnr.getLabel();
655     label.setText("Pool");
656     label.setPosition(NodeLabel.LEFT);
657     label.setRotationAngle(270);
658 
659     final YInsets insets = table.getInsets();
660     final double columnSizeAdjustment = 10 + 2 * insets.right;
661     final double rowSizeAdjustment = 10;
662 
663     // configure the rows:
664     //  - assign each row a label
665     //  - set a suitable minimum size (height) to each row
666     int r = 1;
667     for (Iterator it = table.getRows().iterator(); it.hasNext(); ++r) {
668       final Row row = (Row) it.next();
669       final NodeLabel rowLabel = tgnr.createNodeLabel();
670       rowLabel.setText("Lane " + r);
671       final double minHeight = rowLabel.getWidth() + rowSizeAdjustment;
672       row.setHeight(minHeight);
673       row.setMinimumHeight(minHeight);
674 
675       // associate the label to the row
676       // the ratio value of 0 means the label will be left-aligned (regarding
677       // the row) and rotated 90 degress counter clockwise
678       tgnr.configureRowLabel(rowLabel, row, true, 0);
679 
680       // row labels are normal node labels and have to be explicitly added
681       // to the realizer as usual
682       tgnr.addLabel(rowLabel);
683     }
684 
685     // configure the columns:
686     //  - assign each row a label
687     //  - set a suitable minimum size (width) to each column
688     int c = 1;
689     for (Iterator it = table.getColumns().iterator(); it.hasNext(); ++c) {
690       final Column column = (Column) it.next();
691       final NodeLabel columnLabel = tgnr.createNodeLabel();
692       columnLabel.setText("Milestone " + c);
693       final double minWidth = columnLabel.getWidth() + columnSizeAdjustment;
694       column.setWidth(minWidth);
695       column.setMinimumWidth(minWidth);
696 
697       // associate the label to the column
698       tgnr.configureColumnLabel(columnLabel, column, false, 0);
699 
700       // column labels are normal node labels and have to be explicitly added
701       // to the realizer as usual
702       tgnr.addLabel(columnLabel);
703     }
704 
705     // ensure that the table has the correct (sufficiently large) size
706     tgnr.updateTableBounds();
707 
708 
709     final HierarchyManager hm = HierarchyManager.getInstance(graph);
710 
711 
712     // create a group node ..
713     final Node pool = hm.createGroupNode(graph);
714     // .. and assign it the previously created table realizer
715     graph.setRealizer(pool, tgnr);
716 
717 
718     // prototype realizer for rectangular child nodes
719     final GenericNodeRealizer roundRect = new GenericNodeRealizer();
720     roundRect.setConfiguration(CONFIGURATION_SIMPLE_ROUNDRECT);
721     roundRect.setSize(80, 60);
722     roundRect.setCenter(tgnr.getCenterX(), tgnr.getCenterY());
723     roundRect.setFillColor(Color.WHITE);
724     roundRect.setLineColor(new Color(3, 104, 154));
725 
726     // prototype realizer for circular child nodes
727     final GenericNodeRealizer ellipse = new GenericNodeRealizer();
728     ellipse.setConfiguration(CONFIGURATION_SIMPLE_ELLIPSE);
729     ellipse.setSize(30, 30);
730     ellipse.setCenter(tgnr.getCenterX(), tgnr.getCenterY());
731     ellipse.setFillColor(Color.WHITE);
732     ellipse.setLineColor(new Color(198, 194, 139));
733 
734     // prototype realizer for diamond-shaped child nodes
735     final GenericNodeRealizer diamond = new GenericNodeRealizer();
736     diamond.setConfiguration(CONFIGURATION_SIMPLE_DIAMOND);
737     diamond.setSize(30, 30);
738     diamond.setCenter(tgnr.getCenterX(), tgnr.getCenterY());
739     diamond.setFillColor(Color.WHITE);
740     diamond.setLineColor(new Color(166, 166, 29));
741 
742 
743     // create several child nodes using the previously created prototype
744     // realizers and distribute the nodes over the table
745     final Node[] nodes = new Node[15];
746     for (int i = 0; i < nodes.length; ++i) {
747       if (i < 5) {
748         nodes[i] = graph.createNode(ellipse.createCopy());
749       } else if (i < 11) {
750         nodes[i] = graph.createNode(roundRect.createCopy());
751       } else {
752         nodes[i] = graph.createNode(diamond.createCopy());
753       }
754 
755       // important: assign the node to the table group
756       hm.setParentNode(nodes[i], pool);
757 
758       // "assign" the node to a column
759       if (i == 4 || i == 9 || i == 10 || i == 14) {
760         table.moveToColumn(nodes[i], table.getColumn(2));
761       } else if ((0 < i && i < 4) || i == 7 || i == 8 || i == 13) {
762         table.moveToColumn(nodes[i], table.getColumn(1));
763       } else {
764         table.moveToColumn(nodes[i], table.getColumn(0));
765       }
766 
767       // "assign" the node to a row
768       if (i == 10) {
769         table.moveToRow(nodes[i], table.getRow(2));
770       } else if (i == 4 || i == 8 || i == 14) {
771         table.moveToRow(nodes[i], table.getRow(1));
772       } else {
773         table.moveToRow(nodes[i], table.getRow(0));
774       }
775     }
776 //    for (int i = 0; i < nodes.length; ++i) {
777 //      graph.getRealizer(nodes[i]).setLabelText(Integer.toString(i));
778 //    }
779 
780 
781     // create some edges
782     final Edge[] edges = {
783       graph.createEdge(nodes[0], nodes[5]),
784       graph.createEdge(nodes[2], nodes[7]),
785       graph.createEdge(nodes[3], nodes[8]),
786       graph.createEdge(nodes[5], nodes[6]),
787       graph.createEdge(nodes[6], nodes[11]),
788       graph.createEdge(nodes[7], nodes[13]),
789       graph.createEdge(nodes[8], nodes[14]),
790       graph.createEdge(nodes[9], nodes[4]),
791       graph.createEdge(nodes[10], nodes[4]),
792       graph.createEdge(nodes[11], nodes[1]),
793       graph.createEdge(nodes[11], nodes[12]),
794       graph.createEdge(nodes[12], nodes[2]),
795       graph.createEdge(nodes[12], nodes[3]),
796       graph.createEdge(nodes[13], nodes[1]),
797       graph.createEdge(nodes[13], nodes[12]),
798       graph.createEdge(nodes[14], nodes[9]),
799       graph.createEdge(nodes[14], nodes[10]),
800     };
801 //    for (int i = 0; i < edges.length; ++i) {
802 //      graph.getRealizer(edges[i]).setLabelText(Integer.toString(i));
803 //    }
804 
805     for (int i = 0; i < nodes.length; ++i) {
806       final Node node = nodes[i];
807       if (node.inDegree() == 0) {
808         graph.getRealizer(node).setLineColor(DARK_GREEN);
809       } else if (node.outDegree() == 0) {
810         graph.getRealizer(node).setLineColor(BLOOD_RED);
811       }
812     }
813 
814 
815     // setup port constraints to get BPMN-like edge routing for diamond-shaped
816     // and circular child nodes
817 
818     // create the corresponding port constraints ...
819     final EdgeMap srcPc = graph.createEdgeMap();
820     srcPc.set(edges[9], PortConstraint.create(PortConstraint.NORTH));
821     srcPc.set(edges[10], PortConstraint.create(PortConstraint.SOUTH));
822     srcPc.set(edges[12], PortConstraint.create(PortConstraint.SOUTH));
823     srcPc.set(edges[13], PortConstraint.create(PortConstraint.NORTH));
824     srcPc.set(edges[14], PortConstraint.create(PortConstraint.WEST));
825     srcPc.set(edges[15], PortConstraint.create(PortConstraint.NORTH));
826     srcPc.set(edges[16], PortConstraint.create(PortConstraint.SOUTH));
827     final EdgeMap tgtPc = graph.createEdgeMap();
828     tgtPc.set(edges[4], PortConstraint.create(PortConstraint.WEST));
829     tgtPc.set(edges[5], PortConstraint.create(PortConstraint.EAST));
830     tgtPc.set(edges[7], PortConstraint.create(PortConstraint.NORTH));
831     tgtPc.set(edges[8], PortConstraint.create(PortConstraint.SOUTH));
832     tgtPc.set(edges[13], PortConstraint.create(PortConstraint.SOUTH));
833     tgtPc.set(edges[14], PortConstraint.create(PortConstraint.NORTH));
834 
835     // ... and register the port constraints
836     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, srcPc);
837     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, tgtPc);
838 
839 
840     // add a few layering constraint for a more BPMN-like result
841     final LayerConstraintFactory lcf = new IncrementalHierarchicLayouter()
842             .createLayerConstraintFactory(graph);
843     lcf.addPlaceNodeInSameLayerConstraint(nodes[7], nodes[1]);
844     lcf.addPlaceNodeInSameLayerConstraint(nodes[7], nodes[13]);
845 
846     try {
847       layout(graph, false);
848     } finally {
849 
850       lcf.dispose();
851 
852       graph.removeDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
853       graph.removeDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
854       graph.disposeEdgeMap(tgtPc);
855       graph.disposeEdgeMap(srcPc);
856     }
857   }
858 
859   /**
860    * Performs a layout calculation for the specified graph using
861    * {@link y.layout.hierarchic.IncrementalHierarchicLayouter}.
862    * @param graph   the graph to be laid out.
863    * @param vertical   if <code>true</code> a top-to-bottom layout is calculated
864    * and if <code>false</code> a left-to-right layout is calculated.
865    */
866   void layout( final Graph2D graph, final boolean vertical ) {
867     // setup a suitable layout algorithm for a graph with table nodes
868     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
869     ihl.setOrthogonallyRouted(true);
870     ihl.setLayoutOrientation(
871             vertical
872             ? LayoutOrientation.TOP_TO_BOTTOM
873             : LayoutOrientation.LEFT_TO_RIGHT);
874 
875     final Graph2DLayoutExecutor layoutExecutor =
876             new Graph2DLayoutExecutor(Graph2DLayoutExecutor.BUFFERED);
877     layoutExecutor.setConfiguringTableNodeRealizers(true);
878     layoutExecutor.doLayout(graph, ihl);
879   }
880 
881   protected void createExamplesMenu(JMenuBar menuBar) {
882     final JMenu menu = new JMenu("Example Graphs");
883     menuBar.add(menu);
884 
885     menu.add(new AbstractAction("Gradient Rows") {
886       public void actionPerformed(ActionEvent e) {
887         createGradientSample(view.getGraph2D());
888         view.fitContent();
889       }
890     });
891 
892     menu.add(new AbstractAction("Alternating Columns") {
893       public void actionPerformed(ActionEvent e) {
894         createAlternatingSample(view.getGraph2D());
895         view.fitContent();
896       }
897     });
898 
899     menu.add(new AbstractAction("Generic") {
900       public void actionPerformed(ActionEvent e) {
901         createGenericSample(view.getGraph2D());
902         view.fitContent();
903       }
904     });
905 
906     menu.add(new AbstractAction("BPMN Style") {
907       public void actionPerformed(ActionEvent e) {
908         createBpmnStyleSample(view.getGraph2D());
909         view.fitContent();
910       }
911     });
912   }
913 
914   /**
915    * Overwritten.
916    * @return the application menu bar.
917    */
918   protected JMenuBar createMenuBar() {
919     final JMenu fileMenu = new JMenu("File");
920     fileMenu.add(new PrintAction());
921     fileMenu.addSeparator();
922     fileMenu.add(new ExitAction());
923 
924     final JMenuBar jmb = new JMenuBar();
925     jmb.add(fileMenu);
926     createExamplesMenu(jmb);
927     return jmb;
928   }
929 
930   /**
931    * Overwritten to prevent deletion of graph elements.
932    * @return <code>false</code>.
933    */
934   protected boolean isDeletionEnabled() {
935     return false;
936   }
937 
938   public static void main( String[] args ) {
939     EventQueue.invokeLater(new Runnable() {
940       public void run() {
941         Locale.setDefault(Locale.ENGLISH);
942         initLnF();
943         (new TableStyleDemo()).start();
944       }
945     });
946   }
947 
948   /**
949    * Creates and registers lots of configurations for the various (generic)
950    * node realizers used throughout the demo.
951    */
952   private static void initConfigurations() {
953     final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
954 
955 
956     // configurations used for table nodes
957     {
958       // configuration for the table node in the BPMN style sample
959       final Map bpmn = TableGroupNodeRealizer.createDefaultConfigurationMap();
960       configureSelectionMode(bpmn);
961       configureHotSpots(bpmn);
962       bpmn.put(GenericNodeRealizer.Painter.class, TableNodePainter.newBpmnInstance());
963 
964 
965       // configuration for the table node in the generic sample
966       final Map generic = TableGroupNodeRealizer.createDefaultConfigurationMap();
967       configureSelectionMode(generic);
968       configureHotSpots(generic);
969 
970 
971       // configuration for the table node in the alternating columns sample
972       final Map alternating = TableGroupNodeRealizer.createDefaultConfigurationMap();
973       configureSelectionMode(alternating);
974       configureHotSpots(alternating);
975       alternating.put(GenericNodeRealizer.Painter.class,
976                     TableNodePainter.newAlternatingColumnsInstance());
977 
978 
979       // configure the table painter for the gradient rows sample
980       final TableNodePainter painter = TableNodePainter.newDefaultInstance();
981       // disable column background rendering
982       painter.setSubPainter(TableNodePainter.PAINTER_COLUMN_BACKGROUND, null);
983       // register a custom row subordinate painter
984       painter.setSubPainter(TableNodePainter.PAINTER_ROW_BACKGROUND, new GradientRowPainter());
985 
986       // configuration for the table node in the gradient rows sample
987       final Map gradient = TableGroupNodeRealizer.createDefaultConfigurationMap();
988       configureSelectionMode(gradient);
989       configureHotSpots(gradient);
990       gradient.put(GenericNodeRealizer.Painter.class, painter);
991 
992 
993       // register the configurations
994       factory.addConfiguration(
995               CONFIGURATION_POOL_GRADIENT, gradient);
996       factory.addConfiguration(
997               CONFIGURATION_POOL_ALTERNATING, alternating);
998       factory.addConfiguration(
999               CONFIGURATION_POOL_GENERIC, generic);
1000      factory.addConfiguration(
1001              CONFIGURATION_POOL_BPMN_STYLE, bpmn);
1002    }
1003
1004
1005    // configurations used for child nodes in the BPMN style sample
1006    {
1007      final ShapeNodePainter roundRect =
1008              new ShapeNodePainter(ShapeNodePainter.ROUND_RECT);
1009      final Map simpleRoundRect = factory.createDefaultConfigurationMap();
1010      simpleRoundRect.put(GenericNodeRealizer.Painter.class, roundRect);
1011      simpleRoundRect.put(GenericNodeRealizer.ContainsTest.class, roundRect);
1012
1013      final ShapeNodePainter diamond =
1014              new ShapeNodePainter(ShapeNodePainter.DIAMOND);
1015      final Map simpleDiamond = factory.createDefaultConfigurationMap();
1016      simpleDiamond.put(GenericNodeRealizer.Painter.class, diamond);
1017      simpleDiamond.put(GenericNodeRealizer.ContainsTest.class, diamond);
1018
1019      final ShapeNodePainter ellipse =
1020              new ShapeNodePainter(ShapeNodePainter.ELLIPSE);
1021      final Map simpleEllipse = factory.createDefaultConfigurationMap();
1022      simpleEllipse.put(GenericNodeRealizer.Painter.class, ellipse);
1023      simpleEllipse.put(GenericNodeRealizer.ContainsTest.class, ellipse);
1024
1025      factory.addConfiguration(CONFIGURATION_SIMPLE_ROUNDRECT, simpleRoundRect);
1026      factory.addConfiguration(CONFIGURATION_SIMPLE_DIAMOND, simpleDiamond);
1027      factory.addConfiguration(CONFIGURATION_SIMPLE_ELLIPSE, simpleEllipse);
1028    }
1029
1030
1031    // configurations used for all other child nodes
1032    {
1033      final Map gradientRect = factory.createDefaultConfigurationMap();
1034      configureSelectionMode(gradientRect);
1035      gradientRect.put(
1036            GenericNodeRealizer.Painter.class,
1037            new SimpleGradientNodePainter(ShapeNodePainter.RECT));
1038
1039      final Map gradientRoundRect = factory.createDefaultConfigurationMap();
1040      final SimpleGradientNodePainter roundRect =
1041              new SimpleGradientNodePainter(ShapeNodePainter.ROUND_RECT);
1042      gradientRoundRect.put(GenericNodeRealizer.Painter.class, roundRect);
1043      gradientRoundRect.put(GenericNodeRealizer.ContainsTest.class, roundRect);
1044
1045      final Map gradientDiamond = factory.createDefaultConfigurationMap();
1046      final SimpleGradientNodePainter diamond =
1047              new SimpleGradientNodePainter(ShapeNodePainter.DIAMOND);
1048      gradientDiamond.put(GenericNodeRealizer.Painter.class, diamond);
1049      gradientDiamond.put(GenericNodeRealizer.ContainsTest.class, diamond);
1050
1051      final Map gradientEllipse = factory.createDefaultConfigurationMap();
1052      final SimpleGradientNodePainter ellipse =
1053              new SimpleGradientNodePainter(ShapeNodePainter.ELLIPSE);
1054      gradientEllipse.put(GenericNodeRealizer.Painter.class, ellipse);
1055      gradientEllipse.put(GenericNodeRealizer.ContainsTest.class, ellipse);
1056
1057      factory.addConfiguration(CONFIGURATION_GRADIENT_RECT, gradientRect);
1058      factory.addConfiguration(CONFIGURATION_GRADIENT_ROUNDRECT, gradientRoundRect);
1059      factory.addConfiguration(CONFIGURATION_GRADIENT_DIAMOND, gradientDiamond);
1060      factory.addConfiguration(CONFIGURATION_GRADIENT_ELLIPSE, gradientEllipse);
1061    }
1062  }
1063
1064  /**
1065   * Configures the specified configuration map for default hot spot painting
1066   * and hit testing.
1067   * @param map   a configuration map.
1068   */
1069  private static void configureHotSpots( final Map map ) {
1070    // setting a null HotSpotPainter actually configures GenericNodeRealizer
1071    // to use the default hot spot painting
1072    map.put(GenericNodeRealizer.HotSpotPainter.class, null);
1073    // setting a null HotSpotHitTest actually configures GenericNodeRealizer
1074    // to use the default hot spot hit testing
1075    map.put(GenericNodeRealizer.HotSpotHitTest.class, null);
1076  }
1077
1078  /**
1079   * Configures the <code>TableSelectionMode</code> in the specified
1080   * configuration map to couple column/row selection state and realizer
1081   * selection state.
1082   * @param map   a configuration map.
1083   */
1084  private static void configureSelectionMode( final Map map ) {
1085    final Object miep =
1086            map.get(GenericNodeRealizer.GenericMouseInputEditorProvider.class);
1087    if (miep instanceof MultiplexingNodeEditor) {
1088      final MultiplexingNodeEditor editor = (MultiplexingNodeEditor) miep;
1089      for (Iterator it = editor.getNodeEditors().iterator(); it.hasNext();) {
1090        final Object mode = it.next();
1091        if (mode instanceof TableSelectionEditor) {
1092          ((TableSelectionEditor) mode).setSelectionPolicy(
1093                  TableSelectionEditor.RELATE_TO_NODE_SELECTION);
1094        }
1095      }
1096    }
1097  }
1098
1099  static boolean useGradientStyle( final Graphics2D graphics ) {
1100    return YRenderingHints.isGradientPaintingEnabled(graphics);
1101  }
1102
1103
1104  /**
1105   * {@link y.view.ShapeNodePainter} painter that uses a vertical gradient paint
1106   * (defined by the context realizer's fill color and fill color 2) to fill
1107   * node shapes and adds a very simple drop shadow.
1108   */
1109  private static final class SimpleGradientNodePainter extends ShapeNodePainter {
1110    SimpleGradientNodePainter( final byte type ) {
1111      super(type);
1112    }
1113
1114    protected void paintFilledShape(
1115            final NodeRealizer context,
1116            final Graphics2D graphics,
1117            final Shape shape
1118    ) {
1119      if (!context.isTransparent()) {
1120        final boolean useSelectionStyle = useSelectionStyle(context, graphics);
1121        final Paint paint =
1122                useGradientStyle(graphics)
1123                ? getFillPaint(context, useSelectionStyle)
1124                : getFillColor(context, useSelectionStyle);
1125        if (paint != null) {
1126          final AffineTransform oldTransform = graphics.getTransform();
1127          graphics.translate(3, 3);
1128          graphics.setColor(Color.GRAY);
1129          graphics.fill(shape);
1130          graphics.setTransform(oldTransform);
1131
1132          graphics.setPaint(paint);
1133          graphics.fill(shape);
1134        }
1135      }
1136    }
1137
1138    protected Paint getFillPaint( final NodeRealizer context, final boolean selected ) {
1139      Color fill1 = getFillColor(context, selected);
1140      if (fill1 != null) {
1141        Color fill2 = getFillColor2(context, selected);
1142        if (fill2 != null) {
1143          final float x = (float) context.getX();
1144          final double y = context.getY();
1145          return new GradientPaint(
1146                  x, (float) y, fill1,
1147                  x, (float) (y + context.getHeight()), fill2, true);
1148        } else {
1149          return fill1;
1150        }
1151      } else {
1152        return null;
1153      }
1154    }
1155
1156    private static boolean useSelectionStyle(
1157            final NodeRealizer context,
1158            final Graphics2D graphics
1159    ) {
1160      return context.isSelected() &&
1161             YRenderingHints.isSelectionPaintingEnabled(graphics);
1162    }
1163  }
1164
1165  /**
1166   * {@link y.view.GenericNodeRealizer.Painter} meant to be used as a row
1167   * background painter for {@link y.view.tabular.TableNodePainter}.
1168   * The row background is filled using gradient paints defined by custom
1169   * style properties.
1170   */
1171  private static final class GradientRowPainter extends AbstractCustomNodePainter {
1172    /**
1173     * Style property ID used to retrieve style properties of type
1174     * {@link demo.view.realizer.TableStyleDemo.GradientRowPainter.RowColorMap}
1175     * that are used to create appropriate gradient paints.
1176     */
1177    static final String STYLE_ROW_COLOR_MAP = "ROW_COLOR_MAP";
1178
1179
1180    final Rectangle2D.Double shape;
1181
1182    GradientRowPainter() {
1183      shape = new Rectangle2D.Double();
1184    }
1185
1186    /**
1187     * Overwritten to prevent hot spot and label painting.
1188     * @param dummy   the dummy realizer representing the bounds of the row
1189     * that is to be painted.
1190     * @param graphics   the graphics context for painting.
1191     */
1192    public void paint( final NodeRealizer dummy, final Graphics2D graphics ) {
1193      if (!dummy.isVisible()) {
1194        return;
1195      }
1196      backupGraphics(graphics);
1197      try {
1198        paintNode(dummy, graphics, false);
1199      } finally {
1200        restoreGraphics(graphics);
1201      }
1202    }
1203
1204    /**
1205     * Paints the row represented by the specified realizer.
1206     * @param dummy   the dummy realizer representing the bounds of the row
1207     * that is to be painted.
1208     * @param graphics   the graphics context for painting.
1209     * @param sloppy   ignored.
1210     */
1211    protected void paintNode(
1212            final NodeRealizer dummy,
1213            final Graphics2D graphics,
1214            final boolean sloppy
1215    ) {
1216      if (!dummy.isTransparent()) {
1217        final Paint paint =
1218                useGradientStyle(graphics)
1219                ? getFillPaint(dummy, dummy.isSelected())
1220                : getFillColor(dummy, dummy.isSelected());
1221        if (paint != null) {
1222          shape.setFrame(dummy.getX(), dummy.getY(), dummy.getWidth(), dummy.getHeight());
1223          graphics.setPaint(paint);
1224          graphics.fill(shape);
1225        }
1226
1227        final YInsets insets = getRow((dummy)).getInsets();
1228        if (insets != null && insets.left > 0) {
1229          final Color color = getFillColor(dummy, false);
1230          if (color != null) {
1231            shape.setFrame(dummy.getX(), dummy.getY(), insets.left, dummy.getHeight());
1232            graphics.setColor(color);
1233            graphics.fill(shape);
1234          }
1235        }
1236      }
1237    }
1238
1239    /**
1240     * Determines the fill paint for the row represented by the specified
1241     * realizer depending on the registered {@link #STYLE_ROW_COLOR_MAP} style
1242     * property.
1243     * @param dummy   the dummy realizer representing the bounds of the row
1244     * that is to be painted.
1245     * @param selected whether the node is currently selected
1246     * @return the background fill paint for the row represented by the
1247     * specified realizer.
1248     */
1249    protected Paint getFillPaint( final NodeRealizer dummy, final boolean selected ) {
1250      final GenericNodeRealizer gnr = (GenericNodeRealizer) dummy;
1251      final RowColorMap rcm = (RowColorMap) gnr.getStyleProperty(STYLE_ROW_COLOR_MAP);
1252      if (rcm != null) {
1253        final Row row = getRow(gnr);
1254        Color color = rcm.getColor(row);
1255        if (color == null) {
1256          color = new Color(0, 0, 0, 0);
1257        }
1258
1259        final double x = dummy.getX();
1260        final float y = (float) dummy.getY();
1261        return new GradientPaint(
1262                (float) x, y, color,
1263                (float) (x + dummy.getWidth()),  y, getFillColor2(dummy, false));
1264      } else {
1265        return getFillColor(dummy, selected);
1266      }
1267    }
1268
1269    /**
1270     * Returns the row represented by the specified realizer.
1271     * @param dummy   a {@link y.view.GenericNodeRealizer} representing
1272     * a row in a {@link y.view.tabular.TableGroupNodeRealizer}.
1273     * @return the row represented by the specified realizer.
1274     */
1275    private static Row getRow( final NodeRealizer dummy ) {
1276      return TableNodePainter.getRow(dummy);
1277    }
1278
1279
1280    private interface RowColorMap {
1281      public Color getColor( Row row );
1282    }
1283  }
1284
1285
1286  /**
1287   * <code>TableStyle</code> intended for rows that uses a realizer's fill color
1288   * 2 as fill color.
1289   */
1290  private static final class RowStyle implements TableStyle {
1291    private final boolean selected;
1292
1293    RowStyle( final boolean selected ) {
1294      this.selected = selected;
1295    }
1296
1297    public Stroke getBorderLineType( final NodeRealizer context ) {
1298      return null;
1299    }
1300
1301    public Color getBorderLineColor( final NodeRealizer context ) {
1302      return null;
1303    }
1304
1305    public Color getBorderFillColor( final NodeRealizer context ) {
1306      return null;
1307    }
1308
1309    public Stroke getLineType( final NodeRealizer context ) {
1310      if (selected) {
1311        final LineType lt = context.getLineType();
1312        return LineType.getLineType(
1313                (int)Math.ceil(lt.getLineWidth()) + 2,
1314                lt.getLineStyle());
1315      } else {
1316        return context.getLineType();
1317      }
1318    }
1319
1320    public Color getLineColor( final NodeRealizer context ) {
1321      return context.getLineColor();
1322    }
1323
1324    public Color getFillColor( final NodeRealizer context ) {
1325      return context.getFillColor2();
1326    }
1327  }
1328}
1329