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.hierarchy;
29  
30  import demo.view.DemoBase;
31  import y.algo.GraphConnectivity;
32  import y.algo.Trees;
33  import y.base.Graph;
34  import y.base.Node;
35  import y.base.NodeCursor;
36  import y.base.NodeList;
37  import y.layout.tree.BalloonLayouter;    
38  import y.layout.tree.TreeReductionStage; 
39  import y.layout.LayoutTool;
40  import demo.layout.module.HierarchicLayoutModule;  
41  import demo.layout.module.OrganicLayoutModule;     
42  import demo.layout.module.OrthogonalLayoutModule;  
43  import y.view.EditMode;
44  import y.view.Graph2D;
45  import y.view.Graph2DLayoutExecutor;     
46  import y.view.NodeLabel;
47  import y.view.NodeRealizer;
48  import y.view.Overview;
49  import y.view.PopupMode;
50  import y.view.ProxyShapeNodeRealizer;
51  import y.view.ViewMode;
52  import y.view.LineType;
53  import y.view.ShapeNodeRealizer;
54  import y.view.NodeStateChangeEdgeRouter;
55  import y.view.NodeStateChangeHandler;
56  import y.view.hierarchy.DefaultHierarchyGraphFactory;
57  import y.view.hierarchy.DefaultNodeChangePropagator;
58  import y.view.hierarchy.GroupNodeRealizer;
59  import y.view.hierarchy.HierarchyJTree;
60  import y.view.hierarchy.HierarchyManager;
61  import y.view.hierarchy.HierarchyTreeModel;
62  import y.view.hierarchy.HierarchyTreeTransferHandler;
63  import y.geom.YPoint;
64  
65  import javax.swing.AbstractAction;
66  import javax.swing.Action;
67  import javax.swing.JLabel;
68  import javax.swing.JMenu;
69  import javax.swing.JMenuBar;
70  import javax.swing.JMenuItem;
71  import javax.swing.JPanel;
72  import javax.swing.JPopupMenu;
73  import javax.swing.JScrollPane;
74  import javax.swing.JSplitPane;
75  import javax.swing.JToolBar;
76  import javax.swing.JTree;
77  import java.awt.BorderLayout;
78  import java.awt.Dimension;
79  import java.awt.Rectangle;
80  import java.awt.Color;
81  import java.awt.EventQueue;
82  import java.awt.geom.Rectangle2D;
83  import java.awt.event.ActionEvent;
84  import java.awt.event.MouseEvent;
85  import java.util.Locale;
86  
87  /**
88   * Demonstrates the use of <b>Nested Graph Hierarchy</b> technology and also
89   * <b>Node Grouping</b>.
90   * <p>
91   * <b>Note:</b>
92   * <br>
93   * This application demonstrates a legacy approach for interacting with nested
94   * graph hierarchies and node groups.
95   * Please refer to {@link demo.view.hierarchy.GroupingDemo} and
96   * {@link demo.view.hierarchy.GroupNavigationDemo} for the recommended approach
97   * as of yFiles for Java 2.7.
98   * </p>
99   * <p>
100  * The main view displays a nested graph hierarchy from a specific hierarchy level
101  * on downward.
102  * So-called folder nodes are used to nest graphs within them, so-called group nodes
103  * are used to group a set of nodes.
104  * <br>
105  * Both these types of node look similar but represent different concepts: while
106  * grouped nodes still belong to the same graph as their enclosing group node, the
107  * graph that is contained within a folder node is a separate entity.
108  * </p>
109  * <p>
110  * There are several ways provided to create, modify, and navigate a graph hierarchy:
111  * <ul>
112  * <li>
113  * By means of popup menu actions selected nodes can be grouped and also nested.
114  * Reverting these operations is also supported.
115  * </li>
116  * <li>
117  * By Shift-dragging nodes they can be moved into and out of group nodes.
118  * </li>
119  * <li>
120  * Double-clicking on a folder node "drills" into the nested graph hierarchy and
121  * displays only the folder node's content, i.e., effectively moves a level deeper
122  * in the hierarchy.
123  * A button in the tool bar allows to move back to see the folder node again (one
124  * level higher in the hierarchy).
125  * </li>
126  * <li>
127  * Folder node and group node both allow switching to the other type by either using
128  * popup menu actions or clicking the icon in their upper-left corner.
129  * </li>
130  * </ul>
131  * </p>
132  * <p>
133  * Note that the size of group nodes is determined by the space requirements of
134  * their content, i.e., their resizing behavior is restricted.
135  * </p>
136  * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/hier_mvc_model" target="_blank">Section Managing Graph Hierarchies</a> in the yFiles for Java Developer's Guide
137  * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/hier_mvc_controller" target="_blank">Section User Interaction</a> in the yFiles for Java Developer's Guide
138  */
139 public class HierarchyDemo extends DemoBase {
140 
141   /**
142    * The graph hierarchy manager. This is the central class for managing
143    * a hierarchy of graphs.
144    */
145   protected HierarchyManager hierarchy;
146   private HierarchicLayoutModule hierarchicLayoutModule;                                 
147   private OrthogonalLayoutModule orthogonalLayoutModule;                                 
148   private OrganicLayoutModule organicLayoutModule;                                       
149 
150 
151   /**
152    * Instantiates this demo. Builds the GUI.
153    */
154   public HierarchyDemo() {
155     Graph2D rootGraph = view.getGraph2D();
156 
157     //register a hierarchy listener that will automatically adjust the state of
158     //the realizers that are used for the group nodes
159     hierarchy.addHierarchyListener(new GroupNodeRealizer.StateChangeListener());
160 
161     //propagates text label changes on nodes as change events
162     //on the hierarchy.
163     rootGraph.addGraph2DListener(new DefaultNodeChangePropagator());
164 
165     //create a TreeModel, that represents the hierarchy of the nodes.
166     HierarchyTreeModel htm = new HierarchyTreeModel(hierarchy);
167 
168     //use a convenience comparator that sorts the elements in the tree model
169     htm.setChildComparator(HierarchyTreeModel.createNodeStateComparator(hierarchy));
170 
171     //display the graph hierarchy in a special JTree using the given TreeModel
172     JTree tree = new HierarchyJTree(hierarchy, htm);
173 
174     //add a double click listener to the tree.
175     tree.addMouseListener(new HierarchyJTreeDoubleClickListener(view));
176 
177     //add drag and drop functionality to HierarchyJTree. The drag and drop gesture
178     //will allow to reorganize the group structure using HierarchyJTree.
179     tree.setDragEnabled(true);
180     tree.setTransferHandler(new HierarchyTreeTransferHandler(hierarchy));
181 
182 
183     //add another view mode that acts upon clicking on
184     //a folder node and clicking on the open/close icon
185     view.addViewMode(new HierarchicClickViewMode());
186 
187     //plug the gui elements together and add them to the pane
188     JScrollPane scrollPane = new JScrollPane(tree);
189     scrollPane.setPreferredSize(new Dimension(150, 0));
190     JPanel leftPane = new JPanel(new BorderLayout());
191 
192     view.fitContent();
193 
194     Overview overView = new Overview(view);
195     overView.setPreferredSize(new Dimension(150, 150));
196     leftPane.add(overView, BorderLayout.NORTH);
197     leftPane.add(scrollPane);
198     leftPane.setMinimumSize(new Dimension(150,150));
199 
200     JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPane, view);
201 
202     contentPane.add(splitPane, BorderLayout.CENTER);
203 
204     loadInitialGraph();
205     //configure default graphics for default node realizers.
206     configureDefaultGroupNodeRealizers();
207   }
208 
209   protected void configureDefaultGroupNodeRealizers() {
210     //Create additional configuration for default group node realizers
211     DefaultHierarchyGraphFactory hgf = (DefaultHierarchyGraphFactory) hierarchy.getGraphFactory();
212 
213     GroupNodeRealizer gnr = new GroupNodeRealizer();
214     //Register first, since this will also configure the node label
215     gnr.setConsiderNodeLabelSize(true);
216 
217     //Nicer colors
218     gnr.setFillColor(new Color(202, 236, 255, 84));
219     gnr.setLineColor(Color.decode("#666699"));
220     gnr.setLineType(LineType.DOTTED_1);
221     gnr.getLabel().setBackgroundColor(Color.decode("#99CCFF"));
222     gnr.getLabel().setTextColor(Color.BLACK);
223     gnr.getLabel().setFontSize(15);
224     gnr.setShapeType(ShapeNodeRealizer.ROUND_RECT);
225 
226     hgf.setProxyNodeRealizerEnabled(true);
227 
228     hgf.setDefaultGroupNodeRealizer(gnr.createCopy());
229 
230     //Folder nodes have a different color
231     GroupNodeRealizer fnr = (GroupNodeRealizer) gnr.createCopy();
232 
233     fnr.setFillColor(Color.decode("#F2F0D8"));
234     fnr.setLineColor(Color.decode("#000000"));
235     fnr.getLabel().setBackgroundColor(Color.decode("#B7B69E"));
236     
237     hgf.setDefaultFolderNodeRealizer(fnr.createCopy());
238   }
239 
240   protected void loadInitialGraph() {
241     loadGraph("resource/hierarchy.graphml");
242   }
243 
244   protected void initialize() {
245     // create hierarchy manager before undo manager (for view actions)
246     // to ensure undo/redo works for grouped graphs
247     hierarchy = new HierarchyManager(view.getGraph2D());
248 
249     hierarchicLayoutModule = new HierarchicLayoutModule();                                            
250     orthogonalLayoutModule = new OrthogonalLayoutModule();                                            
251     organicLayoutModule = new OrganicLayoutModule();                                                  
252   }
253 
254   /**
255    * Creates a toolbar for this demo.
256    */
257   protected JToolBar createToolBar() {
258     final Action hierarchicalLayoutAction = new AbstractAction(                                       
259             "Hierarchical",SHARED_LAYOUT_ICON) {                                                      
260       public void actionPerformed(ActionEvent e) {                                                    
261         OptionSupport.showDialog(hierarchicLayoutModule, view.getGraph2D(), true, view.getFrame());   
262       }                                                                                               
263     };                                                                                                
264                                                                                                       
265     final Action layoutAction = new AbstractAction(                                                   
266             "Orthogonal", SHARED_LAYOUT_ICON) {                                                       
267       public void actionPerformed(ActionEvent e) {                                                    
268         OptionSupport.showDialog(orthogonalLayoutModule, view.getGraph2D(), true, view.getFrame());   
269       }                                                                                               
270     };                                                                                                
271                                                                                                       
272     final Action propertiesAction = new AbstractAction(                                               
273             "Organic", SHARED_LAYOUT_ICON) {                                                          
274       public void actionPerformed(ActionEvent e) {                                                    
275         OptionSupport.showDialog(organicLayoutModule, view.getGraph2D(), true, view.getFrame());      
276       }                                                                                               
277     };                                                                                                
278 
279     final JToolBar toolBar = super.createToolBar();
280 
281     toolBar.addSeparator();
282     toolBar.add(new ViewParentAction());
283 
284     toolBar.addSeparator();                                                                           
285     toolBar.add(new JLabel("Layout: "));                                                               
286     toolBar.add(createActionControl(hierarchicalLayoutAction));                                       
287     toolBar.add(createActionControl(layoutAction));                                                   
288     toolBar.add(createActionControl(propertiesAction));                                               
289 
290     return toolBar;
291   }
292 
293   /**
294    * Creates a menu bar for this demo.
295    */
296   protected JMenuBar createMenuBar() {
297     JMenuBar menuBar = super.createMenuBar();
298 
299     JMenu toolsMenu = new JMenu("Tools");
300     menuBar.add(toolsMenu);
301 
302     toolsMenu.add(new JMenuItem(new FoldComponentsAction()));
303     toolsMenu.add(new JMenuItem(new FoldSubtreesAction()));
304     toolsMenu.add(new JMenuItem(new UnfoldAllAction()));
305     toolsMenu.addSeparator();
306     toolsMenu.add(new JMenuItem(new LoadInitialGraphAction()));
307 
308     return menuBar;
309   }
310 
311   protected EditMode createEditMode() {
312     EditMode mode = super.createEditMode();
313     //add hierarchy actions to the views popup menu
314     mode.setPopupMode(new HierarchicPopupMode());
315     return mode;
316   }
317 
318   protected void registerViewModes() {
319     view.addViewMode(createEditMode());
320   }
321 
322 
323   /**
324    * Launches this demo.
325    */
326   public static void main(String[] args) {
327     EventQueue.invokeLater(new Runnable() {
328       public void run() {
329         Locale.setDefault(Locale.ENGLISH);
330         initLnF();
331         (new HierarchyDemo()).start("Hierarchy Demo");
332       }
333     });
334   }
335 
336 
337   //////////////////////////////////////////////////////////////////////////////
338   // VIEW MODES ////////////////////////////////////////////////////////////////
339   //////////////////////////////////////////////////////////////////////////////
340 
341   /**
342    * provides the context sensitive popup menus
343    */
344   class HierarchicPopupMode extends PopupMode {
345     public JPopupMenu getPaperPopup(double x, double y) {
346       return addFolderPopupItems(new JPopupMenu(), x, y, null, false);
347     }
348 
349     public JPopupMenu getNodePopup(Node v) {
350       Graph2D graph = getGraph2D();
351       return addFolderPopupItems(new JPopupMenu(),
352           graph.getCenterX(v),
353           graph.getCenterY(v),
354           v, true);
355     }
356 
357     public JPopupMenu getSelectionPopup(double x, double y) {
358       return addFolderPopupItems(new JPopupMenu(), x, y, null, getGraph2D().selectedNodes().ok());
359     }
360 
361     JPopupMenu addFolderPopupItems(JPopupMenu pm, double x, double y, Node node, boolean selected)
362     {
363       AbstractAction action;
364       action = new GroupSelectionAction(x, y);
365       pm.add(action);
366       action = new UngroupSelectionAction();
367       pm.add(action);
368       action = new CloseGroupAction(node);
369       action.setEnabled(node != null && hierarchy.isGroupNode(node));
370       pm.add(action);
371       pm.addSeparator();
372       action = new CreateFolderNodeAction(getGraph2D(), x, y);
373       action.setEnabled(node == null);
374       pm.add(action);
375       action = new FoldSelectionAction();
376       action.setEnabled(selected);
377       pm.add(action);
378       action = new UnfoldSelectionAction();
379       action.setEnabled(selected && !hierarchy.isRootGraph(getGraph2D()));
380       pm.add(action);
381       action = new ExtractFolderAction(node);
382       action.setEnabled(node != null && hierarchy.isFolderNode(node));
383       pm.add(action);
384       action = new OpenFolderAction(node);
385       action.setEnabled(node != null && hierarchy.isFolderNode(node));
386       pm.add(action);
387       action = new RemoveGroupAction(node);
388       action.setEnabled(node != null && hierarchy.isGroupNode(node));
389       pm.add(action);
390       return pm;
391     }
392   }
393 
394   /**
395    * view mode that allows to navigate to the inner graph of a folder node.
396    * a double click on a folder node triggers the action.
397    */
398   class HierarchicClickViewMode extends ViewMode {
399     public void mouseClicked(MouseEvent e) {
400       if (e.getClickCount() == 2) {
401         Node v = getHitInfo(e).getHitNode();
402         if (v != null) {
403           navigateToInnerGraph(v);
404         } else {
405           navigateToParentGraph();
406         }
407       } else {
408         Node v = getHitInfo(e).getHitNode();
409         if (v != null && !hierarchy.isNormalNode(v)) {
410           double x = translateX(e.getX());
411           double y = translateY(e.getY());
412           Graph2D graph = this.view.getGraph2D();
413           NodeRealizer r = graph.getRealizer(v);
414           GroupNodeRealizer gnr = null;
415           if (r instanceof GroupNodeRealizer) {
416             gnr = (GroupNodeRealizer) r;
417           } else if (r instanceof ProxyShapeNodeRealizer &&
418               ((ProxyShapeNodeRealizer) r).getRealizerDelegate() instanceof GroupNodeRealizer) {
419             gnr = (GroupNodeRealizer) ((ProxyShapeNodeRealizer) r).getRealizerDelegate();
420           }
421           if (gnr != null) {
422             NodeLabel handle = gnr.getStateLabel();
423             if (handle.getBox().contains(x, y)) {
424               if (hierarchy.isFolderNode(v)) {
425                 openFolder(v);
426               } else {
427                 closeGroup(v);
428               }
429             }
430           }
431         }
432       }
433     }
434   }
435 
436   //////////////////////////////////////////////////////////////////////////////
437   // OPERATIONS ////////////////////////////////////////////////////////////////
438   //////////////////////////////////////////////////////////////////////////////
439 
440   /**
441    * navigates to the graph inside of the given folder node
442    */
443   public void navigateToInnerGraph(Node folderNode) {
444     if (hierarchy.isFolderNode(folderNode)) {
445       Graph2D innerGraph = (Graph2D) hierarchy.getInnerGraph(folderNode);
446       Rectangle box = innerGraph.getBoundingBox();
447       view.setGraph2D(innerGraph);
448       view.setCenter(box.x + box.width / 2, box.y + box.height / 2);
449       innerGraph.updateViews();
450     }
451   }
452 
453   /**
454    * navigates to the parent graph of the graph currently displayed
455    * in the graph view.
456    */
457   public void navigateToParentGraph() {
458     Graph2D graph = view.getGraph2D();
459     if (!hierarchy.isRootGraph(graph)) {
460       Graph2D parentGraph = (Graph2D) hierarchy.getParentGraph(graph);
461       view.setGraph2D(parentGraph);
462       Node anchor = hierarchy.getAnchorNode(graph);
463       view.setZoom(1.0);
464       view.setCenter(parentGraph.getCenterX(anchor), parentGraph.getCenterY(anchor));
465       view.getGraph2D().updateViews();
466     }
467   }
468 
469   /**
470    * creates a new folder node and moves the subgraph induced by the
471    * current node selection to the inner graph of that folder node.
472    */
473   void foldSelection()
474   {
475     Graph2D graph = view.getGraph2D();
476     Node folderNode = hierarchy.createFolderNode(graph);
477     graph.setLabelText(folderNode, "Folder");
478 
479     hierarchy.foldSubgraph(new NodeList(graph.selectedNodes()), folderNode);
480 
481     Graph2D innerGraph = (Graph2D)hierarchy.getInnerGraph(folderNode);
482     innerGraph.unselectAll();
483 
484     Rectangle box = innerGraph.getBoundingBox();
485     graph.setSize(folderNode,box.width+10, box.height+10);
486     graph.setLocation(folderNode,box.x-5,box.y-5);
487     graph.updateViews();
488   }
489 
490 
491   /**
492    * moves the graph induced by the
493    * current node selection to the parent graph of the
494    * currently viewed graph.
495    */
496   void unfoldSelection()
497   {
498     Graph2D graph = view.getGraph2D();
499     NodeList selectedNodes = new NodeList(graph.selectedNodes());
500 
501     if(!selectedNodes.isEmpty() && !hierarchy.isRootGraph(graph))
502     {
503       hierarchy.unfoldSubgraph(graph, selectedNodes );
504     }
505     graph.updateViews();
506   }
507 
508   /**
509    * moves all nodes within the given folder node to the
510    * parent graph and removes the now empty folder node.
511    */
512   void extractFolder(Node folderNode) {
513     Graph2D graph = (Graph2D) folderNode.getGraph();
514     Graph innerGraph = hierarchy.getInnerGraph(folderNode);
515     NodeList subNodes = new NodeList(innerGraph.nodes());
516     hierarchy.unfoldSubgraph(innerGraph, subNodes);
517 
518     //cleanup  metaNode
519     //hierarchy.removeFolderNode(folderNode);
520     graph.removeNode(folderNode);
521 
522     //ok, some sugar follows...
523     for (NodeCursor nc = subNodes.nodes(); nc.ok(); nc.next()) {
524       graph.setSelected(nc.node(), true);
525     }
526 
527     graph.updateViews();
528   }
529 
530   /**
531    * folds each separate connected graph component to
532    * a newly created folder node,
533    */
534   void foldComponents() {
535     Graph2D graph = view.getGraph2D();
536     //remove all group nodes
537     for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
538       Node node = nodeCursor.node();
539       if (hierarchy.isGroupNode(node)) {
540         graph.removeNode(node);
541       }
542     }
543 
544     NodeList[] components = GraphConnectivity.connectedComponents(graph);
545 
546     if (components.length > 1) {
547       for (int i = 0; i < components.length; i++) {
548         NodeList subNodes = components[i];
549         Node folderNode = hierarchy.createFolderNode(graph);
550         graph.setCenter(folderNode, 150 * (i + 1), 0);
551         graph.setLabelText(folderNode, "Comp " + (i + 1));
552         hierarchy.setParentNode(subNodes, folderNode);
553       }
554       view.fitContent();
555       view.getGraph2D().updateViews();
556     }
557 
558   }
559 
560   /**
561    * This method finds tree-structures that are part of the
562    * displayed graph. For each of these trees a new
563    * folder-node will be created. Each tree will be
564    * moved from the displayed graph to the corresponding folder node.
565    * Each nested tree could be be automatically laid out using,
566    * for example, balloon layouter. The size-ratio of the folder-nodes will be
567    * automatically adjusted to the size of the nested trees.
568    * The code to automatically layout the subgraphs is commented out by default.
569    * If the yFiles layout package is available uncomment the lines again to
570    * activate the layouter.
571    */
572   void foldSubtrees() {
573     Graph2D graph = view.getGraph2D();
574 
575     //remove all group nodes
576     for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
577       Node node = nodeCursor.node();
578       if (hierarchy.isGroupNode(node)) {
579         graph.removeNode(node);
580       }
581     }
582 
583     NodeList[] trees = Trees.getTreeNodes(graph);
584 
585     for (int i = 0; i < trees.length; i++) {
586       NodeList tree = trees[i];
587 
588       Node folderNode = hierarchy.createFolderNode(graph);
589 
590       hierarchy.foldSubgraph(tree, folderNode);
591 
592       Graph2D innerGraph = (Graph2D) hierarchy.getInnerGraph(folderNode);
593 
594       BalloonLayouter balloonLayouter = new BalloonLayouter(); 
595       balloonLayouter.appendStage(new TreeReductionStage()); 
596       new Graph2DLayoutExecutor().doLayout(innerGraph, balloonLayouter); 
597 
598       //adjust label and size of folderNode
599       Node root = tree.firstNode();
600       String rootName = innerGraph.getLabelText(root);
601       graph.setLabelText(folderNode, rootName + " Tree");
602 
603       Rectangle box = innerGraph.getBoundingBox();
604       if (box.height > box.width) {
605         graph.setSize(folderNode, Math.max(150 * box.width / box.height, 40), 170);
606       } else {
607         graph.setSize(folderNode, 150, Math.max(40, 150 * box.height / box.width + 20));
608       }
609     }
610 
611     view.fitContent();
612     graph.updateViews();
613   }
614 
615   /**
616    * recursively unfold all folder nodes in the displayed view.
617    */
618   void unfoldAll()
619   {
620     NodeList result = hierarchy.getFolderNodes(view.getGraph2D(),true);
621     while(!result.isEmpty())
622     {
623       Node folderNode = result.popNode();
624       extractFolder(folderNode);
625     }
626   }
627 
628 
629   protected void closeGroup(Node groupNode)
630   {
631     Graph2D graph = view.getGraph2D();
632 
633     NodeList groupNodes = new NodeList();
634     if(groupNode == null)
635     {
636       //use selected top level groups
637       for(NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next())
638       {
639         Node v = nc.node();
640         if(hierarchy.isGroupNode(v) && hierarchy.getLocalGroupDepth(v) == 0)
641         {
642           groupNodes.add(v);
643         }
644       }
645     }
646     else
647     {
648       groupNodes.add(groupNode);
649     }
650 
651     graph.firePreEvent();
652     NodeStateChangeHandler stateChangeHandler = new NodeStateChangeEdgeRouter();
653     for(NodeCursor nc = groupNodes.nodes(); nc.ok(); nc.next())
654     {
655         stateChangeHandler.preNodeStateChange(nc.node());
656         hierarchy.closeGroup(nc.node());
657         stateChangeHandler.postNodeStateChange(nc.node());
658       }
659     graph.firePostEvent();
660 
661     graph.unselectAll();
662     for(NodeCursor nc = groupNodes.nodes(); nc.ok(); nc.next())
663     {
664       graph.setSelected(nc.node(), true);
665     }
666 
667     graph.updateViews();
668   }
669 
670   protected void removeGroup(Node groupNode) {
671     hierarchy.removeGroupNode(groupNode);
672   }
673 
674   protected void openFolder(Node folderNode)
675   {
676     Graph2D graph = view.getGraph2D();
677 
678     NodeList folderNodes = new NodeList();
679     if(folderNode == null)
680     {
681       //use selected top level groups
682       for(NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next())
683       {
684         Node v = nc.node();
685         if(hierarchy.isFolderNode(v))
686         {
687           folderNodes.add(v);
688         }
689       }
690     }
691     else
692     {
693       folderNodes.add(folderNode);
694     }
695 
696     graph.firePreEvent();
697 
698     NodeStateChangeHandler stateChangeHandler = new NodeStateChangeEdgeRouter();
699     
700     for(NodeCursor nc = folderNodes.nodes(); nc.ok(); nc.next())
701     {
702       //get original location of folder node
703       Graph2D innerGraph = (Graph2D)hierarchy.getInnerGraph(nc.node());
704       YPoint folderP = graph.getLocation(nc.node());
705       NodeList innerNodes = new NodeList(innerGraph.nodes());
706       stateChangeHandler.preNodeStateChange(nc.node());
707       hierarchy.openFolder(nc.node());
708 
709       //get new location of group node
710       Rectangle2D.Double gBox = graph.getRealizer(nc.node()).getBoundingBox();
711       //move grouped nodes to former location of folder node
712       LayoutTool.moveSubgraph(graph, innerNodes.nodes(),
713                               folderP.x - gBox.x,
714                               folderP.y - gBox.y);
715       stateChangeHandler.postNodeStateChange(nc.node());
716       }
717     graph.firePostEvent();
718 
719 
720     graph.unselectAll();
721     for(NodeCursor nc = folderNodes.nodes(); nc.ok(); nc.next())
722     {
723       graph.setSelected(nc.node(), true);
724     }
725 
726     graph.updateViews();
727   }
728 
729   void groupSelection(double x, double y)
730   {
731     Graph2D graph = view.getGraph2D();
732 
733     graph.firePreEvent();
734 
735     NodeList subNodes = new NodeList(graph.selectedNodes());
736     Node groupNode;
737     if(subNodes.isEmpty())
738     {
739       groupNode = hierarchy.createGroupNode(graph);
740       if(Double.isNaN(x)  || Double.isNaN(y))
741       {
742         x = view.getCenter().getX();
743         y = view.getCenter().getY();
744       }
745       graph.setCenter(groupNode, x,y);
746     }
747     else
748     {
749       Node nca = hierarchy.getNearestCommonAncestor(subNodes);
750       groupNode = hierarchy.createGroupNode(nca);
751       hierarchy.groupSubgraph(new NodeList(graph.selectedNodes()), groupNode);
752     }
753     graph.setLabelText(groupNode, "Group");
754     graph.firePostEvent();
755 
756     graph.unselectAll();
757     graph.setSelected(groupNode, true);
758 
759     graph.updateViews();
760   }
761 
762 
763   void ungroupSelection()
764   {
765     Graph2D graph = view.getGraph2D();
766     graph.firePreEvent();
767 
768     hierarchy.ungroupSubgraph(new NodeList(graph.selectedNodes()));
769 
770     graph.firePostEvent();
771 
772     graph.updateViews();
773   }
774 
775   void down(Node v)
776   {
777     Graph2D graph = view.getGraph2D();
778 
779     if(v == null)
780     {
781       NodeCursor nc = graph.selectedNodes();
782       if (nc.size() == 1) {
783         v = nc.node();
784       }
785     }
786 
787     if (hierarchy.isFolderNode(v)) {
788       Graph2D innerGraph = (Graph2D) hierarchy.getInnerGraph(v);
789       Rectangle box = innerGraph.getBoundingBox();
790       view.setGraph2D(innerGraph);
791       view.setCenter(box.x + box.width / 2, box.y + box.height / 2);
792       innerGraph.updateViews();
793     }
794   }
795 
796   void up() {
797     Graph2D graph = view.getGraph2D();
798     if (!hierarchy.isRootGraph(graph)) {
799       Graph2D parentGraph = (Graph2D) hierarchy.getParentGraph(graph);
800       view.setGraph2D(parentGraph);
801       Node anchor = hierarchy.getAnchorNode(graph);
802 
803       view.setZoom(1.0);
804       view.setCenter(parentGraph.getCenterX(anchor), parentGraph.getCenterY(anchor));
805       parentGraph.unselectAll();
806       parentGraph.setSelected(anchor, true);
807       view.getGraph2D().updateViews();
808     }
809   }
810 
811   //////////////////////////////////////////////////////////////////////////////
812   // ACTIONS ///////////////////////////////////////////////////////////////////
813   //////////////////////////////////////////////////////////////////////////////
814 
815   class FoldSubtreesAction extends AbstractAction {
816     FoldSubtreesAction() {
817       super("Fold Subtrees");
818     }
819 
820     public void actionPerformed(ActionEvent e) {
821       foldSubtrees();
822     }
823   }
824 
825   class FoldComponentsAction extends AbstractAction {
826     FoldComponentsAction() {
827       super("Fold Components");
828     }
829 
830     public void actionPerformed(ActionEvent e) {
831       foldComponents();
832     }
833   }
834 
835   class FoldSelectionAction extends AbstractAction
836   {
837     FoldSelectionAction()
838     {
839       super("Fold Selection");
840     }
841 
842     public void actionPerformed(ActionEvent e)
843     {
844       foldSelection();
845     }
846   }
847 
848   class UnfoldSelectionAction extends AbstractAction
849   {
850     UnfoldSelectionAction()
851     {
852       super("Unfold Selection");
853     }
854 
855     public void actionPerformed(ActionEvent e)
856     {
857       unfoldSelection();
858     }
859   }
860 
861   class UnfoldAllAction extends AbstractAction
862   {
863     UnfoldAllAction()
864     {
865       super("Unfold All");
866     }
867 
868     public void actionPerformed(ActionEvent e)
869     {
870       unfoldAll();
871     }
872   }
873 
874   class ExtractFolderAction extends AbstractAction
875   {
876     Node folderNode;
877 
878     ExtractFolderAction(Node folderNode) {
879       super("Extract Folder");
880       this.folderNode = folderNode;
881     }
882 
883     public void actionPerformed(ActionEvent e) {
884       extractFolder(folderNode);
885     }
886   }
887 
888   class ViewParentAction extends AbstractAction {
889     ViewParentAction() {
890       super("View Parent");
891     }
892 
893     public void actionPerformed(ActionEvent e) {
894       navigateToParentGraph();
895     }
896   }
897 
898   class CreateFolderNodeAction extends AbstractAction {
899     double x, y;
900     Graph2D graph;
901 
902     CreateFolderNodeAction(Graph2D graph, double x, double y) {
903       super("Create Folder");
904       this.graph = graph;
905       this.x = x;
906       this.y = y;
907     }
908 
909     public void actionPerformed(ActionEvent e) {
910       Node folderNode = hierarchy.createFolderNode(graph);
911       graph.setCenter(folderNode, x, y);
912       graph.setLabelText(folderNode, "Folder");
913       graph.updateViews();
914     }
915   }
916 
917   class CloseGroupAction extends AbstractAction
918   {
919     Node groupNode;
920     CloseGroupAction(Node groupNode)
921     {
922       super("Close Group");
923       this.groupNode = groupNode;
924     }
925 
926     public void actionPerformed(ActionEvent e)
927     {
928       closeGroup(groupNode);
929     }
930   }
931 
932   class OpenFolderAction extends AbstractAction
933   {
934     Node folderNode;
935     OpenFolderAction(Node folderNode)
936     {
937       super("Open Folder");
938       this.folderNode = folderNode;
939     }
940 
941     public void actionPerformed(ActionEvent e)
942     {
943       openFolder(folderNode);
944     }
945   }
946 
947   class RemoveGroupAction extends AbstractAction
948   {
949     Node groupNode;
950     RemoveGroupAction(Node groupNode)
951     {
952       super("Remove Group");
953       this.groupNode = groupNode;
954     }
955 
956     public void actionPerformed(ActionEvent e) {
957       removeGroup(groupNode);
958     }
959   }
960 
961 
962   class GroupSelectionAction extends AbstractAction
963     {
964     double x;
965     double y;
966     GroupSelectionAction(double x, double y)
967     {
968       super("Group Selection");
969       this.x = x;
970       this.y = y;
971     }
972 
973     public void actionPerformed(ActionEvent e)
974     {
975       groupSelection(x, y);
976     }
977   }
978 
979   class UngroupSelectionAction extends AbstractAction
980   {
981     UngroupSelectionAction()
982     {
983       super("Ungroup Selection");
984     }
985 
986     public void actionPerformed(ActionEvent e)
987     {
988       ungroupSelection();
989     }
990   }
991 
992   class UpAction extends AbstractAction
993   {
994     UpAction()
995     {
996       super("View Parent");
997     }
998 
999     public void actionPerformed(ActionEvent e) {
1000      up();
1001    }
1002  }
1003
1004  class DownAction extends AbstractAction {
1005    Node v;
1006
1007    DownAction(Node v) {
1008      super("View Folder");
1009      this.v = v;
1010    }
1011
1012    public void actionPerformed(ActionEvent e) {
1013      down(v);
1014    }
1015  }
1016
1017  class LoadInitialGraphAction extends AbstractAction {
1018    LoadInitialGraphAction() {
1019      super("Load Initial Graph");
1020    }
1021
1022    public void actionPerformed(ActionEvent ae) {
1023      view.getGraph2D().clear();
1024      loadInitialGraph();
1025      view.getGraph2D().updateViews();
1026    }
1027  }
1028}
1029