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.application;
29  
30  import demo.view.DemoBase;
31  import demo.view.DemoDefaults;
32  import y.base.Node;
33  import y.geom.YInsets;
34  import y.layout.FixNodeLayoutStage;
35  import y.layout.LayoutOrientation;
36  import y.layout.NodeLabelModel;
37  import y.layout.hierarchic.IncrementalHierarchicLayouter;
38  import y.layout.hierarchic.incremental.SimplexNodePlacer;
39  import y.option.RealizerCellRenderer;
40  import y.util.DataProviders;
41  import y.view.CreateEdgeMode;
42  import y.view.Drawable;
43  import y.view.DropSupport;
44  import y.view.EditMode;
45  import y.view.GenericNodeRealizer;
46  import y.view.Graph2D;
47  import y.view.Graph2DLayoutExecutor;
48  import y.view.Graph2DView;
49  import y.view.Graph2DViewActions;
50  import y.view.HitInfo;
51  import y.view.LineType;
52  import y.view.MultiplexingNodeEditor;
53  import y.view.NodeRealizer;
54  import y.view.ShapeNodePainter;
55  import y.view.Graph2DListener;
56  import y.view.Graph2DEvent;
57  import y.view.NodeLabel;
58  import y.view.hierarchy.GenericGroupNodeRealizer;
59  import y.view.hierarchy.HierarchyManager;
60  import y.view.tabular.ColumnDropTargetListener;
61  import y.view.tabular.RowDropTargetListener;
62  import y.view.tabular.TableGroupNodeRealizer;
63  import y.view.tabular.TableGroupNodeRealizer.ColumnNodeLabelModel;
64  import y.view.tabular.TableGroupNodeRealizer.Column;
65  import y.view.tabular.TableGroupNodeRealizer.Row;
66  import y.view.tabular.TableGroupNodeRealizer.RowNodeLabelModel;
67  import y.view.tabular.TableLabelEditor;
68  import y.view.tabular.TableNodePainter;
69  import y.view.tabular.TableOrderEditor;
70  import y.view.tabular.TableSelectionEditor;
71  import y.view.tabular.TableSizeEditor;
72  import y.view.tabular.TableStyle;
73  import y.view.tabular.TableSupport;
74  
75  import java.awt.BorderLayout;
76  import java.awt.Color;
77  import java.awt.Component;
78  import java.awt.Dimension;
79  import java.awt.EventQueue;
80  import java.awt.Graphics;
81  import java.awt.Graphics2D;
82  import java.awt.Paint;
83  import java.awt.Rectangle;
84  import java.awt.Stroke;
85  import java.awt.dnd.DnDConstants;
86  import java.awt.dnd.DragGestureEvent;
87  import java.awt.dnd.DragGestureListener;
88  import java.awt.dnd.DragSource;
89  import java.awt.event.ActionEvent;
90  import java.awt.geom.Rectangle2D;
91  import java.net.URL;
92  import java.util.Collection;
93  import java.util.Iterator;
94  import java.util.List;
95  import java.util.Locale;
96  import java.util.Map;
97  import javax.swing.AbstractAction;
98  import javax.swing.Action;
99  import javax.swing.ActionMap;
100 import javax.swing.DefaultListCellRenderer;
101 import javax.swing.Icon;
102 import javax.swing.InputMap;
103 import javax.swing.JComponent;
104 import javax.swing.JList;
105 import javax.swing.JToolBar;
106 import javax.swing.ListSelectionModel;
107 import javax.swing.ListCellRenderer;
108 import javax.swing.border.LineBorder;
109 
110 /**
111  * <p>Demonstrates how to use and customize {@link y.view.tabular.TableGroupNodeRealizer} to work as a pool
112  * having several swim lanes and milestones.</p>
113  * <p>A list using {@link y.view.tabular.RowDropTargetListener} and
114  * {@link y.view.tabular.ColumnDropTargetListener} is added to showcase how additional rows and
115  * columns can be added via drag'n'drop.</p>
116  * <p>Two different ways to customize the rendering of rows and columns are used:</p>
117  * <ul>
118  * <li>For columns, customized {@link y.view.tabular.TableStyle.SimpleStyle SimpleStyles} are registered as style properties
119  * of the <code>TableGroupNodeRealizer</code> which are used by the default column sub painter.</li>
120  * <li>For rows, a custom row sub painter is used that alternates the fill color of childless rows while rendering rows
121  * with children in a third color.</li>
122  * </ul>
123  *
124  * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/incremental_hierarchical_layouter" target="_blank">Section Hierarchical Layout Style</a> in the yFiles for Java Developer's Guide
125  * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/tabular_data_table_structure" target="_blank">Section Table Structure Model</a> in the yFiles for Java Developer's Guide
126  */
127 public class SwimlaneDemo extends DemoBase {
128   static final String CONFIGURATION_GROUP_NODE = "CONFIGURATION_GROUP_NODE";
129   static final String CONFIGURATION_TABLE_NODE = "CONFIGURATION_TABLE_NODE";
130 
131   static {
132     initConfigurations();
133   }
134 
135 
136   private YInsets rowInsets;
137   private YInsets columnInsets;
138   private MinimumSizeManager minimumSizeManager;
139 
140 
141   public static void main( String[] args ) {
142     EventQueue.invokeLater(new Runnable() {
143       public void run() {
144         Locale.setDefault(Locale.ENGLISH);
145         initLnF();
146         (new SwimlaneDemo("resource/swimlanehelp.html")).start();
147       }
148     });
149   }
150 
151   public SwimlaneDemo() {
152     this(null);
153   }
154 
155   public SwimlaneDemo( final String helpFilePath ) {
156     addHelpPane(helpFilePath);
157   }
158 
159   private NodeRealizer createConfiguredNormalNodeRealizer() {
160     final NodeRealizer normalNode = view.getGraph2D().getDefaultNodeRealizer().createCopy();
161     normalNode.setSize(80, 50);
162     return normalNode;
163   }
164 
165   private NodeRealizer createConfiguredGroupNodeRealizer() {
166     final GenericGroupNodeRealizer ggnr = new GenericGroupNodeRealizer();
167     ggnr.setConfiguration(CONFIGURATION_GROUP_NODE);
168     ggnr.setFillColor(null);
169     ggnr.setLineType(LineType.DASHED_DOTTED_1);
170     ggnr.removeLabel(ggnr.getLabel());
171     ggnr.setGroupClosed(false);
172     ggnr.setSize(80, 50);
173     return ggnr;
174   }
175 
176   private NodeRealizer createConfiguredTableNodeRealizer() {
177     final TableGroupNodeRealizer tgnr = new TableGroupNodeRealizer();
178     tgnr.setConfiguration(CONFIGURATION_TABLE_NODE);
179 
180     // background color used for the TableGroupNodeRealizer and therefore per default for the table.
181     tgnr.setFillColor(new Color(236, 245, 255));
182 
183     // use custom styles for selected and unselected columns
184     final Color columnFillColor = new Color(113, 146, 178);
185     tgnr.setStyleProperty(
186             TableNodePainter.COLUMN_STYLE_ID,
187             new TableStyle.SimpleStyle(
188                     tgnr.getLineType(),
189                     tgnr.getLineColor(),
190                     columnFillColor,
191                     null,
192                     null,
193                     columnFillColor
194             )
195     );
196 
197     final LineType lt = tgnr.getLineType();
198     final Color columnSelectedFillColor = new Color(55, 93, 129);
199     tgnr.setStyleProperty(
200             TableNodePainter.COLUMN_SELECTION_STYLE_ID,
201             new TableStyle.SimpleStyle(
202                     LineType.createLineType((int) Math.ceil(lt.getLineWidth()) + 2, lt.getLineStyle()),
203                     tgnr.getLineColor(),
204                     columnSelectedFillColor,
205                     null,
206                     null,
207                     columnSelectedFillColor
208             )
209     );
210 
211     // Defaults for columns and rows should be set before those of the table.
212     // This way the defaults are also applied to the first row and column which
213     // are automatically added to the table on it's first access.
214     tgnr.setDefaultColumnWidth(600);
215     tgnr.setDefaultMinimumColumnWidth(200);
216     tgnr.setDefaultColumnInsets(columnInsets);
217     tgnr.setDefaultRowHeight(150);
218     tgnr.setDefaultMinimumRowHeight(50);
219     tgnr.setDefaultRowInsets(rowInsets);
220     tgnr.setAutoResize(true);
221 
222     final TableGroupNodeRealizer.Table table = tgnr.getTable();
223     table.setInsets(new YInsets(30, 0, 0, 0));
224 
225     tgnr.setSize(250, 200);
226     return tgnr;
227   }
228 
229   /**
230    * Adds configurations for nodes with a bevel node style and those using a
231    * {@link y.view.tabular.TableGroupNodeRealizer} to the factory.
232    */
233   private static void initConfigurations() {
234     final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
235 
236     final Map groupMap = createGroupNodeConfiguration();
237     factory.addConfiguration(CONFIGURATION_GROUP_NODE, groupMap);
238 
239     final Map tableMap = createTableNodeConfiguration();
240     factory.addConfiguration(CONFIGURATION_TABLE_NODE, tableMap);
241   }
242 
243   protected void initialize() {
244     // a hierarchy manager has to be used for table group nodes to work.
245     new HierarchyManager(view.getGraph2D());
246 
247     minimumSizeManager = new MinimumSizeManager(view.getGraph2D());
248 
249     rowInsets = new YInsets(0, 30, 0, 0);
250     columnInsets = new YInsets(30, 5, 0, 5);
251     final DropSupport dropSupport = createDropSupport(view);
252 
253     contentPane.add(createDragNDropList(dropSupport), BorderLayout.WEST);
254 
255     loadGraph( "resource/SwimlaneDemo.graphml" );
256 
257     view.getGraph2D().addDataProvider(
258         FixNodeLayoutStage.FIXED_NODE_DPKEY,
259         DataProviders.createConstantDataProvider(Boolean.TRUE));
260 
261     view.setPreferredSize(new Dimension(950, 550));
262     view.fitContent();
263     view.updateView();
264   }
265 
266   protected void loadGraph( final URL resource ) {
267     // disable the size manager because loading a graph results in lots of
268     // label text property changes
269     minimumSizeManager.setEnabled(false);
270     try {
271       super.loadGraph(resource);
272     } finally {
273       minimumSizeManager.setEnabled(true);
274     }
275   }
276 
277   protected void registerViewActions() {
278     // register default keyboard actions
279     super.registerViewActions();
280 
281     ActionMap amap = view.getCanvasComponent().getActionMap();
282     if (amap != null) {
283       if (isDeletionEnabled()) {
284         // replace the default action for deleting selected elements with the
285         // application's custom action that supports deleting table columns or
286         // rows independently of deleting table nodes
287         amap.put(Graph2DViewActions.DELETE_SELECTION, createDeleteSelectionActionImpl());
288       }
289     }
290   }
291 
292   private static DropSupport createDropSupport(Graph2DView view) {
293     // a customized DropSupport is used which only created new nodes if they are dropped onto a group node
294     DropSupport dropSupport = new DropSupport(view) {
295 
296       protected boolean dropNodeRealizer(Graph2DView view, NodeRealizer r, double worldCoordX, double worldCoordY) {
297         final HierarchyManager hm = HierarchyManager.getInstance(view.getGraph2D());
298         final HitInfo hitInfo = DemoBase.checkNodeHit(view, worldCoordX, worldCoordY);
299         if (hm != null &&
300             hitInfo.hasHitNodes()) {
301           final Node node = (Node) hitInfo.hitNodes().current();
302           if (hm.isGroupNode(node) &&
303               ! (r instanceof TableGroupNodeRealizer)) {
304             // there is a group node at the drop location which will become the parent of the new node
305             return super.dropNodeRealizer(view, r, worldCoordX, worldCoordY);
306           }
307         } else if (r instanceof TableGroupNodeRealizer) {
308           return super.dropNodeRealizer(view, r, worldCoordX, worldCoordY);
309         }
310         return false;
311       }
312     };
313     dropSupport.setSnappingEnabled(true);
314     dropSupport.getSnapContext().setNodeToNodeDistance(30);
315     dropSupport.getSnapContext().setNodeToEdgeDistance(20);
316     dropSupport.getSnapContext().setUsingSegmentSnapLines(true);
317     dropSupport.setPreviewEnabled(true);
318     return dropSupport;
319   }
320 
321   private JList createDragNDropList(final DropSupport support) {
322     final Object[] listContent = new Object[] {
323             createConfiguredTableNodeRealizer(),
324             DropItemListCellRenderer.DROP_TYPE_ROW,
325             DropItemListCellRenderer.DROP_TYPE_COLUMN,
326             createConfiguredNormalNodeRealizer(),
327             createConfiguredGroupNodeRealizer()
328     };
329 
330     final Color lightBlueFillColor = new Color(126, 179, 240, 128);
331     final Color unselectedBorderColor = new Color(58, 82, 109);
332     final Stroke borderStroke = LineType.LINE_1;
333 
334     // configure how the icons for rows and column drag'n'dropable shall look like
335     final DropDrawable rowIcon = new DropDrawable(unselectedBorderColor, lightBlueFillColor, borderStroke);
336     rowIcon.insets = new YInsets(0, 15, 0, 0);
337     rowIcon.setBounds(0, 0, 80, 50);
338 
339     final DropDrawable columnIcon = new DropDrawable(unselectedBorderColor, lightBlueFillColor, borderStroke);
340     columnIcon.insets = new YInsets(15, 0, 0, 0);
341     columnIcon.setBounds(0, 0, 80, 50);
342 
343     final DropItemListCellRenderer cellRenderer =
344             new DropItemListCellRenderer(columnIcon, rowIcon);
345 
346     // configure the list itself
347     final JList dropItemList = new JList(listContent);
348     dropItemList.setCellRenderer(cellRenderer);
349     dropItemList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
350     dropItemList.setSelectedIndex(0);
351     dropItemList.setFixedCellHeight(100);
352     dropItemList.setFixedCellWidth(100);
353     dropItemList.setBorder(LineBorder.createBlackLineBorder());
354 
355     final DragSource dragSource = new DragSource();
356 
357     // configure the drop target listener used for rows and columns
358     final RowDropTargetListener rowListener =
359             new RowDropTargetListener(view) {
360               DropDrawable drawable = new DropDrawable(unselectedBorderColor, lightBlueFillColor, borderStroke);
361 
362               protected Drawable createDrawable(Rectangle2D bounds, YInsets insets) {
363                 drawable.insets = insets;
364                 drawable.setBounds(bounds);
365                 return drawable;
366               }
367             };
368     rowListener.setDefaultHeight(50);
369     rowListener.setDefaultMinimumHeight(30);
370     rowListener.setDrawableWidth(200);
371     rowListener.setDefaultInsets(rowInsets);
372     rowListener.setMaxLevel(2);
373 
374 
375     final ColumnDropTargetListener columnListener =
376             new ColumnDropTargetListener(view) {
377               DropDrawable drawable = new DropDrawable(unselectedBorderColor, lightBlueFillColor, borderStroke);
378 
379               protected Drawable createDrawable(Rectangle2D bounds, YInsets insets) {
380                 drawable.insets = insets;
381                 drawable.setBounds(bounds);
382                 return drawable;
383               }
384             };
385     columnListener.setDefaultWidth(100);
386     columnListener.setDefaultMinimumWidth(200);
387     columnListener.setDrawableHeight(180);
388     columnListener.setDefaultInsets(columnInsets);
389     columnListener.setMaxLevel(2);
390 
391 
392     // use the drop support class to initialize the drag and drop operation.
393     dragSource.createDefaultDragGestureRecognizer(dropItemList, DnDConstants.ACTION_MOVE,
394         new DragGestureListener() {
395           public void dragGestureRecognized(DragGestureEvent event) {
396             final Object value = dropItemList.getSelectedValue();
397             if (value.equals(DropItemListCellRenderer.DROP_TYPE_ROW)) {
398               support.startDrag(dragSource,
399                       rowListener,
400                       event,
401                       DragSource.DefaultMoveDrop);
402             } else if (value.equals(DropItemListCellRenderer.DROP_TYPE_COLUMN)) {
403               support.startDrag(dragSource,
404                       columnListener,
405                       event,
406                       DragSource.DefaultMoveDrop);
407             } else if (value instanceof NodeRealizer) {
408               NodeRealizer nr = (NodeRealizer) value;
409               support.startDrag(dragSource, nr, event, DragSource.DefaultMoveDrop);
410             }
411           }
412         });
413     return dropItemList;
414   }
415 
416 
417   /**
418    * Creates a new edit mode that is configured to support user interaction with the {@link y.view.tabular.TableGroupNodeRealizer}.
419    *
420    * @return An edit mode for the user interaction in this demo.
421    */
422   protected EditMode createEditMode() {
423     final EditMode editMode = super.createEditMode();
424 
425     // the property setNodeSearchingEnabled has to be set to 'true' to allow custom MouseInputEditorProviders to be used
426     editMode.getMouseInputMode().setNodeSearchingEnabled(true);
427 
428     // nodes may only be created via drag'n'drop
429     editMode.allowNodeCreation(false);
430 
431     // activate snap lines
432     editMode.setSnappingEnabled(true);
433 
434     // ensure orthogonal edges during interactive edits
435     editMode.setOrthogonalEdgeRouting(true);
436 
437     // activate snapping and ensure orthogonal edges during edge creation
438     final CreateEdgeMode cem = new CreateEdgeMode();
439     cem.setFuzzyTargetPortDetermination(true);
440     cem.setSnapToOrthogonalSegmentsDistance(5);
441     cem.setUsingNodeCenterSnapping(true);
442     cem.setSnappingOrthogonalSegments(true);
443     cem.setIndicatingTargetNode(true);
444     cem.setRemovingInnerBends(true);
445     cem.setOrthogonalEdgeCreation(true);
446     editMode.setCreateEdgeMode(cem);
447 
448     return editMode;
449   }
450 
451   /**
452    * Overwritten to add a layout action to the demo's tool bar.
453    */
454   protected JToolBar createToolBar() {
455     final JToolBar toolBar = super.createToolBar();
456 
457     toolBar.addSeparator();
458 
459     final AbstractAction layoutAction = new AbstractAction(
460             "Layout", SHARED_LAYOUT_ICON) {
461       public void actionPerformed( ActionEvent e ) {
462         layout(view.getGraph2D());
463         view.updateView();
464       }
465     };
466 
467     toolBar.add(createActionControl(layoutAction));
468 
469     return toolBar;
470   }
471 
472   /**
473    * Overwritten to create an action that loads/opens a graph and clears
474    * the undo queue right afterwards.
475    * @return an action that loads/opens a graph and clears
476    * the undo queue right afterwards.
477    */
478   protected Action createLoadAction() {
479     final Action action = super.createLoadAction();
480     return new AbstractAction((String) action.getValue(Action.NAME)) {
481       public void actionPerformed( final ActionEvent e ) {
482         action.actionPerformed(e);
483         getUndoManager().resetQueue();
484       }
485     };
486   }
487 
488   /**
489    * Runs an incremental hierarchic layout that respects the assignments of nodes to swimlanes and milestones.
490    */
491   private void layout( final Graph2D graph ) {
492     graph.firePreEvent();
493     try {
494       // undoability
495       graph.backupRealizers();
496 
497       final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
498       ihl.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
499       ihl.setOrthogonallyRouted(true);
500       ihl.setRecursiveGroupLayeringEnabled(false);
501       ((SimplexNodePlacer) ihl.getNodePlacer()).setBaryCenterModeEnabled(true);
502 
503       final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.BUFFERED);
504       layoutExecutor.setConfiguringTableNodeRealizers(true);
505       layoutExecutor.getTableLayoutConfigurator().setCompactionEnabled(false);
506       layoutExecutor.getTableLayoutConfigurator().setHorizontalLayoutConfiguration(true);
507       layoutExecutor.doLayout(graph, new FixNodeLayoutStage(ihl));
508     } finally {
509       graph.firePostEvent();
510     }
511   }
512 
513   protected Action createDeleteSelectionAction() {
514     final Action action = createDeleteSelectionActionImpl();
515     action.putValue(Action.SMALL_ICON, getIconResource("resource/delete.png"));
516     action.putValue(Action.SHORT_DESCRIPTION, "Delete Selection");
517     return action;
518   }
519 
520   private Action createDeleteSelectionActionImpl() {
521     final Graph2DViewActions.DeleteSelectionAction action =
522             new Graph2DViewActions.DeleteSelectionAction(view);
523     action.setDeletionMask(Graph2DViewActions.DeleteSelectionAction.ALL_TYPES_MASK);
524     action.setKeepingTableNodesOnTableContentDeletion(true);
525     action.setKeepingParentGroupNodeSizes(true);
526     return action;
527   }
528 
529   private static Map createGroupNodeConfiguration() {
530     final Map map = GenericGroupNodeRealizer.createDefaultConfigurationMap();
531     final ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.ROUND_RECT);
532     map.put(GenericNodeRealizer.ContainsTest.class, painter);
533     map.put(GenericNodeRealizer.Painter.class, painter);
534     map.put(GenericNodeRealizer.GenericMouseInputEditorProvider.class, null);
535     return map;
536   }
537 
538   private static Map createTableNodeConfiguration() {
539     final Map map = TableGroupNodeRealizer.createDefaultConfigurationMap();
540 
541     // configure the painter used for the swim lanes
542     final AlternatingPainter rowPainter = new AlternatingPainter();
543     final TableNodePainter tableNodePainter = TableNodePainter.newDefaultInstance();
544     tableNodePainter.setSubPainter(TableNodePainter.PAINTER_ROW_BACKGROUND, rowPainter);
545     map.put(GenericNodeRealizer.Painter.class, tableNodePainter);
546 
547     // configure MouseInputEditor for the TableGroupNodeRealizer
548     final MultiplexingNodeEditor editor = new MultiplexingNodeEditor();
549     final TableLabelEditor editLabelEditor = new TableLabelEditor();
550     editor.addNodeEditor(editLabelEditor);
551     final TableSelectionEditor tableSelectionEditor = new TableSelectionEditor();
552     tableSelectionEditor.setSelectionPolicy(TableSelectionEditor.RELATE_TO_NODE_SELECTION);
553     editor.addNodeEditor(tableSelectionEditor);
554     final TableSizeEditor resizeEditor = new TableSizeEditor();
555     editor.addNodeEditor(resizeEditor);
556     final TableOrderEditor tableOrderEditor = new TableOrderEditor();
557     tableOrderEditor.setMaxColumnLevel(2);
558     tableOrderEditor.setMaxRowLevel(2);
559     editor.addNodeEditor(tableOrderEditor);
560     map.put(GenericNodeRealizer.GenericMouseInputEditorProvider.class, editor);
561 
562     return map;
563   }
564 
565   /**
566    * Ensures that the minimum width of columns and the minimum height of rows
567    * is never smaller than the width or height of their associated labels.
568    * <p>
569    * The implementation relies on the fact that there is at most one label
570    * associated to any column or row.
571    * </p>
572    */
573   private static final class MinimumSizeManager implements Graph2DListener {
574     private boolean enabled;
575 
576     MinimumSizeManager( final Graph2D graph ) {
577       graph.addGraph2DListener(this);
578       enabled = true;
579     }
580 
581     public boolean isEnabled() {
582       return enabled;
583     }
584 
585     public void setEnabled( final boolean enabled ) {
586       this.enabled = enabled;
587     }
588 
589     public void onGraph2DEvent( final Graph2DEvent e ) {
590       if (isEnabled()) {
591         if ("text".equals(e.getPropertyName())) {
592           final Object subject = e.getSubject();
593           if (subject instanceof NodeLabel) {
594             final NodeLabel label = (NodeLabel) subject;
595             final NodeLabelModel model = label.getLabelModel();
596             if (model instanceof ColumnNodeLabelModel) {
597               handleColumnLabelEvent(label);
598             } else if (model instanceof RowNodeLabelModel) {
599               handleRowLabelEvent(label);
600             }
601           }
602         }
603       }
604     }
605 
606     private void handleRowLabelEvent( final NodeLabel label ) {
607       final Row row = RowNodeLabelModel.getRow(label);
608       if (row != null) {
609         final double h = label.getHeight() + 8;
610         if (h > row.getHeight()) {
611           (new TableSupport()).setHeight(row, h, false);
612         }
613         row.setMinimumHeight(Math.max(
614                 h, ((TableGroupNodeRealizer) label.getRealizer())
615                         .getDefaultMinimumRowHeight()));
616       }
617     }
618 
619     private void handleColumnLabelEvent( final NodeLabel label ) {
620       final Column column = ColumnNodeLabelModel.getColumn(label);
621       if (column != null) {
622         final double w = label.getWidth() + 8;
623         if (w > column.getWidth()) {
624           (new TableSupport()).setWidth(column, w, false);
625         }
626         column.setMinimumWidth(Math.max(
627                 w, ((TableGroupNodeRealizer) label.getRealizer())
628                         .getDefaultMinimumColumnWidth()));
629       }
630     }
631   }
632 
633 
634   //////////////////////////////////////////////////////////////////////////////////////////////////////
635   /////////////////////////////   Class AlternatingPainter    //////////////////////////////////////////
636   //////////////////////////////////////////////////////////////////////////////////////////////////////
637 
638   /**
639    * A background {@link y.view.GenericNodeRealizer.Painter Painter} for rows in
640    * a table that paints all inner or parent rows (i.e. rows for which
641    * <code>getRows()</code> returns a non-empty list) in a given color and
642    * alternates between two different colors for leaf rows (i.e. rows for which
643    * <code>getRows()</code> returns an empty list).
644    */
645   static final class AlternatingPainter extends ShapeNodePainter {
646     private static final Color PARENT_ROW_COLOR = new Color(113, 146, 178);
647     private static final Color EVEN_ROW_COLOR = new Color(196, 215, 237);
648     private static final Color ODD_ROW_COLOR = new Color(171, 200, 226);
649     private static final Color SELECTED_ROW_COLOR = new Color(55, 93, 129);
650 
651     AlternatingPainter() {
652       super(ShapeNodePainter.RECT);
653     }
654 
655     protected Paint getFillPaint( final NodeRealizer context, final boolean selected ) {
656       return getFillColor(context, selected);
657     }
658 
659     /**
660      * Determines the fill color for the row corresponding to the specified
661      * realizer.
662      * @param context a dummy realizer representing a row in a table.
663      * @param selected ignored.
664      * @return the fill color for the row corresponding to the specified
665      * realizer.
666      */
667     protected Color getFillColor( final NodeRealizer context, final boolean selected ) {
668       final Row row = TableNodePainter.getRow(context);
669       if (row.isSelected()) {
670         return SELECTED_ROW_COLOR;
671       }
672 
673       if (row.getRows().isEmpty()) {
674         if (indexOf(row, TableNodePainter.getTable(context).getRows(), new int[]{-1}) % 2 == 0) {
675           return EVEN_ROW_COLOR;
676         } else {
677           return ODD_ROW_COLOR;
678         }
679       } else {
680         return PARENT_ROW_COLOR;
681       }
682     }
683 
684     /**
685      * Calls the various utility method and callbacks in this class.
686      */
687     public void paint(NodeRealizer context, Graphics2D graphics) {
688       if (!context.isVisible()){
689         return;
690       }
691       backupGraphics(graphics);
692       try {
693         paintNode(context, graphics, false);
694       } finally {
695         restoreGraphics(graphics);
696       }
697     }
698 
699     /**
700      * Determines the leaf index of the specified row.
701      * @param row    the <code>Row</code> to search for.
702      * @param i      used to track the number of previously visited leaf rows.
703      *  (<code>int[]</code> is used as poor man's mutable <code>Integer</code>.)
704      * @return the leaf index of the specified row.
705      */
706     private int indexOf( final Row row, final Collection rows, final int[] i ) {
707       for (Iterator it = rows.iterator(); it.hasNext();) {
708         final Row r = (Row) it.next();
709         final List children = r.getRows();
710         if (children.isEmpty()) {
711           ++i[0];
712           if (r.equals(row)) {
713             return i[0];
714           }
715         } else {
716           final int idx = indexOf(row, children, i);
717           if (idx > -1) {
718             return idx;
719           }
720         }
721       }
722 
723       return -1;
724     }
725   }
726 
727   //////////////////////////////////////////////////////////////////////////////////////////////////////
728   /////////////////////////////   Class DropDrawable    ////////////////////////////////////////////////
729   //////////////////////////////////////////////////////////////////////////////////////////////////////
730 
731   /**
732    * This class is used to render a representative of a row or a column either as {@link y.view.Drawable} during drag'n'drop
733    * gestures or as {@link javax.swing.Icon} in the drag'n'drop list.
734    */
735   static class DropDrawable implements Drawable, Icon {
736     Rectangle bounds;
737     YInsets insets;
738 
739     Color borderColor;
740     Color fillColor;
741     Stroke stroke;
742 
743     /**
744      * Creates a new instance using the specified colors and stroke.
745      *
746      * @param borderColor The color used for the stripe border.
747      * @param fillColor The fill color of the stripe.
748      * @param stroke The stroke used for the border.
749      */
750     DropDrawable(Color borderColor, Color fillColor, Stroke stroke) {
751       this.borderColor = borderColor;
752       this.fillColor = fillColor;
753       this.stroke = stroke;
754     }
755 
756     /**
757      * Called from classes using the {@link y.view.Drawable} interface.
758      * It delegates to {@link #paintIcon(java.awt.Component, java.awt.Graphics, int, int)}.
759      *
760      * @param g The graphics object to render on.
761      */
762     public void paint(Graphics2D g) {
763       g.setStroke(stroke);
764       paintIcon(null, g, bounds.x, bounds.y);
765     }
766 
767     /**
768      * Called from classes using the {@link javax.swing.Icon} interface and from {@link #paint(java.awt.Graphics2D)}.
769      *
770      * @param c The component the icon shall be rendered in.
771      * @param g The graphics object to render on.
772      * @param x The horizontal coordinate of the icon.
773      * @param y The vertical coordinate of the icon.
774      */
775     public void paintIcon(Component c, Graphics g, int x, int y) {
776       // update the bounds if necessary
777       if (bounds == null ||
778           bounds.getX() != x ||
779           bounds.getY() != y) {
780         int newWidth = (bounds == null) ? 0 : bounds.width;
781         int newHeight = (bounds == null) ? 0 : bounds.height;
782         bounds = new Rectangle(x, y, newWidth, newHeight);
783       }
784 
785       // if the stripe shall be painted as an icon, it shall be horizontally centered in it's containing component.
786       int cWidth = 0;
787       if (c != null) {
788         cWidth = c.getWidth() - 2;
789       }
790       int offX = (cWidth > getIconWidth()) ? (cWidth - getIconWidth())/2 : 0;
791 
792       g.setColor(fillColor);
793       g.fillRect(bounds.x + offX, bounds.y, bounds.width, bounds.height);
794 
795       g.setColor(borderColor);
796       g.drawRect(bounds.x + offX, bounds.y, bounds.width, bounds.height);
797 
798       if (insets != null &&
799           (insets.top + insets.bottom < bounds.height &&
800            insets.left + insets.right < bounds.width)) {
801         g.setColor(fillColor);
802         g.fillRect((int) (bounds.x + offX + insets.left),
803                 (int) (bounds.y + insets.top),
804                 (int) (bounds.width - insets.left - insets.right),
805                 (int) (bounds.height - insets.top - insets.bottom));
806         g.setColor(borderColor);
807         g.drawRect((int) (bounds.x + offX + insets.left),
808                 (int) (bounds.y + insets.top),
809                 (int) (bounds.width - insets.left - insets.right),
810                 (int) (bounds.height - insets.top - insets.bottom));
811       }
812     }
813 
814     public Rectangle getBounds() {
815       return bounds;
816     }
817 
818     /**
819      * Sets the specified <code>bounds</code>.
820      * @param bounds The new bounds of the drawable.
821      */
822     public void setBounds(Rectangle2D bounds) {
823       this.bounds = new Rectangle((int) bounds.getX(), (int) bounds.getY(),
824                           (int) Math.ceil(bounds.getWidth()), (int) Math.ceil(bounds.getHeight()));
825     }
826 
827     /**
828      * Sets the bounds to the specified values.
829      * @param x The horizontal coordinate.
830      * @param y The vertical coordinate.
831      * @param width The width of the drawable.
832      * @param height The height of the drawable.
833      */
834     public void setBounds(int x, int y, int width, int height) {
835       this.bounds = new Rectangle(x, y, width, height);
836     }
837 
838     public int getIconWidth() {
839       return bounds != null ? bounds.width : 0;
840     }
841 
842     public int getIconHeight() {
843       return bounds != null ? bounds.height : 0;
844     }
845   }
846 
847   /**
848    * Cell renderer for the drop item list that is used as DnD source to create
849    * new nodes, columns, and rows.
850    */
851   static class DropItemListCellRenderer implements ListCellRenderer {
852     /**
853      * Value type constant representing a {@link y.view.tabular.TableGroupNodeRealizer.Row}.
854      */
855     static final Object DROP_TYPE_ROW = "DROP_TYPE_ROW";
856     /**
857      * Value type constant representing a {@link y.view.tabular.TableGroupNodeRealizer.Column}.
858      */
859     static final Object DROP_TYPE_COLUMN = "DROP_TYPE_COLUMN";
860 
861 
862     private static final Dimension PREFERRED_SIZE = new Dimension(100, 100);
863 
864     private final DefaultListCellRenderer dlcr;
865     private final RealizerCellRenderer realizerRenderer;
866 
867     private final Icon rowIcon;
868     private final Icon columnIcon;
869 
870     /**
871      * Creates a new <code>DropItemListCellRenderer</code>.
872      *
873      * @param columnIcon   the icon to display {@link #DROP_TYPE_COLUMN} values.
874      * @param rowIcon      the icon to display {@link #DROP_TYPE_COLUMN} values.
875      */
876     DropItemListCellRenderer(
877             final Icon columnIcon,
878             final Icon rowIcon
879     ) {
880       this.columnIcon = columnIcon;
881       this.rowIcon = rowIcon;
882 
883       realizerRenderer = new RealizerCellRenderer(
884               PREFERRED_SIZE.width, PREFERRED_SIZE.height);
885       dlcr = new DefaultListCellRenderer();
886     }
887 
888     public Component getListCellRendererComponent(
889             JList list,
890             Object value,
891             int index,
892             boolean isSelected,
893             boolean cellHasFocus
894     ) {
895       if (value instanceof NodeRealizer) {
896         final Component c = realizerRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
897         if (c instanceof JComponent) {
898           if (value instanceof GenericNodeRealizer) {
899             final String configuration = ((GenericNodeRealizer) value).getConfiguration();
900             if (SwimlaneDemo.CONFIGURATION_GROUP_NODE.equals(configuration)) {
901               ((JComponent) c).setToolTipText("Create new group node");
902             }
903             if (SwimlaneDemo.CONFIGURATION_TABLE_NODE.equals(configuration)) {
904               ((JComponent) c).setToolTipText("Create new table node");
905             }
906             if (DemoDefaults.NODE_CONFIGURATION.equals(configuration)) {
907               ((JComponent) c).setToolTipText("Create new child node");
908             }
909           } else {
910             ((JComponent) c).setToolTipText("Create new node");
911           }
912         }
913         return c;
914       } else {
915         dlcr.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
916         dlcr.setText("");
917         dlcr.setPreferredSize(PREFERRED_SIZE);
918         if (DROP_TYPE_COLUMN.equals(value)) {
919           dlcr.setIcon(columnIcon);
920           dlcr.setToolTipText("Create new column");
921         } else if (DROP_TYPE_ROW.equals(value)) {
922           dlcr.setIcon(rowIcon);
923           dlcr.setToolTipText("Create new row");
924         } else {
925           dlcr.setIcon(null);
926           dlcr.setToolTipText(null);
927         }
928         return dlcr;
929       }
930     }
931   }
932 }
933