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.viewmode;
29  
30  import demo.view.DemoBase;
31  import y.view.Arrow;
32  import y.view.Graph2D;
33  import y.view.Graph2DView;
34  import y.view.Graph2DViewMouseWheelZoomListener;
35  import y.view.ImageNodeRealizer;
36  import y.view.NavigationComponent;
37  import y.view.NavigationMode;
38  import y.view.NodeRealizer;
39  import y.view.Overview;
40  
41  import javax.swing.AbstractAction;
42  import javax.swing.AbstractButton;
43  import javax.swing.Action;
44  import javax.swing.BorderFactory;
45  import javax.swing.Icon;
46  import javax.swing.JButton;
47  import javax.swing.JComponent;
48  import javax.swing.JFrame;
49  import javax.swing.JPanel;
50  import javax.swing.JToggleButton;
51  import javax.swing.JToolBar;
52  import javax.swing.JViewport;
53  import javax.swing.KeyStroke;
54  import javax.swing.ScrollPaneConstants;
55  import java.awt.BorderLayout;
56  import java.awt.Color;
57  import java.awt.Component;
58  import java.awt.Dimension;
59  import java.awt.DisplayMode;
60  import java.awt.EventQueue;
61  import java.awt.GraphicsDevice;
62  import java.awt.GraphicsEnvironment;
63  import java.awt.GridBagConstraints;
64  import java.awt.GridBagLayout;
65  import java.awt.GridLayout;
66  import java.awt.Insets;
67  import java.awt.event.ActionEvent;
68  import java.awt.event.ActionListener;
69  import java.awt.event.KeyEvent;
70  import java.awt.event.MouseAdapter;
71  import java.awt.event.MouseEvent;
72  import java.awt.geom.Point2D;
73  import java.util.Locale;
74  
75  /**
76   * This demo demonstrates the usage of {@link y.view.NavigationComponent} and {@link y.view.Overview}. Both controls
77   * will be added to a glass pane the view provides {@link y.view.Graph2DView#getGlassPane()} and can be toggled during
78   * runtime.
79   * <p/>
80   * Besides one can switch to a full screen mode and navigate through the graph view.
81   *
82   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/mvc_view#cls_NavigationComponent" target="_blank">Section Class NavigationComponents</a> in the yFiles for Java Developer's Guide
83   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/mvc_view#cls_OverView" target="_blank">Section Class Overview</a> in the yFiles for Java Developer's Guide
84   */
85  public class FullScreenNavigationDemo extends DemoBase {
86    protected Icon overviewIcon, navigationIcon;
87    private JComponent overview, navigationComponent;
88    private JToolBar toolBar;
89  
90    public FullScreenNavigationDemo() {
91      overviewIcon = createIcon("resource/overview_tool.png");
92      navigationIcon = createIcon("resource/navigation_tool.png");
93  
94      //add some controls to the glass pane
95      addGlassPaneComponents();
96  
97      //fill the toolbar
98      fillToolBar();
99  
100     //load an initial graph
101     loadGraph("resource/peopleNav_small.graphml");
102 
103     //set default edge arrow
104     Graph2D graph = view.getGraph2D();
105     graph.getDefaultEdgeRealizer().setArrow(Arrow.DELTA);
106 
107     //set a default node realizer (from the loaded graph
108     NodeRealizer realizer = graph.getRealizer(graph.getNodeArray()[10]);
109     if (realizer instanceof ImageNodeRealizer) {
110       ImageNodeRealizer inr = new ImageNodeRealizer();
111       inr.setImage(((ImageNodeRealizer) realizer).getImage());
112       inr.setSize(48, 48);
113       graph.setDefaultNodeRealizer(inr);
114     }
115 
116     //focus some nodes in the graph
117     view.focusView(1.1, new Point2D.Double(150, 750), false);
118   }
119 
120   protected Action createLoadAction() {
121     //Overridden method to disable the Load menu in the demo
122     //The load action for other graphs makes no sense, because the overview window is wrong for other graphs.
123     return null;
124   }
125 
126   protected Action createSaveAction() {
127     //Overridden method to disable the Save menu in the demo
128     return null;
129   }
130 
131   protected void registerViewModes() {
132     view.addViewMode(new NavigationMode());
133   }
134 
135   protected JToolBar createToolBar() {
136     toolBar = new JToolBar();
137     return toolBar;
138   }
139 
140   protected Icon createIcon(String resourceName) {
141     return getIconResource(resourceName);
142   }
143 
144   private void addGlassPaneComponents() {
145     //get the glass pane
146     JPanel glassPane = view.getGlassPane();
147     //set an according layout manager
148     glassPane.setLayout(new BorderLayout());
149 
150     JPanel toolsPanel = new JPanel(new GridBagLayout());
151     toolsPanel.setOpaque(false);
152     toolsPanel.setBackground(null);
153     toolsPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 0, 0));
154 
155     //create and add the overview to the tools panel
156     GridBagConstraints gbc = new GridBagConstraints();
157     gbc.gridx = 0;
158     gbc.anchor = GridBagConstraints.LINE_START;
159     gbc.insets = new Insets(0, 0, 16, 0);
160     overview = createOverviewComponent(view);
161     toolsPanel.add(overview, gbc);
162 
163     //create and add the navigation component to the tools panel
164     navigationComponent = createNavigationComponent(view, 20, 30);
165     toolsPanel.add(navigationComponent, gbc);
166 
167     //add the toolspanel to the glass pane
168     gbc.gridx = 0;
169     gbc.gridy = 0;
170     gbc.weightx = 1;
171     gbc.weighty = 1;
172     gbc.anchor = GridBagConstraints.FIRST_LINE_START;
173     JViewport viewport = new JViewport();
174     viewport.add(toolsPanel);
175     viewport.setOpaque(false);
176     viewport.setBackground(null);
177     JPanel westPanel = new JPanel(new BorderLayout());
178     westPanel.setOpaque(false);
179     westPanel.setBackground(null);
180     westPanel.add(viewport, BorderLayout.NORTH);
181     glassPane.add(westPanel, BorderLayout.WEST);
182   }
183 
184   private NavigationComponent createNavigationComponent(Graph2DView view, double scrollStepSize, int scrollTimerDelay) {
185     //create the NavigationComponent itself
186     final NavigationComponent navigation = new NavigationComponent(view);
187     navigation.setScrollStepSize(scrollStepSize);
188     //set the duration between scroll ticks
189     navigation.putClientProperty("NavigationComponent.ScrollTimerDelay", new Integer(scrollTimerDelay));
190     //set the initial duration until the first scroll tick is triggered
191     navigation.putClientProperty("NavigationComponent.ScrollTimerInitialDelay", new Integer(scrollTimerDelay));
192     //set a flag so that the fit content button will adjust the viewports in an animated fashion
193     navigation.putClientProperty("NavigationComponent.AnimateFitContent", Boolean.TRUE);
194 
195     //add a mouse listener that will make a semi transparent background, as soon as the mouse enters this component
196     navigation.setBackground(new Color(255, 255, 255, 0));
197     MouseAdapter navigationToolListener = new MouseAdapter() {
198       public void mouseEntered(MouseEvent e) {
199         super.mouseEntered(e);
200         Color background = navigation.getBackground();
201         //add some semi transparent background
202         navigation.setBackground(new Color(background.getRed(), background.getGreen(), background.getBlue(), 196));
203       }
204 
205       public void mouseExited(MouseEvent e) {
206         super.mouseExited(e);
207         Color background = navigation.getBackground();
208         //make the background completely transparent
209         navigation.setBackground(new Color(background.getRed(), background.getGreen(), background.getBlue(), 0));
210       }
211     };
212     navigation.addMouseListener(navigationToolListener);
213 
214     //add mouse listener to all sub components of the navigationComponent
215     for (int i = 0; i < navigation.getComponents().length; i++) {
216       Component component = navigation.getComponents()[i];
217       component.addMouseListener(navigationToolListener);
218     }
219 
220     return navigation;
221   }
222 
223   private Overview createOverview(Graph2DView view) {
224     Overview ov = new Overview(view);
225     /* customize the overview */
226     //animates the scrolling
227     ov.putClientProperty("Overview.AnimateScrollTo", Boolean.TRUE);
228     //blurs the part of the graph which can currently not be seen
229     ov.putClientProperty("Overview.PaintStyle", "Funky");
230     //allows zooming from within the overview
231     ov.putClientProperty("Overview.AllowZooming", Boolean.TRUE);
232     //provides functionality for navigation via keybord (zoom in (+), zoom out (-), navigation with arrow keys)
233     ov.putClientProperty("Overview.AllowKeyboardNavigation", Boolean.TRUE);
234     //determines how to differ between the part of the graph that can currently be seen, and the rest
235     ov.putClientProperty("Overview.Inverse", Boolean.TRUE);
236     return ov;
237   }
238 
239   JComponent createOverviewComponent(Graph2DView view) {
240     final Overview overview = createOverview(view);
241     final JPanel overviewPane = new JPanel(new GridLayout(1, 1));
242     overviewPane.setPreferredSize(new Dimension(150, 150));
243     overviewPane.setMinimumSize(new Dimension(150, 150));
244     overviewPane.setBorder(BorderFactory.createEtchedBorder());
245     overviewPane.add(overview);
246     return overviewPane;
247   }
248 
249   private void fillToolBar() {
250     //create and add the overview button to the toolbar
251     AbstractAction overviewAction = new ToggleComponentVisibilityAction(overview);
252     JToggleButton overviewButton = new JToggleButton(overviewAction);
253     configure(overviewButton, "Overview", "Toggle Overview", overviewIcon);
254     overviewButton.setSelected(true);
255     toolBar.add(overviewButton);
256 
257     AbstractAction navigationControlsAction = new ToggleComponentVisibilityAction(navigationComponent);
258     JToggleButton navigationButton = new JToggleButton(navigationControlsAction);
259     configure(navigationButton, "Navigation", "Toggle Navigation Controls", navigationIcon);
260     navigationButton.setSelected(true);
261     toolBar.add(navigationButton);
262 
263     toolBar.addSeparator();
264 
265     //add the fullscreen action to the toolbar
266     toolBar.add(new FullScreenAction(view.getGraph2D()));
267   }
268 
269 
270   static void configure(
271           final AbstractButton jb,
272           final String title, final String toolTipText, final Icon icon
273   ) {
274     if (icon == null) {
275       jb.setText(title);
276     } else {
277       jb.setIcon(icon);
278     }
279     jb.setToolTipText(toolTipText);
280     jb.setMargin(new Insets(2, 2, 2, 2));
281   }
282 
283 
284   /**
285    * Launches this demo.
286    *
287    * @param args args
288    */
289   public static void main(String[] args) {
290     EventQueue.invokeLater(new Runnable() {
291       public void run() {
292         Locale.setDefault(Locale.ENGLISH);
293         initLnF();
294         (new FullScreenNavigationDemo()).start("Full Screen Navigation Demo");
295       }
296     });
297   }
298 
299   /** An action that will toggle the visibility of the given component. */
300   static class ToggleComponentVisibilityAction extends AbstractAction {
301     private final Component component;
302 
303     public ToggleComponentVisibilityAction(Component component) {
304       super();
305       this.component = component;
306     }
307 
308     public void actionPerformed(ActionEvent e) {
309       component.setVisible(!component.isVisible());
310     }
311   }
312 
313   /** displays the current graph in full screen mode. */
314   class FullScreenAction extends AbstractAction {
315     private Graph2DView view;
316     private JFrame frame;
317     private int scrollStepSize = 15;
318     private int scrollTimerDelay = 5;
319     private final Graph2D graph2D;
320     private Icon closeIcon;
321 
322     /**
323      * creates an instance.
324      *
325      * @param graph2D the graph this action is created for
326      */
327     public FullScreenAction(Graph2D graph2D) {
328       super("Full Screen");
329       closeIcon = createIcon("resource/close.png");
330 
331       this.putValue(Action.SMALL_ICON, createIcon("resource/fullscreen.png"));
332       this.putValue(Action.SHORT_DESCRIPTION, "Fullscreen Mode");
333       this.graph2D = graph2D;
334     }
335 
336     /** @return the current step size for scrolling the graph with the navigation component in the full screen view. */
337     public int getScrollStepSize() {
338       return scrollStepSize;
339     }
340 
341     /**
342      * sets the step size for scrolling the graph with the navigation component in the full screen view.
343      *
344      * @param scrollStepSize the step size for scrolling
345      */
346     public void setScrollStepSize(int scrollStepSize) {
347       this.scrollStepSize = scrollStepSize;
348     }
349 
350     /** @return the current delay between two scroll events of the navigation component in the full screen view. */
351     public int getScrollTimerDelay() {
352       return scrollTimerDelay;
353     }
354 
355     /**
356      * sets the delay between two scroll events of the navigation component in the full screen view.
357      *
358      * @param scrollTimerDelay the delay in ms
359      */
360     public void setScrollTimerDelay(int scrollTimerDelay) {
361       this.scrollTimerDelay = scrollTimerDelay;
362     }
363 
364     private void showFrame() {
365       frame.setVisible(true);
366       view.fitContent();
367     }
368 
369     private boolean addGraphView() {
370       createGraphView();
371       if (view != null) {
372         frame.getRootPane().setContentPane(view);
373         return true;
374       } else {
375         return false;
376       }
377     }
378 
379     private void addGlassPaneComponents() {
380       //get the glass pane
381       JPanel glassPane = view.getGlassPane();
382       //set n according layout
383       glassPane.setLayout(new GridBagLayout());
384 
385       JPanel toolsPanel = new JPanel(new GridBagLayout());
386       toolsPanel.setOpaque(false);
387       toolsPanel.setBackground(null);
388 
389       //create the overview
390       JComponent overview = createOverviewComponent(view);
391       //create the navigation component
392       NavigationComponent navigationComponent = createNavigationComponent(view, scrollStepSize, scrollTimerDelay);
393 
394       //create the inner toolbar and add
395       JPanel innerToolbar = createInnerToolbar(overview, navigationComponent);
396       GridBagConstraints gbc = new GridBagConstraints();
397       gbc.gridx = 0;
398       gbc.anchor = GridBagConstraints.LINE_END;
399       toolsPanel.add(innerToolbar, gbc);
400 
401       //add the overview
402       gbc.gridy = 1;
403       gbc.insets = new Insets(16, 0, 0, 0);
404       toolsPanel.add(overview, gbc);
405 
406       //add the navigation component
407       gbc.gridx = 0;
408       gbc.gridy = 0;
409       gbc.anchor = GridBagConstraints.FIRST_LINE_START;
410       gbc.insets = new Insets(11, 11, 0, 0);
411       navigationComponent.setPreferredSize(new Dimension((int) navigationComponent.getPreferredSize().getWidth(), 300));
412       glassPane.add(navigationComponent, gbc);
413 
414       gbc.gridx = 0;
415       gbc.gridy = 0;
416       gbc.weightx = 1;
417       gbc.weighty = 1;
418       gbc.anchor = GridBagConstraints.FIRST_LINE_END;
419       gbc.insets = new Insets(16, 0, 0, 16);
420       glassPane.add(toolsPanel, gbc);
421     }
422 
423     private JPanel createInnerToolbar(final JComponent overview, final NavigationComponent navigationComponent) {
424       GridBagConstraints gbc = new GridBagConstraints();
425 
426       JPanel headPanel = new JPanel(new GridBagLayout());
427       headPanel.setBackground(Color.WHITE);
428 
429       //overview toggle
430       final JToggleButton overviewButton = new JToggleButton(new ToggleComponentVisibilityAction(overview));
431       configure(overviewButton, "Overview", "Toggle Overview", overviewIcon);
432       gbc.weightx = 0;
433       gbc.anchor = GridBagConstraints.LINE_END;
434       gbc.insets = new Insets(0, 1, 0, 1);
435       headPanel.add(overviewButton, gbc);
436 
437       //navigation controls toggle
438       final JToggleButton navigateButton = new JToggleButton(new ToggleComponentVisibilityAction(navigationComponent));
439       configure(navigateButton, "Navigation", "Toggle Navigation Controls", navigationIcon);
440       headPanel.add(navigateButton, gbc);
441 
442       //close button to leave fullscreen mode
443       JButton closeButton = new JButton(new AbstractAction() {
444         public void actionPerformed(ActionEvent e) {
445           closeFrame();
446         }
447       });
448       configure(closeButton, "Close", "Leave Fullscreen Mode (ESC)", closeIcon);
449       headPanel.add(closeButton, gbc);
450 
451       return headPanel;
452     }
453 
454     private void createFrame() {
455       frame = new JFrame();
456       frame.setResizable(false);
457       if (!frame.isDisplayable()) {
458         frame.setUndecorated(true);
459       }
460       frame.getContentPane().setLayout(new BorderLayout());
461       GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
462       DisplayMode mode = gd.getDisplayMode();
463       frame.setSize(mode.getWidth(), mode.getHeight());
464     }
465 
466     private void createGraphView() {
467       view = new Graph2DView(graph2D);
468       view.setAntialiasedPainting(true);
469       new Graph2DViewMouseWheelZoomListener().addToCanvas(view);
470       view.addViewMode(new NavigationMode());
471       //view.addViewMode(new NavigationMode());
472       view.setScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
473           ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
474     }
475 
476     private void addEscapeListener() {
477       KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
478       view.registerKeyboardAction(new ActionListener() {
479         public void actionPerformed(ActionEvent e) {
480           closeFrame();
481         }
482       }, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
483     }
484 
485     private void closeFrame() {
486       if (frame != null) {
487         frame.setVisible(false);
488         frame.dispose();
489       }
490       if (view != null) {
491         view.getGraph2D().removeView(view);
492       }
493       view = null;
494       frame = null;
495     }
496 
497     public void actionPerformed(ActionEvent e) {
498       // Close former full screen, if it is still open.
499       closeFrame();
500       // Create new full screen.
501       createFrame();
502       // Add view for current graph.
503       if (addGraphView()) {
504         // If adding the view was successful, decorate it and show the full screen.
505         addEscapeListener();
506         addGlassPaneComponents();
507         showFrame();
508       }
509     }
510   }
511 }
512