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 y.base.Node;
17  import y.base.NodeList;
18  import y.view.Graph2D;
19  import y.view.Graph2DView;
20  import y.view.Graph2DViewActions;
21  import y.view.hierarchy.HierarchyManager;
22  
23  import javax.swing.ActionMap;
24  import javax.swing.InputMap;
25  import javax.swing.JPopupMenu;
26  import javax.swing.KeyStroke;
27  import javax.swing.JMenuItem;
28  import javax.swing.JMenu;
29  import java.awt.event.ActionEvent;
30  import java.awt.event.InputEvent;
31  import java.awt.event.KeyEvent;
32  import java.awt.EventQueue;
33  import java.util.HashSet;
34  import java.util.Locale;
35  import java.util.Set;
36  
37  /**
38   * This demo shows how to implement actions for navigation between different hierarchy levels.
39   * <p>
40   * This demo creates two actions (along with corresponding key bindings):
41   * </p>
42   * <ul>
43   * <li>EnterGroupAction navigates to the content of a group or folder node. This action is bound to <code>CTRL+PAGE_DOWN</code>.</li>
44   * <li>NavigateToParentAction navigates to the parent graph of an inner graph. This action is bound to <code>CTRL+PAGE_UP</code>.</li>
45   * </ul>
46   * <p>
47   * In addition, both the "Grouping" menu and the context menu provide these actions.
48   * </p>
49   * @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
50   * @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
51   */
52  public class GroupNavigationDemo extends GroupingDemo {
53    private static final String ENTER_GROUP_ACTION = "ENTER_GROUP_ACTION";
54    private static final String NAVIGATE_TO_PARENT_ACTION = "NAVIGATE_TO_PARENT_ACTION";
55  
56  
57    protected void registerViewActions() {
58      super.registerViewActions();
59  
60      //Register custom navigation actions and corresponding key bindings...
61      ActionMap actionMap = view.getCanvasComponent().getActionMap();
62      actionMap.put(ENTER_GROUP_ACTION, new EnterGroupAction(ENTER_GROUP_ACTION, view));
63      actionMap.put(NAVIGATE_TO_PARENT_ACTION, new NavigateToParentAction(NAVIGATE_TO_PARENT_ACTION, view));
64      InputMap inputMap = view.getCanvasComponent().getInputMap();
65      inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK), ENTER_GROUP_ACTION);
66      inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, InputEvent.CTRL_MASK), NAVIGATE_TO_PARENT_ACTION);
67    }
68  
69    static {
70      actionNames.put(ENTER_GROUP_ACTION, "Enter Group");
71      actionNames.put(NAVIGATE_TO_PARENT_ACTION, "Navigate to Parent");
72    }
73  
74    protected void populateGroupingPopup(JPopupMenu pm, double x, double y, final Node node, boolean selected) {
75      super.populateGroupingPopup(pm, x, y, node, selected);
76      pm.addSeparator();
77      //Actions for navigation actions
78      //We want to enter only the group oder folder node for which the popup has been triggered
79      //so we extend the action
80      JMenuItem item = new JMenuItem(new EnterGroupAction(ENTER_GROUP_ACTION, view){
81        protected Node getGroupOrFolderNode(Graph2D g) {
82          if(node != null) {
83            return node;
84          }
85          return super.getGroupOrFolderNode(g);
86        }
87      });
88      item.setText("Enter Group");
89      item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK));
90      if((node != null && !getHierarchyManager().isNormalNode(node)) || selected) {
91        item.setEnabled(true);
92      }
93      pm.add(item);
94      //We enable this context menu action only if we are not at root graph level
95      registerAction(pm, NAVIGATE_TO_PARENT_ACTION, view.getGraph2D() != getHierarchyManager().getRootGraph());
96    }
97  
98    protected void populateGroupingMenu(JMenu hierarchyMenu) {
99      super.populateGroupingMenu(hierarchyMenu);
100     hierarchyMenu.addSeparator();
101     //Actions for navigation actions
102     registerAction(hierarchyMenu, ENTER_GROUP_ACTION, true);
103     registerAction(hierarchyMenu, NAVIGATE_TO_PARENT_ACTION, true);
104   }
105 
106   /**
107    * Launches this demo.
108    */
109   public static void main(String[] args) {
110     EventQueue.invokeLater(new Runnable() {
111       public void run() {
112         Locale.setDefault(Locale.ENGLISH);
113         initLnF();
114         (new GroupNavigationDemo()).start();
115       }
116     });
117   }
118 
119   private final Set openGroupNodes = new HashSet();//All group nodes with state "open" are saved within
120 
121   /**
122    * Create custom action that allow to navigate to the inner graph of a group/folder node.
123    *
124    * If the node is a group node, it is closed first, since separate inner graph instances exist only
125    * for folder nodes.
126    */
127   class EnterGroupAction extends Graph2DViewActions.AbstractGroupingAction {
128     public EnterGroupAction(String name, Graph2DView view) {
129       super(name, view);
130     }
131 
132     /**
133      * Returns the selected node if exactly one node is selected
134      */
135     protected Node getGroupOrFolderNode(Graph2D g) {
136       NodeList selectedNodes = new NodeList(g.selectedNodes());
137       if (selectedNodes.size() == 1) {
138         return selectedNodes.firstNode();
139       } else {
140         return null;
141       }
142     }
143 
144     public void actionPerformed(ActionEvent e) {
145       Graph2DView view = getView(e);
146       Graph2D g = view.getGraph2D();
147       Node groupNode = getGroupOrFolderNode(g);
148       if (groupNode != null) {
149         enterGroup(groupNode, view);
150       }
151     }
152 
153     /**
154      * Enter the group.
155      *
156      * @param groupOrFolderNode the group or folder node that is opened
157      */
158     public void enterGroup(Node groupOrFolderNode, Graph2DView view) {
159       Graph2D graph = view.getGraph2D();
160       HierarchyManager hierarchyManager = getHierarchyManager(graph);
161       if (hierarchyManager.isNormalNode(groupOrFolderNode)) {
162         //Do nothing for normal nodes
163         return;
164       }
165 
166       //Close (if it is a group), since actual inner graphs only exist for folder nodes.
167       if (hierarchyManager.isGroupNode(groupOrFolderNode)) {
168         //Remember state for later
169         openGroupNodes.add(groupOrFolderNode);
170         preNodeStateChange(groupOrFolderNode, graph);
171         getHierarchyManager(graph).closeGroup(groupOrFolderNode);
172         postNodeStateChange(groupOrFolderNode, graph);
173       }
174 
175       Graph2D folderGraph = (Graph2D) hierarchyManager.getInnerGraph(groupOrFolderNode);
176       //We change the graph in the view to the inner graph.
177       view.setGraph2D(folderGraph);
178 
179       // adapt view
180       view.fitContent();
181       view.getGraph2D().updateViews();
182     }
183   }
184 
185   /**
186    * Create custom action that allow to navigate to parent graph of the current graph (if any).
187    *
188    * If the node was a group node before the inner graph has been entered with {@link EnterGroupAction}, it
189    * will be reopened.
190    */
191   class NavigateToParentAction extends Graph2DViewActions.AbstractGroupingAction {
192     public NavigateToParentAction(String name, Graph2DView view) {
193       super(name, view);
194     }
195 
196     public void actionPerformed(ActionEvent e) {
197       Graph2DView view = getView(e);
198       Graph2D g = view.getGraph2D();
199       HierarchyManager hm = getHierarchyManager(g);
200       if (g == hm.getRootGraph()) {
201         //Do nothing if already at root level...
202         return;
203       }
204       navigateToParent(view);
205     }
206 
207     /**
208      * Navigate to the parent graph
209      */
210     public void navigateToParent(Graph2DView view) {
211       Graph2D graph = view.getGraph2D();
212       HierarchyManager hierarchyManager = getHierarchyManager(graph);
213 
214       //Retrieve the folder node that represents the inner 
215       Node folderNode = hierarchyManager.getAnchorNode(graph);
216 
217       Graph2D parentGraph = (Graph2D) hierarchyManager.getParentGraph(graph);
218       //We change the graph in the view to the parent graph.
219       this.view.setGraph2D(parentGraph);
220 
221       //Restore the original state of the node if it has been closed by EnterGroupAction
222       if (openGroupNodes.contains(folderNode)) {
223         preNodeStateChange(folderNode, graph);
224         getHierarchyManager(graph).openFolder(folderNode);
225         postNodeStateChange(folderNode, graph);
226       }
227       openGroupNodes.remove(folderNode);
228 
229       view.fitContent();
230       view.getGraph2D().setSelected(folderNode, true);
231       view.getGraph2D().updateViews();
232     }
233   }
234 }
235