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