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