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.layout.hierarchic;
29  
30  import demo.view.DemoBase;
31  import demo.view.DemoDefaults;
32  
33  import y.base.DataMap;
34  import y.base.EdgeCursor;
35  import y.base.EdgeList;
36  import y.base.Node;
37  import y.base.NodeCursor;
38  import y.geom.YInsets;
39  import y.layout.grouping.Grouping;
40  import y.layout.hierarchic.IncrementalHierarchicLayouter;
41  import y.layout.hierarchic.incremental.IncrementalHintsFactory;
42  import y.util.Maps;
43  import y.view.EditMode;
44  import y.view.GenericNodeRealizer;
45  import y.view.GenericNodeRealizer.Factory;
46  import y.view.Graph2D;
47  import y.view.Graph2DCanvas;
48  import y.view.Graph2DLayoutExecutor;
49  import y.view.HitInfo;
50  import y.view.LineType;
51  import y.view.NodeLabel;
52  import y.view.NodeRealizer;
53  import y.view.ShinyPlateNodePainter;
54  import y.view.SmartNodeLabelModel;
55  import y.view.TooltipMode;
56  import y.view.ViewMode;
57  import y.view.hierarchy.DefaultHierarchyGraphFactory;
58  import y.view.hierarchy.GenericGroupNodeRealizer;
59  import y.view.hierarchy.GroupLayoutConfigurator;
60  import y.view.hierarchy.HierarchyManager;
61  import y.view.tabular.TableGroupNodeRealizer;
62  import y.view.tabular.TableGroupNodeRealizer.Column;
63  import y.view.tabular.TableGroupNodeRealizer.Table;
64  import y.view.tabular.TableNodePainter;
65  import y.view.tabular.TableStyle;
66  
67  import java.awt.Color;
68  import java.awt.EventQueue;
69  import java.awt.event.ActionEvent;
70  import java.awt.event.MouseEvent;
71  import java.util.Locale;
72  import java.util.Map;
73  
74  import javax.swing.AbstractAction;
75  import javax.swing.JToolBar;
76  
77  /**
78   * This demo shows the effect of combining
79   * <code>IncrementalHierarchicLayouter</code>'s support for grouping and
80   * swim lanes.
81   * <p>
82   * Things to try:
83   * </p>
84   * <ul>
85   *   <li>
86   *     Drag a node or set of nodes into another swim lane.
87   *     This will automatically trigger an incremental layout calculation.
88   *   </li>
89   *   <li>
90   *     Create a new node. It will be assigned to either a new swim lane if
91   *     created to the left or right of the existing lanes or to the lane in
92   *     which the node's center lies.
93   *     This will automatically trigger an incremental layout calculation.
94   *   </li>
95   *   <li>
96   *     Open/close folder/group nodes. Upon closing a group node, the resulting
97   *     folder node will be assigned to the minimum swim lane of the group's
98   *     child nodes.
99   *     This will automatically trigger an incremental layout calculation.
100  *   </li>
101  * </ul>
102  *
103  */
104 public class SwimlaneGroupDemo extends IncrementalHierarchicGroupDemo {
105   private static final Color NODE_COLOR = DemoDefaults.DEFAULT_NODE_COLOR;
106   private static final Color NODE_GRADIENT_COLOR = Color.WHITE;
107   private static final Color NODE_LINE_COLOR = DemoDefaults.DEFAULT_NODE_LINE_COLOR;
108   private static final Color GROUP_NODE_COLOR = new Color(255, 255, 255, 127);
109   private static final Color GROUP_NODE_LINE_COLOR = DemoDefaults.DEFAULT_NODE_COLOR;
110   private static final Color GROUP_NODE_LABEL_COLOR = DemoDefaults.DEFAULT_NODE_COLOR;
111   private static final Color ODD_LANE_COLOR = DemoDefaults.DEFAULT_CONTRAST_COLOR;
112   private static final Color EVEN_LANE_COLOR = new Color(237, 247, 247);
113 
114   private static final String NODE_CONFIGURATION = "NODE_CONFIGURATION";
115   private static final String SWIMLANE_CONFIGURATION = "SWIMLANE_CONFIGURATION";
116 
117   static {
118     initConfigurations();
119   }
120 
121 
122   public SwimlaneGroupDemo() {
123     view.addViewMode(new TriggerIncrementalLayout());
124     configureRealizers(view.getGraph2D());
125     loadInitialGraph();
126   }
127 
128   /**
129    * Creates a sample graph to display initially.
130    */
131   protected void loadInitialGraph() {
132     final Graph2D graph = view.getGraph2D();
133     graph.clear();
134    
135     HierarchyManager hierarchy = getHierarchyManager();
136     
137     if (layouter != null && hierarchy != null) {
138       // create a dummy node that visualizes swim lanes
139       final TableGroupNodeRealizer tgnr = new TableGroupNodeRealizer();
140       tgnr.setConfiguration(SWIMLANE_CONFIGURATION);
141 
142       tgnr.setLabelText("Swimlane Pool");
143       NodeLabel label = tgnr.getLabel();
144       SmartNodeLabelModel labelModel = new SmartNodeLabelModel();
145       label.setLabelModel(labelModel,
146           labelModel.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_TOP));
147       // "removes" the label from the graph view,
148       // but keeps it in the tree component
149       tgnr.getLabel().setVisible(false);
150 
151       // configure swim lane colors
152       final TableStyle.SimpleStyle oddLane =
153           new TableStyle.SimpleStyle(null, null, ODD_LANE_COLOR);
154       tgnr.setStyleProperty(TableNodePainter.COLUMN_STYLE_ID, oddLane);
155       tgnr.setStyleProperty(TableNodePainter.COLUMN_SELECTION_STYLE_ID, oddLane);
156 
157       final TableStyle.SimpleStyle evenLane =
158           new TableStyle.SimpleStyle(null, null, EVEN_LANE_COLOR);
159       tgnr.setStyleProperty(TableNodePainter.ALTERNATE_COLUMN_STYLE_ID, evenLane);
160       tgnr.setStyleProperty(TableNodePainter.ALTERNATE_COLUMN_SELECTION_STYLE_ID, evenLane);
161 
162       final TableStyle.SimpleStyle none = new TableStyle.SimpleStyle();
163       tgnr.setStyleProperty(TableNodePainter.ROW_STYLE_ID, none);
164       tgnr.setStyleProperty(TableNodePainter.ROW_SELECTION_STYLE_ID, none);
165       tgnr.setStyleProperty(TableNodePainter.TABLE_STYLE_ID, none);
166       tgnr.setStyleProperty(TableNodePainter.TABLE_SELECTION_STYLE_ID, none);
167 
168       // configure swim lane insets and minimum size
169       tgnr.setDefaultColumnInsets(new YInsets(25, 5, 0, 5));
170       tgnr.setDefaultMinimumColumnWidth(50);
171       tgnr.setDefaultRowInsets(new YInsets(15, 0, 15, 0));
172 
173       // label swim lanes
174       final Column[] columns = new Column[9];
175       final Table table = tgnr.getTable();
176       for (int i = 0; i < columns.length; ++i) {
177         columns[i] = i == 0 ? table.getColumn(0) : table.addColumn();
178 
179         final NodeLabel nl = tgnr.createNodeLabel();
180         nl.setText("Lane " + (i + 1));
181         tgnr.configureColumnLabel(nl, columns[i], true, 0);
182         tgnr.addLabel(nl);
183       }
184 
185       tgnr.updateTableBounds();
186 
187 
188       final Node pool = hierarchy.createGroupNode(graph);
189       graph.setRealizer(pool, tgnr);
190 
191       final Node n00 = graph.createNode();
192       final Node n01 = graph.createNode();
193       final Node g03 = hierarchy.createGroupNode(graph);
194       final Node g04 = hierarchy.createGroupNode(graph);
195       final Node n05 = graph.createNode();
196       final Node n06 = graph.createNode();
197       final Node n07 = graph.createNode();
198       final Node g08 = hierarchy.createGroupNode(graph);
199       final Node n09 = graph.createNode();
200       final Node n10 = graph.createNode();
201       final Node n11 = graph.createNode();
202       final Node g12 = hierarchy.createGroupNode(graph);
203       final Node n13 = graph.createNode();
204       final Node n14 = graph.createNode();
205       final Node n15 = graph.createNode();
206       final Node n16 = graph.createNode();
207       final Node n17 = graph.createNode();
208       final Node g18 = hierarchy.createGroupNode(graph);
209       final Node n19 = graph.createNode();
210       final Node n20 = graph.createNode();
211       final Node n21 = graph.createNode();
212       final Node n22 = graph.createNode();
213       final Node n23 = graph.createNode();
214 
215       // configure node nesting hierarchy
216       hierarchy.setParentNode(n00, pool);
217       hierarchy.setParentNode(n01, pool);
218 
219       hierarchy.setParentNode(g03, pool);
220       hierarchy.setParentNode(g04, pool);
221       hierarchy.setParentNode(n05, pool);
222       hierarchy.setParentNode(n06, pool);
223       hierarchy.setParentNode(n07, pool);
224 
225       hierarchy.setParentNode(g08, g03);
226       hierarchy.setParentNode(n09, g03);
227       hierarchy.setParentNode(n10, g03);
228       hierarchy.setParentNode(n11, g03);
229 
230       hierarchy.setParentNode(g12, g08);
231       hierarchy.setParentNode(n13, g08);
232       hierarchy.setParentNode(n14, g08);
233 
234       hierarchy.setParentNode(n15, g12);
235       hierarchy.setParentNode(n16, g12);
236       hierarchy.setParentNode(n17, g12);
237 
238       hierarchy.setParentNode(g18, g04);
239       hierarchy.setParentNode(n19, g04);
240       hierarchy.setParentNode(n20, g04);
241 
242       hierarchy.setParentNode(n21, g18);
243       hierarchy.setParentNode(n22, g18);
244       hierarchy.setParentNode(n23, g18);
245 
246 
247       hierarchy.createEdge(n00, n01);
248       hierarchy.createEdge(n01, n06);
249       hierarchy.createEdge(n06, n07);
250       hierarchy.createEdge(n06, n05);
251       hierarchy.createEdge(n06, n20);
252       hierarchy.createEdge(n07, n11);
253       hierarchy.createEdge(n09, n05);
254       hierarchy.createEdge(n10, n05);
255       hierarchy.createEdge(n11, n09);
256       hierarchy.createEdge(n11, n14);
257       hierarchy.createEdge(n13, n09);
258       hierarchy.createEdge(n14, n13);
259       hierarchy.createEdge(n14, n15);
260       hierarchy.createEdge(n15, n13);
261       hierarchy.createEdge(n15, n17);
262       hierarchy.createEdge(n16, n13);
263       hierarchy.createEdge(n17, n16);
264       hierarchy.createEdge(n19, n05);
265       hierarchy.createEdge(n20, n19);
266       hierarchy.createEdge(n20, n21);
267       hierarchy.createEdge(n21, n22);
268       hierarchy.createEdge(n21, n23);
269       hierarchy.createEdge(n21, n05);
270       hierarchy.createEdge(n22, n05);
271       hierarchy.createEdge(n23, n05);
272 
273       // create initial swim lane affiliations for nodes
274       table.moveToColumn(n00, columns[8]);
275       table.moveToColumn(n01, columns[5]);
276       // g02
277       // g03
278       // g04
279       table.moveToColumn(n05, columns[5]);
280       table.moveToColumn(n06, columns[5]);
281       table.moveToColumn(n07, columns[1]);
282       // g08
283       table.moveToColumn(n09, columns[1]);
284       table.moveToColumn(n10, columns[0]);
285       table.moveToColumn(n11, columns[1]);
286       // g12
287       table.moveToColumn(n13, columns[2]);
288       table.moveToColumn(n14, columns[2]);
289       table.moveToColumn(n15, columns[3]);
290       table.moveToColumn(n16, columns[2]);
291       table.moveToColumn(n17, columns[4]);
292       // g18
293       table.moveToColumn(n19, columns[7]);
294       table.moveToColumn(n20, columns[6]);
295       table.moveToColumn(n21, columns[6]);
296       table.moveToColumn(n22, columns[6]);
297       table.moveToColumn(n23, columns[6]);
298 
299       // update node labels to display swim lane affiliation
300       initLabels(graph);
301 
302       layout();
303     }
304 
305     view.fitContent();
306     view.getGraph2D().updateViews();
307     getUndoManager().resetQueue();
308   }
309 
310   /**
311    * Updates node labels to display either group or folder state or
312    * for normal nodes the associated swim lane.
313    */
314   private void initLabels(final Graph2D graph) {
315     Node tableNode = null;
316     Table table = null;
317     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
318       final NodeRealizer nr = graph.getRealizer(nc.node());
319       if (nr instanceof TableGroupNodeRealizer) {
320         tableNode = nc.node();
321         table = ((TableGroupNodeRealizer) nr).getTable();
322         break;
323       }
324     }
325 
326     HierarchyManager hierarchy = getHierarchyManager();
327     
328     int grp = 0;
329     int fldr = 0;
330     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
331       if (nc.node() == tableNode) {
332         continue;
333       }
334             
335       if (hierarchy.isNormalNode(nc.node())) {
336         final Column column = table == null ? null : table.getColumn(nc.node());
337         if (column != null) {
338           graph.getRealizer(nc.node()).setLabelText(
339               Integer.toString(column.getIndex() + 1));
340         } else {
341           graph.getRealizer(nc.node()).setLabelText("");
342         }
343         NodeLabel label = graph.getRealizer(nc.node()).getLabel();
344         SmartNodeLabelModel model = new SmartNodeLabelModel();
345         label.setLabelModel(model, model.getDefaultParameter());
346       } else if (hierarchy.isGroupNode(nc.node())) {
347         graph.getRealizer(nc.node()).setLabelText(
348             "Group " + (++grp));
349       } else if (hierarchy.isFolderNode(nc.node())) {
350         graph.getRealizer(nc.node()).setLabelText(
351             "Folder " + (++fldr));
352       }
353     }
354   }
355 
356   /*
357   * #####################################################################
358   * overriden methods
359   * #####################################################################
360   */
361 
362   /**
363    * Overwritten to configure incremental layout to take swim lane pool nodes
364    * into account.
365    */
366   void layoutIncrementally() {
367     Graph2D graph = view.getGraph2D();
368 
369     layouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
370 
371     // create storage for both nodes and edges
372     DataMap incrementalElements = Maps.createHashedDataMap();
373     // configure the mode
374     final IncrementalHintsFactory ihf = layouter.createIncrementalHintsFactory();
375 
376     //prepare grouping information
377     final GroupLayoutConfigurator glc = new GroupLayoutConfigurator(graph);
378     glc.prepareAll();
379     final Grouping grouping = new Grouping(graph);
380 
381     //mark incremental elements
382     for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
383       Node n = nc.node();
384       incrementalElements.set(n, ihf.createLayerIncrementallyHint(nc.node()));
385       if (grouping.isGroupNode(n)) {
386         //also mark the group node's incoming/outgoing edges
387         EdgeList markedEdges = grouping.getEdgesGoingIn(n);
388         markedEdges.addAll(grouping.getEdgesGoingOut(n));
389         for (EdgeCursor ec = markedEdges.edges(); ec.ok(); ec.next()) {
390           incrementalElements.set(ec.edge(), ihf.createSequenceIncrementallyHint(ec.edge()));
391         }
392       }
393     }
394     grouping.dispose();
395     glc.restoreAll();
396 
397     for (EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next()) {
398       incrementalElements.set(ec.edge(), ihf.createSequenceIncrementallyHint(ec.edge()));
399     }
400     graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, incrementalElements);
401 
402     try {
403       final Graph2DLayoutExecutor layoutExecutor =
404               new Graph2DLayoutExecutor(Graph2DLayoutExecutor.ANIMATED);
405       layoutExecutor.setConfiguringTableNodeRealizers(true);
406       layoutExecutor.doLayout(view, layouter);
407     } finally {
408       graph.removeDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY);
409     }
410 
411     // update node labels to display swim lane affiliation
412     initLabels(graph);
413     graph.updateViews();
414   }
415 
416   /**
417    * Overwritten to configure layout to take swim lane pool nodes into account.
418    */
419   void layout() {
420     final Graph2D graph = view.getGraph2D();
421     layouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_FROM_SCRATCH);
422 
423     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor();
424     layoutExecutor.setConfiguringTableNodeRealizers(true);
425     layoutExecutor.doLayout(view, layouter);
426 
427     // update node labels to display swim lane affiliation
428     initLabels(graph);
429     graph.updateViews();
430   }
431 
432   /*
433   * #####################################################################
434   * GUI
435   * #####################################################################
436   */
437 
438   protected void addLayoutActions(JToolBar toolBar) {
439     toolBar.addSeparator();
440     toolBar.add(createActionControl(new AbstractAction(
441             "Layout", SHARED_LAYOUT_ICON) {
442       public void actionPerformed(ActionEvent e) {
443         layout();
444       }
445     }));
446   }
447 
448   protected EditMode createEditMode() {
449     final EditMode editMode = new EditMode() {
450       protected void nodeCreated(final Node v) {
451         layoutIncrementally();
452       }      
453     };
454     // listen for clicks on state icon +/- of group and folder nodes.  
455     editMode.getMouseInputMode().setNodeSearchingEnabled(true);
456 
457     // do not automatically create node labels as these are used to display
458     // swim lane affiliation of nodes
459     editMode.assignNodeLabel(false);
460 
461     // activate child node creation when clicking into group nodes
462     editMode.setChildNodeCreationEnabled(true);
463     
464     return editMode;
465   }
466 
467   /**
468    * Creates a custom {@link TooltipMode} which shows tooltips for nodes, edges and "lanes".
469    */
470   protected TooltipMode createTooltipMode() {
471     return new TooltipMode() {
472       protected String getNodeTip(Node node) {
473         NodeRealizer nodeRealizer = view.getGraph2D().getRealizer(node);
474         if (nodeRealizer instanceof TableGroupNodeRealizer) {
475           // get a tooltip text for the column where the mouse is located
476           MouseEvent event = getLastMoveEvent();
477           Graph2DCanvas canvas = (Graph2DCanvas) view.getCanvasComponent();
478           double x = canvas.translateX(event.getX());
479           double y = canvas.translateY(event.getY());
480           // the first label is the table title in this case, so start with the second one
481           int index = ((TableGroupNodeRealizer) nodeRealizer).getTable().columnAt(x,y).getIndex() + 1;
482           return nodeRealizer.getLabel(index).getText();
483         } else {
484           // use the default tooltip text for all other node types
485           return super.getNodeTip(node);
486         }
487       }
488     };
489   }
490 
491   void configureRealizers(final Graph2D graph) {
492     graph.setDefaultNodeRealizer(createDefaultNodeRealizer());
493     final DefaultHierarchyGraphFactory hgf =
494         (DefaultHierarchyGraphFactory) graph.getHierarchyManager().getGraphFactory();
495     hgf.setDefaultGroupNodeRealizer(createDefaultGroupNodeRealizer());
496     hgf.setDefaultFolderNodeRealizer(createDefaultFolderNodeRealizer());
497   }
498 
499   private NodeRealizer createDefaultNodeRealizer() {
500     Factory factory = GenericNodeRealizer.getFactory();
501     Map map = factory.createDefaultConfigurationMap();
502     ShinyPlateNodePainter painter = new ShinyPlateNodePainter();
503     map.put(GenericNodeRealizer.Painter.class, painter);
504     map.put(GenericNodeRealizer.ContainsTest.class, painter);
505     factory.addConfiguration(NODE_CONFIGURATION, map);
506     GenericNodeRealizer gnr = new GenericNodeRealizer(NODE_CONFIGURATION);
507     gnr.setFillColor(NODE_COLOR);
508     gnr.setLineColor(NODE_LINE_COLOR);
509     gnr.setFillColor2(NODE_GRADIENT_COLOR);
510     gnr.setLineType(LineType.LINE_1);
511     return gnr;
512   }
513 
514   private NodeRealizer createDefaultGroupNodeRealizer() {
515     GenericGroupNodeRealizer defaultGroup = new GenericGroupNodeRealizer();
516     defaultGroup.setConfiguration(CONFIGURATION_GROUP);
517     defaultGroup.setSize(100, 60);
518     defaultGroup.setFillColor(GROUP_NODE_COLOR);
519     defaultGroup.setGroupClosed(false);
520     defaultGroup.setLineType(LineType.LINE_2);
521     defaultGroup.setLineColor(GROUP_NODE_LINE_COLOR);
522     defaultGroup.getLabel().setBackgroundColor(GROUP_NODE_LABEL_COLOR);
523     defaultGroup.getLabel().setTextColor(getBlackOrWhite(GROUP_NODE_LABEL_COLOR));   
524     return defaultGroup;
525   }
526 
527   private NodeRealizer createDefaultFolderNodeRealizer() {
528     GenericGroupNodeRealizer defaultFolder = new GenericGroupNodeRealizer();
529     defaultFolder.setConfiguration(CONFIGURATION_GROUP);    
530     defaultFolder.setSize(100, 60);
531     defaultFolder.setFillColor(GROUP_NODE_COLOR);
532     defaultFolder.setGroupClosed(true);
533     defaultFolder.setLineType(LineType.LINE_2);
534     defaultFolder.setLineColor(GROUP_NODE_LINE_COLOR);
535     defaultFolder.getLabel().setBackgroundColor(GROUP_NODE_LABEL_COLOR);
536     defaultFolder.getLabel().setTextColor(getBlackOrWhite(GROUP_NODE_LABEL_COLOR));
537     return defaultFolder;
538   }
539 
540   private Color getBlackOrWhite(Color c) {
541     if (c.getRed() + c.getGreen() + c.getBlue() > 3 * 127) {
542       return Color.BLACK;
543     } else {
544       return Color.WHITE;
545     }
546   }
547 
548 
549   public static void main(String[] args) {
550     EventQueue.invokeLater(new Runnable() {
551       public void run() {
552         Locale.setDefault(Locale.ENGLISH);
553         initLnF();
554         (new SwimlaneGroupDemo()).start();
555       }
556     });
557   }
558 
559 
560   /**
561    * Registers the configuration for the <code>TableGroupNodeRealizer</code>
562    * that is used to display swim lanes.
563    */
564   private static void initConfigurations() {
565     // create a configuration that uses alternating colors for swim lanes
566     final Map map = TableGroupNodeRealizer.createDefaultConfigurationMap();
567     map.put(TableGroupNodeRealizer.Painter.class,
568         TableNodePainter.newAlternatingColumnsInstance());
569     map.put(TableGroupNodeRealizer.GenericMouseInputEditorProvider.class, null);
570 
571     // register the configuration
572     TableGroupNodeRealizer.getFactory()
573         .addConfiguration(SWIMLANE_CONFIGURATION, map);
574   }
575 
576 
577   /**
578    * <code>ViewMode</code> that triggers an incremental layout calculation
579    * after node drag operations.
580    */
581   class TriggerIncrementalLayout extends ViewMode {
582     private boolean dragging;
583     private boolean hasHitNodes;
584 
585     TriggerIncrementalLayout() {
586       this.dragging = false;
587       this.hasHitNodes = false;
588     }
589 
590     public void mouseDraggedLeft(final double x, final double y) {
591       dragging = true;
592     }
593 
594     public void mousePressedLeft(final double x, final double y) {
595       getGraph2D().firePreEvent();
596       final HitInfo info = DemoBase.checkNodeHit(view, x, y);
597       hasHitNodes = info.hasHitNodes();
598     }
599 
600     public void mouseReleasedLeft(final double x, final double y) {
601       if (dragging && hasHitNodes) {
602         layoutIncrementally();
603       }
604       hasHitNodes = false;
605       dragging = false;
606       getGraph2D().firePostEvent();
607     }
608   }
609 }
610