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