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;
15  
16  import y.io.GraphMLIOHandler;
17  import y.io.IOHandler;
18  import y.module.YModule;
19  import y.option.DefaultEditorFactory;
20  import y.option.Editor;
21  import y.option.EditorFactory;
22  import y.option.GuiFactory;
23  import y.option.OptionHandler;
24  import y.util.D;
25  import y.view.AutoDragViewMode;
26  import y.view.CreateEdgeMode;
27  import y.view.EditMode;
28  import y.view.Graph2D;
29  import y.view.Graph2DPrinter;
30  import y.view.Graph2DView;
31  import y.view.Graph2DViewActions;
32  import y.view.Graph2DViewMouseWheelZoomListener;
33  import y.view.MoveLabelMode;
34  import y.view.MovePortMode;
35  import y.view.MoveSelectionMode;
36  import y.view.MoveSnapContext;
37  import y.view.OrthogonalMoveBendsMode;
38  import y.view.HotSpotMode;
39  import y.view.TooltipMode;
40  import y.view.View2DConstants;
41  import y.view.DropSupport;
42  
43  import javax.swing.AbstractAction;
44  import javax.swing.Action;
45  import javax.swing.ActionMap;
46  import javax.swing.BorderFactory;
47  import javax.swing.Icon;
48  import javax.swing.ImageIcon;
49  import javax.swing.InputMap;
50  import javax.swing.JButton;
51  import javax.swing.JComponent;
52  import javax.swing.JDialog;
53  import javax.swing.JEditorPane;
54  import javax.swing.JFileChooser;
55  import javax.swing.JFrame;
56  import javax.swing.JMenu;
57  import javax.swing.JMenuBar;
58  import javax.swing.JPanel;
59  import javax.swing.JRootPane;
60  import javax.swing.JScrollPane;
61  import javax.swing.JToolBar;
62  import javax.swing.JOptionPane;
63  import javax.swing.KeyStroke;
64  import javax.swing.filechooser.FileFilter;
65  import java.awt.BorderLayout;
66  import java.awt.Dimension;
67  import java.awt.EventQueue;
68  import java.awt.FlowLayout;
69  import java.awt.Frame;
70  import java.awt.Rectangle;
71  import java.awt.Color;
72  import java.awt.event.ActionEvent;
73  import java.awt.event.ActionListener;
74  import java.awt.print.PageFormat;
75  import java.awt.print.PrinterJob;
76  import java.io.File;
77  import java.io.IOException;
78  import java.net.MalformedURLException;
79  import java.net.URI;
80  import java.net.URISyntaxException;
81  import java.net.URL;
82  import java.util.Locale;
83  import java.util.regex.Pattern;
84  
85  /**
86   * Abstract base class for GUI- and <code>Graph2DView</code>-based demos.
87   * Provides useful callback methods. <p>To avoid problems with
88   * "calls to overwritten method in constructor", do not initialize the demo
89   * within the constructor of the subclass, use the method {@link #initialize()}
90   * instead.</p>
91   */
92  public abstract class DemoBase {
93    /**
94     * The dimension of a small separator of the toolbar without divider line. This can be used to separate non-icon
95     * components of toolbars.
96     */
97    public static final Dimension TOOLBAR_SMALL_SEPARATOR = new Dimension(3, 3);
98  
99    public static final Icon SHARED_LAYOUT_ICON = getIconResource("resource/layout.png");
100 
101   private static final Pattern PATH_SEPARATOR_PATTERN = Pattern.compile("/");
102 
103   /**
104    * Initializes to a "nice" look and feel.
105    */
106   public static void initLnF() {
107     DemoDefaults.initLnF();
108   }
109 
110   /**
111    * The view component of this demo.
112    */
113   protected Graph2DView view;
114 
115   protected final JPanel contentPane;
116 
117   /**
118    * This constructor creates the {@link #view} and calls,
119    * {@link #createToolBar()} {@link #registerViewModes()},
120    * {@link #registerViewActions()}, and {@link #registerViewListeners()}
121    */
122   protected DemoBase() {
123     view = createGraphView();
124     configureDefaultRealizers();
125 
126     contentPane = new JPanel();
127     contentPane.setLayout(new BorderLayout());
128 
129     initialize();
130 
131     registerViewModes();
132     registerViewActions();
133 
134     contentPane.add(view, BorderLayout.CENTER);
135     final JToolBar jtb = createToolBar();
136     if (jtb != null) {
137       contentPane.add(jtb, BorderLayout.NORTH);
138     }
139 
140     registerViewListeners();
141   }
142 
143   /**
144    * Callback used by the default constructor {@link #DemoBase()} ()} to create the default graph view
145    * @return an instance of {@link y.view.Graph2DView} with activated  "FitContentOnResize" behaviour.
146    */
147   protected Graph2DView createGraphView() {
148     Graph2DView view = new Graph2DView();
149     view.setFitContentOnResize(true);
150     return view;
151   }
152 
153   /**
154    * Configures the default node realizer and default edge realizer used by subclasses
155    * of this demo. The default implementation delegates to {@link DemoDefaults#configureDefaultRealizers(Graph2DView)}.
156    */
157   protected void configureDefaultRealizers() {
158     DemoDefaults.configureDefaultRealizers(view);
159   }
160 
161   /**
162    * This method is called before the view modes and actions are registered and
163    * the menu and toolbar is build.
164    */
165   protected void initialize() {
166   }
167 
168   public void dispose() {
169   }
170 
171   protected void loadGraph(URL resource) {
172 
173     if (resource == null) {
174       String message = "Resource \"" + resource + "\" not found in classpath";
175       D.showError(message);
176       throw new RuntimeException(message);
177     }
178 
179     try {
180       IOHandler ioh = createGraphMLIOHandler();
181       view.getGraph2D().clear();
182       ioh.read(view.getGraph2D(), resource);
183     } catch (IOException e) {
184       String message = "Unexpected error while loading resource \"" + resource + "\" due to " + e.getMessage();
185       D.bug(message);
186       throw new RuntimeException(message, e);
187     }
188     view.getGraph2D().setURL(resource);
189     view.fitContent();
190     view.updateView();
191 
192   }
193 
194   protected GraphMLIOHandler createGraphMLIOHandler() {
195     return new GraphMLIOHandler();
196   }
197 
198   protected void loadGraph(Class aClass, String resourceString) {
199     final URL resource = aClass.getResource(resourceString);
200     if (resource == null) {
201       String message = "Resource \"" + resourceString + "\" not found in classpath of " + aClass;
202       D.showError(message);
203       throw new RuntimeException(message);
204     }
205     loadGraph(resource);
206   }
207 
208   protected void loadGraph(String resourceString) {
209     loadGraph(getClass(), resourceString);
210   }
211 
212   /**
213    * Creates an application frame for this demo and displays it. The simple class name
214    * is the title of the displayed frame.
215    */
216   public final void start() {
217     start(getSimpleClassName());
218   }
219 
220   /**
221    * Returns the simple class name. This works only for top-level classes which shouldn't be a problem for demos.
222    */
223   private String getSimpleClassName() {
224     final String className = getClass().getName();
225     return className.substring(className.lastIndexOf('.') + 1);
226   }
227 
228   /**
229    * Creates an application frame for this demo and displays it. The given
230    * string is the title of the displayed frame.
231    */
232   public final void start(String title) {
233     JFrame frame = new JFrame(title);
234     JOptionPane.setRootFrame(frame);
235     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
236     this.addContentTo(frame.getRootPane());
237     frame.pack();
238     frame.setLocationRelativeTo(null);
239     frame.setVisible(true);
240   }
241 
242   public void addContentTo(final JRootPane rootPane) {
243     final JMenuBar jmb = createMenuBar();
244     if (jmb != null) {
245       rootPane.setJMenuBar(jmb);
246     }
247     rootPane.setContentPane(contentPane);
248   }
249 
250   protected void addHelpPane( final String helpFilePath ) {
251     if (helpFilePath != null) {
252       final URL url = getClass().getResource(helpFilePath);
253       if (url == null) {
254         System.err.println("Could not locate help file: " + helpFilePath);
255       } else {
256         final JComponent helpPane = createHelpPane(url);
257         if (helpPane != null) {
258           contentPane.add(helpPane, BorderLayout.EAST);
259         }
260       }
261     }
262   }
263 
264   /**
265    * Creates the application help pane.
266    * @param helpURL the URL of the HTML help page to display.
267    */
268   protected JComponent createHelpPane( final URL helpURL ) {
269     try {
270       JEditorPane editorPane = new JEditorPane(helpURL);
271       editorPane.setEditable(false);
272       editorPane.setPreferredSize(new Dimension(250, 250));
273       return new JScrollPane(editorPane);
274     } catch (IOException e) {
275       e.printStackTrace();
276     }
277     return null;
278   }
279 
280   protected void registerViewActions() {
281     // register keyboard actions
282     Graph2DViewActions actions = new Graph2DViewActions(view);
283     ActionMap amap = view.getCanvasComponent().getActionMap();
284     if (amap != null) {
285       InputMap imap = actions.createDefaultInputMap(amap);
286       if (!isDeletionEnabled()) {
287         amap.remove(Graph2DViewActions.DELETE_SELECTION);
288       }
289       view.getCanvasComponent().setInputMap(JComponent.WHEN_FOCUSED, imap);
290     }
291   }
292 
293   /**
294    * Adds the view modes to the view. This implementation adds a new EditMode
295    * created by {@link #createEditMode()}, a new TooltipMode created by {@link #createTooltipMode()}
296    * and a new {@link AutoDragViewMode}.
297    */
298   protected void registerViewModes() {
299     EditMode editMode = createEditMode();
300     if (editMode != null) {
301       view.addViewMode(editMode);
302     }
303 
304     TooltipMode tooltipMode = createTooltipMode();
305     if(tooltipMode != null) {
306       view.addViewMode(tooltipMode);
307     }
308 
309     view.addViewMode(new AutoDragViewMode());
310   }
311 
312   /**
313    * Callback used by {@link #registerViewModes()} to create the default
314    * EditMode.
315    *
316    * @return an instance of {@link EditMode} with showNodeTips enabled.
317    */
318   protected EditMode createEditMode() {
319     EditMode editMode = new EditMode();
320     // show the highlighting which is turned off by default
321     if (editMode.getCreateEdgeMode() instanceof CreateEdgeMode) {
322       ((CreateEdgeMode) editMode.getCreateEdgeMode()).setIndicatingTargetNode(true);
323     }
324     if (editMode.getMovePortMode() instanceof MovePortMode) {
325       ((MovePortMode) editMode.getMovePortMode()).setIndicatingTargetNode(true);
326     }
327 
328     //allow moving view port with right drag gesture
329     editMode.allowMovingWithPopup(true);
330 
331     return editMode;
332   }
333 
334   /**
335    * Callback used by {@link #registerViewModes()} to create a default
336    * <code>TooltipMode</code>.
337    *
338    * @return an instance of {@link TooltipMode} where only node tooltips are enabled.
339    */
340   protected TooltipMode createTooltipMode() {
341     TooltipMode tooltipMode = new TooltipMode();
342     tooltipMode.setEdgeTipEnabled(false);
343     return tooltipMode;
344   }
345 
346   /**
347    * Instantiates and registers the listeners for the view (e.g.
348    * {@link y.view.Graph2DViewMouseWheelZoomListener}).
349    */
350   protected void registerViewListeners() {
351     Graph2DViewMouseWheelZoomListener wheelZoomListener = new Graph2DViewMouseWheelZoomListener();
352     //zoom in/out at mouse pointer location 
353     wheelZoomListener.setCenterZooming(false);
354     view.getCanvasComponent().addMouseWheelListener(wheelZoomListener);
355   }
356 
357   /**
358    * Determines whether default actions for deletions will be added to the view
359    * and toolbar.
360    */
361   protected boolean isDeletionEnabled() {
362     return true;
363   }
364 
365   /**
366    * Creates a toolbar for this demo.
367    */
368   protected JToolBar createToolBar() {
369     JToolBar toolBar = new JToolBar();
370     toolBar.add(new Zoom(1.25));
371     toolBar.add(new Zoom(0.8));
372     toolBar.add(new ResetZoom());
373     toolBar.add(new FitContent(view));
374     if (isDeletionEnabled()) {
375       toolBar.add(createDeleteSelectionAction());
376     }
377     return toolBar;
378   }
379 
380   /**
381    * Create a menu bar for this demo.
382    */
383   protected JMenuBar createMenuBar() {
384     JMenuBar menuBar = new JMenuBar();
385     JMenu menu = new JMenu("File");
386     Action action;
387     action = createLoadAction();
388     if (action != null) {
389       menu.add(action);
390     }
391     action = createSaveAction();
392     if (action != null) {
393       menu.add(action);
394     }
395     menu.addSeparator();
396     menu.add(new PrintAction());
397     menu.addSeparator();
398     menu.add(new ExitAction());
399     menuBar.add(menu);
400     
401     if (getExampleResources() != null && getExampleResources().length != 0) {
402       createExamplesMenu(menuBar);
403     }
404     
405     return menuBar;
406   }
407 
408   protected Action createLoadAction() {
409     return new LoadAction();
410   }
411 
412   protected Action createSaveAction() {
413     return new SaveAction();
414   }
415 
416   protected Action createDeleteSelectionAction() {
417     return new DeleteSelection(view);
418   }
419 
420   /**
421    * Creates a menu to load the example graphs returned by {@link #getExampleResources} and adds it to the given menu
422    * bar.
423    * @param menuBar the menu bar to which the created menu is added.
424    */
425   protected void createExamplesMenu(JMenuBar menuBar) {
426     final String[] fileNames = getExampleResources();
427     if (fileNames == null) {
428       return;
429     }
430 
431     final JMenu menu = new JMenu("Example Graphs");
432     menuBar.add(menu);
433 
434     for (int i = 0; i < fileNames.length; i++) {
435       final String filename = fileNames[i];
436       final String[] path = PATH_SEPARATOR_PATTERN.split(filename);
437       menu.add(new AbstractAction(path[path.length - 1]) {
438         public void actionPerformed(ActionEvent e) {
439           loadGraph(filename);
440         }
441       });
442     }
443   }
444 
445   public JPanel getContentPane() {
446     return contentPane;
447   }
448 
449   /**
450    * Returns the list of example graph resources of this demo.
451    */
452   protected String[] getExampleResources() {
453     return null;
454   }
455   
456   /**
457    * Action that prints the contents of the view
458    */
459   public class PrintAction extends AbstractAction {
460     PageFormat pageFormat;
461 
462     OptionHandler printOptions;
463 
464     public PrintAction() {
465       super("Print");
466 
467       // setup option handler
468       printOptions = new OptionHandler("Print Options");
469       printOptions.addInt("Poster Rows", 1);
470       printOptions.addInt("Poster Columns", 1);
471       printOptions.addBool("Add Poster Coords", false);
472       final String[] area = {"View", "Graph"};
473       printOptions.addEnum("Clip Area", area, 1);
474     }
475 
476     public void actionPerformed(ActionEvent e) {
477       Graph2DPrinter gprinter = new Graph2DPrinter(view);
478 
479       // show custom print dialog and adopt values
480       if (!printOptions.showEditor(view.getFrame())) {
481         return;
482       }
483       gprinter.setPosterRows(printOptions.getInt("Poster Rows"));
484       gprinter.setPosterColumns(printOptions.getInt("Poster Columns"));
485       gprinter.setPrintPosterCoords(printOptions.getBool("Add Poster Coords"));
486       if ("Graph".equals(printOptions.get("Clip Area"))) {
487         gprinter.setClipType(Graph2DPrinter.CLIP_GRAPH);
488       } else {
489         gprinter.setClipType(Graph2DPrinter.CLIP_VIEW);
490       }
491 
492       // show default print dialogs
493       PrinterJob printJob = PrinterJob.getPrinterJob();
494       if (pageFormat == null) {
495         pageFormat = printJob.defaultPage();
496       }
497       PageFormat pf = printJob.pageDialog(pageFormat);
498       if (pf == pageFormat) {
499         return;
500       } else {
501         pageFormat = pf;
502       }
503 
504       // setup print job.
505       // Graph2DPrinter is of type Printable
506       printJob.setPrintable(gprinter, pageFormat);
507 
508       if (printJob.printDialog()) {
509         try {
510           printJob.print();
511         } catch (Exception ex) {
512           ex.printStackTrace();
513         }
514       }
515     }
516   }
517 
518   /**
519    * Action that terminates the application
520    */
521   public static class ExitAction extends AbstractAction {
522     public ExitAction() {
523       super("Exit");
524     }
525 
526     public void actionPerformed(ActionEvent e) {
527       System.exit(0);
528     }
529   }
530 
531   /**
532    * Action that saves the current graph to a file in GraphML format.
533    */
534   public class SaveAction extends AbstractAction {
535     JFileChooser chooser;
536 
537     public SaveAction() {
538       super("Save...");
539       chooser = null;
540     }
541 
542     public void actionPerformed(ActionEvent e) {
543       if (chooser == null) {
544         chooser = new JFileChooser();
545         chooser.setAcceptAllFileFilterUsed(false);
546         chooser.addChoosableFileFilter(new FileFilter() {
547           public boolean accept(File f) {
548             return f.isDirectory() || f.getName().endsWith(".graphml");
549           }
550 
551           public String getDescription() {
552             return "GraphML Format (.graphml)";
553           }
554         });
555       }
556 
557       URL url = view.getGraph2D().getURL();
558       if (url != null && "file".equals(url.getProtocol())) {
559         try {
560           chooser.setSelectedFile(new File(new URI(url.toString())));
561         } catch (URISyntaxException e1) {
562           // ignore
563         }
564       }
565 
566       if (chooser.showSaveDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
567         String name = chooser.getSelectedFile().toString();
568         if(!name.endsWith(".graphml")) {
569           name += ".graphml";
570         }
571         IOHandler ioh = createGraphMLIOHandler();
572 
573         try {
574           ioh.write(view.getGraph2D(), name);
575         } catch (IOException ioe) {
576           D.show(ioe);
577         }
578       }
579     }
580   }
581 
582   /**
583    * Action that loads the current graph from a file in GraphML format.
584    */
585   public class LoadAction extends AbstractAction {
586     JFileChooser chooser;
587 
588     public LoadAction() {
589       super("Load...");
590       chooser = null;
591     }
592 
593     public void actionPerformed(ActionEvent e) {
594       if (chooser == null) {
595         chooser = new JFileChooser();
596         chooser.setAcceptAllFileFilterUsed(false);
597         chooser.addChoosableFileFilter(new FileFilter() {
598           public boolean accept(File f) {
599             return f.isDirectory() || f.getName().endsWith(".graphml");
600           }
601 
602           public String getDescription() {
603             return "GraphML Format (.graphml)";
604           }
605         });
606       }
607       if (chooser.showOpenDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
608         URL resource = null;
609         try {
610           resource = chooser.getSelectedFile().toURI().toURL();
611         } catch (MalformedURLException urlex) {
612           urlex.printStackTrace();
613         }
614         loadGraph(resource);
615       }
616     }
617   }
618 
619   /**
620    * Action that deletes the selected parts of the graph.
621    */
622   public static class DeleteSelection extends AbstractAction {
623     private final Graph2DView view;
624 
625     public DeleteSelection(final Graph2DView view) {
626       super("Delete Selection");
627       this.view = view;
628       this.putValue(Action.SMALL_ICON, getIconResource("resource/delete.png"));
629       this.putValue(Action.SHORT_DESCRIPTION, "Delete Selection");
630     }
631 
632     public void actionPerformed(ActionEvent e) {
633       view.getGraph2D().removeSelection();
634       view.getGraph2D().updateViews();
635     }
636   }
637 
638   /**
639    * Action that resets the view's zoom level to <code>1.0</code>.
640    */
641   public class ResetZoom extends AbstractAction {
642     public ResetZoom() {
643       super("Reset Zoom");
644       this.putValue(Action.SMALL_ICON, getIconResource("resource/zoomOriginal.png"));
645       this.putValue(Action.SHORT_DESCRIPTION, "Reset Zoom");
646     }
647 
648     public void actionPerformed( final ActionEvent e ) {
649       view.setZoom(1.0);
650       // optional code that adjusts the size of the
651       // view's world rectangle. The world rectangle
652       // defines the region of the canvas that is
653       // accessible by using the scroll bars of the view.
654       Rectangle box = view.getGraph2D().getBoundingBox();
655       view.setWorldRect(box.x - 20, box.y - 20, box.width + 40, box.height + 40);
656 
657       view.updateView();
658     }
659   }
660 
661   /**
662    * Action that applies a specified zoom level to the view.
663    */
664   public class Zoom extends AbstractAction {
665     double factor;
666 
667     public Zoom(double factor) {
668       super("Zoom " + (factor > 1.0 ? "In" : "Out"));
669       String resource = factor > 1.0d ? "resource/zoomIn.png" : "resource/zoomOut.png";
670       this.putValue(Action.SMALL_ICON, getIconResource(resource));
671       this.putValue(Action.SHORT_DESCRIPTION, "Zoom " + (factor > 1.0 ? "In" : "Out"));
672       this.factor = factor;
673     }
674 
675     public void actionPerformed(ActionEvent e) {
676       view.setZoom(view.getZoom() * factor);
677       // optional code that adjusts the size of the
678       // view's world rectangle. The world rectangle
679       // defines the region of the canvas that is
680       // accessible by using the scroll bars of the view.
681       Rectangle box = view.getGraph2D().getBoundingBox();
682       view.setWorldRect(box.x - 20, box.y - 20, box.width + 40, box.height + 40);
683 
684       view.updateView();
685     }
686   }
687 
688   /**
689    * Action that fits the content nicely inside the view.
690    */
691   public static class FitContent extends AbstractAction {
692     private final Graph2DView view;
693 
694     public FitContent(final Graph2DView view) {
695       super("Fit Content");
696       this.view = view;
697       this.putValue(Action.SMALL_ICON, getIconResource("resource/zoomFit.png"));
698       this.putValue(Action.SHORT_DESCRIPTION, "Fit Content");
699     }
700 
701     public void actionPerformed(ActionEvent e) {
702       view.fitContent();
703       view.updateView();
704     }
705   }
706 
707   /**
708    * This is a convenience class which configures a view, a drop support and/or an edit mode for
709    * snapping and/or grid snapping.<br>
710    * Note that {@link Graph2DView#setGridMode(boolean) enabling the grid on the view} has the effect that nodes
711    * can only be placed on grid positions, thus it prevents the other snapping rules from being applied. However,
712    * the {@link MoveSnapContext#setUsingGridSnapping(boolean) newer grid snapping feature} which is used here
713    * coexists nicely with other snapping rules.
714    */
715   public static class SnappingConfiguration {
716     private boolean snappingEnabled;
717     private double snapDistance;
718     private double snapLineExtension;
719     private Color snapLineColor;
720     private boolean removingInnerBends;
721 
722     private double nodeToNodeDistance;
723     private double nodeToEdgeDistance;
724     private double edgeToEdgeDistance;
725 
726     private boolean gridSnappingEnabled;
727     private double gridSnapDistance;
728     private double gridDistance;
729     private int gridType;
730 
731 
732     public double getEdgeToEdgeDistance() {
733       return edgeToEdgeDistance;
734     }
735 
736     public void setEdgeToEdgeDistance(double edgeToEdgeDistance) {
737       this.edgeToEdgeDistance = edgeToEdgeDistance;
738     }
739 
740     public double getGridSnapDistance() {
741       return gridSnapDistance;
742     }
743 
744     public void setGridSnapDistance(double gridSnapDistance) {
745       this.gridSnapDistance = gridSnapDistance;
746     }
747 
748     public boolean isGridSnappingEnabled() {
749       return gridSnappingEnabled;
750     }
751 
752     public void setGridSnappingEnabled(boolean gridSnappingEnabled) {
753       this.gridSnappingEnabled = gridSnappingEnabled;
754     }
755 
756     public double getNodeToEdgeDistance() {
757       return nodeToEdgeDistance;
758     }
759 
760     public void setNodeToEdgeDistance(double nodeToEdgeDistance) {
761       this.nodeToEdgeDistance = nodeToEdgeDistance;
762     }
763 
764     public double getNodeToNodeDistance() {
765       return nodeToNodeDistance;
766     }
767 
768     public void setNodeToNodeDistance(double nodeToNodeDistance) {
769       this.nodeToNodeDistance = nodeToNodeDistance;
770     }
771 
772     public boolean isRemovingInnerBends() {
773       return removingInnerBends;
774     }
775 
776     public void setRemovingInnerBends(boolean removingInnerBends) {
777       this.removingInnerBends = removingInnerBends;
778     }
779 
780     public double getSnapDistance() {
781       return snapDistance;
782     }
783 
784     public void setSnapDistance(double snapDistance) {
785       this.snapDistance = snapDistance;
786     }
787 
788     public Color getSnapLineColor() {
789       return snapLineColor;
790     }
791 
792     public void setSnapLineColor(Color snapLineColor) {
793       this.snapLineColor = snapLineColor;
794     }
795 
796     public double getSnapLineExtension() {
797       return snapLineExtension;
798     }
799 
800     public void setSnapLineExtension(double snapLineExtension) {
801       this.snapLineExtension = snapLineExtension;
802     }
803 
804     public boolean isSnappingEnabled() {
805       return snappingEnabled;
806     }
807 
808     public void setSnappingEnabled(boolean snappingEnabled) {
809       this.snappingEnabled = snappingEnabled;
810     }
811 
812     public double getGridDistance() {
813       return gridDistance;
814     }
815 
816     public void setGridDistance(double gridDistance) {
817       this.gridDistance = gridDistance;
818     }
819 
820     public int getGridType() {
821       return gridType;
822     }
823 
824     public void setGridType(int gridType) {
825       this.gridType = gridType;
826     }
827 
828     public void configureView(final Graph2DView view) {
829       // Do not use the grid mode of the view. Use grid snapping instead (see configureSnapContext()).
830       view.setGridMode(false);
831 
832       if (isGridSnappingEnabled()) {
833         // Use the normal grid for the display.
834         view.setGridVisible(true);
835         view.setGridResolution(getGridDistance());
836         view.setGridType(getGridType());
837         view.setGridColor(getSnapLineColor());
838       } else {
839         view.setGridVisible(false);
840       }
841 
842       view.updateView();
843     }
844 
845     public void configureDropSupport(final DropSupport dropSupport) {
846       dropSupport.setSnappingEnabled(isSnappingEnabled());
847       configureSnapContext(dropSupport.getSnapContext());
848     }
849 
850     /**
851      * Configures the snap context of the given <code>EditMode</code> and its children according to the given
852      * parameters.
853      *
854      * @noinspection JavaDoc
855      */
856     public void configureEditMode(final EditMode editMode) {
857       if (editMode.getHotSpotMode() instanceof HotSpotMode) {
858         HotSpotMode hotSpotMode = (HotSpotMode) editMode.getHotSpotMode();
859         hotSpotMode.setSnappingEnabled(isSnappingEnabled());
860         hotSpotMode.getSnapContext().setSnapLineColor(getSnapLineColor());
861       }
862       {
863         MoveSelectionMode moveSelectionMode = (MoveSelectionMode) editMode.getMoveSelectionMode();
864         MoveSnapContext snapContext = moveSelectionMode.getSnapContext();
865         moveSelectionMode.setSnappingEnabled(isGridSnappingEnabled() || isSnappingEnabled());
866 
867         configureSnapContext(snapContext);
868         moveSelectionMode.setRemovingInnerBends(isRemovingInnerBends());
869       }
870       {
871         OrthogonalMoveBendsMode moveBendsMode = (OrthogonalMoveBendsMode) editMode.getOrthogonalMoveBendsMode();
872         MoveSnapContext snapContext = moveBendsMode.getSnapContext();
873         moveBendsMode.setSnappingEnabled(isGridSnappingEnabled() || isSnappingEnabled());
874 
875         configureSnapContext(snapContext);
876         moveBendsMode.setRemovingInnerBends(isRemovingInnerBends());
877       }
878       {
879         CreateEdgeMode createEdgeMode = (CreateEdgeMode) editMode.getCreateEdgeMode();
880         MoveSnapContext snapContext = createEdgeMode.getSnapContext();
881         if (isSnappingEnabled()) {
882           createEdgeMode.setSnapToOrthogonalSegmentsDistance(5.0);
883           createEdgeMode.setUsingNodeCenterSnapping(true);
884           createEdgeMode.setSnappingOrthogonalSegments(true);
885         } else {
886           createEdgeMode.setSnapToOrthogonalSegmentsDistance(0.0);
887           createEdgeMode.setUsingNodeCenterSnapping(false);
888           createEdgeMode.setSnappingOrthogonalSegments(false);
889         }
890 
891         configureSnapContext(snapContext);
892       }
893       if (editMode.getMovePortMode() instanceof MovePortMode) {
894         MovePortMode movePortMode = (MovePortMode) editMode.getMovePortMode();
895         movePortMode.setUsingRealizerPortCandidates(!isSnappingEnabled());
896         movePortMode.setSegmentSnappingEnabled(isSnappingEnabled());
897         MoveSnapContext snapContext = movePortMode.getSnapContext();
898         configureSnapContext(snapContext);
899       }
900       if(editMode.getMoveLabelMode() instanceof MoveLabelMode) {
901         final MoveLabelMode moveLabelMode = (MoveLabelMode) editMode.getMoveLabelMode();
902         moveLabelMode.setSnappingEnabled(isSnappingEnabled());
903       }
904     }
905 
906     /**
907      * Configures the given <code>MoveSnapContext</code>.
908      *
909      * @noinspection JavaDoc
910      */
911     public void configureSnapContext(final MoveSnapContext snapContext) {
912 
913       snapContext.setSnapLineColor(getSnapLineColor());
914       snapContext.setSnapDistance(getSnapDistance());
915       snapContext.setGridSnapDistance(getGridSnapDistance());
916       snapContext.setSnapLineExtension(getSnapLineExtension());
917 
918       snapContext.setUsingGridSnapping(isGridSnappingEnabled());
919       snapContext.setRenderingSnapLines(isGridSnappingEnabled() || isSnappingEnabled());
920 
921       if (isGridSnappingEnabled() && !isSnappingEnabled()) {
922         snapContext.setSnappingBendsToSnapLines(false);
923         snapContext.setSnappingSegmentsToSnapLines(false);
924         snapContext.setUsingCenterSnapLines(false);
925         snapContext.setUsingEquidistantSnapLines(false);
926         snapContext.setUsingFixedNodeSnapLines(false);
927         snapContext.setUsingOrthogonalBendSnapping(false);
928         snapContext.setUsingOrthogonalMovementConstraints(false);
929         snapContext.setUsingOrthogonalPortSnapping(false);
930         snapContext.setUsingSegmentSnapLines(false);
931 
932         // Use "null" values if just the grid, but no snap lines are enabled.
933         snapContext.setEdgeToEdgeDistance(0.0);
934         snapContext.setNodeToEdgeDistance(-1.0);
935         snapContext.setNodeToNodeDistance(0.0);
936       } else {
937         snapContext.setSnappingBendsToSnapLines(false);
938         snapContext.setSnappingSegmentsToSnapLines(true);
939         snapContext.setUsingCenterSnapLines(false);
940         snapContext.setUsingEquidistantSnapLines(true);
941         snapContext.setUsingFixedNodeSnapLines(true);
942         snapContext.setUsingOrthogonalBendSnapping(true);
943         snapContext.setUsingOrthogonalMovementConstraints(true);
944         snapContext.setUsingOrthogonalPortSnapping(true);
945         snapContext.setUsingSegmentSnapLines(true);
946 
947         snapContext.setEdgeToEdgeDistance(getEdgeToEdgeDistance());
948         snapContext.setNodeToEdgeDistance(getNodeToEdgeDistance());
949         snapContext.setNodeToNodeDistance(getNodeToNodeDistance());
950       }
951     }
952   }
953 
954   /**
955    * @return a configuration for snapping with default parameters.
956    */
957   public static SnappingConfiguration createDefaultSnappingConfiguration() {
958     SnappingConfiguration result = new SnappingConfiguration();
959     result.setSnappingEnabled(true);
960     result.setGridSnappingEnabled(false);
961     result.setSnapLineColor(Color.LIGHT_GRAY);
962     result.setRemovingInnerBends(true);
963     result.setNodeToNodeDistance(30.0);
964     result.setNodeToEdgeDistance(20.0);
965     result.setEdgeToEdgeDistance(20.0);
966     result.setSnapDistance(5.0);
967     result.setGridSnapDistance(10.0);
968     result.setSnapLineExtension(40.0);
969     result.setGridDistance(50.0);
970     result.setGridType(View2DConstants.GRID_CROSS);
971     return result;
972   }
973 
974   /**
975    * Creates a control for triggering the specified action from the demo toolbars and displays the action's text and
976    * icon, if present.
977    *
978    * @param action the <code>Action</code> that is triggered by the created control.
979    * @return a control for triggering the specified action from the demo toolbars.
980    */
981   public static JComponent createActionControl(final Action action) {
982     return createActionControl(action, true);
983   }
984 
985   /**
986    * Creates a control for triggering the specified action from the demo toolbars.
987    *
988    * @param action         the <code>Action</code> that is triggered by the created control.
989    * @param showActionText <code>true</code> if the control should display the action name; <code>false</code>
990    *                       otherwise.
991    * @return a control for triggering the specified action from the demo toolbars.
992    */
993   public static JComponent createActionControl(final Action action, final boolean showActionText) {
994     final JButton jb = new JButton();
995     if (action.getValue(Action.SMALL_ICON) != null) {
996       jb.putClientProperty("hideActionText", Boolean.valueOf(!showActionText));
997     }
998     jb.setAction(action);
999     return jb;
1000  }
1001
1002  /**
1003   * Finds an <code>Icon</code> resource with the specified name.
1004   * This methods uses
1005   * <blockquote>
1006   * <code>DemoBase.class.getResource(resource)</code>
1007   * </blockquote>
1008   * to resolve resources.
1009   * @param resource the name/path of the desired resource.
1010   * @return the resolved <code>Icon</code> of <code>null</code> if no icon
1011   * with the specified name could be found.
1012   */
1013  public static Icon getIconResource( final String resource ) {
1014    try {
1015      final URL icon = DemoBase.class.getResource(resource);
1016      if (icon == null) {
1017        return null;
1018      } else {
1019        return new ImageIcon(icon);
1020      }
1021    } catch (Exception e) {
1022      return null;
1023    }
1024  }
1025
1026  /**
1027   * Creates dialogs for OptionHandlers.
1028   */
1029  public static class OptionSupport {
1030    private final EditorFactory editorFactory;
1031
1032    /**
1033     * Displays the OptionHandler of the given module in a dialog and runs the module on the given graph if either the
1034     * <code>Ok</code> or <code>Apply</code> button were pressed. The creation of the dialog is delegated to {@link
1035     * #createDialog(y.option.OptionHandler, java.awt.event.ActionListener, java.awt.Frame)}
1036     *
1037     * @param module the option handler.
1038     * @param graph  an optional ActionListener that is added to each button of the dialog.
1039     * @param owner  the owner of the created dialog.
1040     */
1041    public static void showDialog(final YModule module, final Graph2D graph, final boolean runOnOk, Frame owner) {
1042      if (module.getOptionHandler() == null) {
1043        module.start(graph);
1044      } else {
1045        final ActionListener listener = new ActionListener() {
1046          public void actionPerformed(ActionEvent e) {
1047            module.start(graph);
1048          }
1049        };
1050
1051        showDialog(module.getOptionHandler(), listener, runOnOk, owner);
1052      }
1053    }
1054
1055    /**
1056     * Displays the given OptionHandler in a dialog and invokes the given ActionListener if either the <code>Ok</code>
1057     * or <code>Apply</code> button were pressed. The creation of the dialog is delegated to {@link
1058     * #createDialog(y.option.OptionHandler, java.awt.event.ActionListener, java.awt.Frame)}
1059     *
1060     * @param oh       the option handler.
1061     * @param listener the ActionListener that is invoked if either the <code>Ok</code> or <code>Apply</code> button
1062     *                 were pressed.
1063     * @param owner    the owner of the created dialog.
1064     */
1065    public static void showDialog(final OptionHandler oh, final ActionListener listener,
1066                                  final boolean runOnOk, Frame owner) {
1067      final ActionListener delegatingListener = new ActionListener() {
1068        public void actionPerformed(final ActionEvent e) {
1069          final String actionCommand = e.getActionCommand();
1070          if ("Apply".equals(actionCommand) || (runOnOk && "Ok".equals(actionCommand))) {
1071            EventQueue.invokeLater(new Runnable() {
1072              public void run() {
1073                listener.actionPerformed(e);
1074              }
1075            });
1076          }
1077        }
1078      };
1079
1080      final JDialog dialog = new OptionSupport().createDialog(oh, delegatingListener, owner);
1081      dialog.setVisible(true);
1082    }
1083
1084    public OptionSupport() {
1085      editorFactory = new DefaultEditorFactory();
1086    }
1087
1088    /**
1089     * Returns an dialog for the editor of the given option handler which contains four control buttons: Ok, Apply,
1090     * Reset, and Cancel. An optional ActionListener can be used to add custom behavior to these buttons.
1091     *
1092     * @param oh       the option handler.
1093     * @param listener an optional ActionListener that is added to each button of the dialog.
1094     * @param owner    the owner of the created dialog.
1095     */
1096    public JDialog createDialog(final OptionHandler oh, ActionListener listener, Frame owner) {
1097      final JDialog dialog = new JDialog(owner, getTitle(oh), true);
1098
1099      final AbstractAction applyAction = new AbstractAction("Apply") {
1100        public void actionPerformed(ActionEvent e) {
1101          if (oh.checkValues()) {
1102            oh.commitValues();
1103          }
1104        }
1105      };
1106      final JButton applyButton = createButton(applyAction, listener);
1107      applyButton.setDefaultCapable(true);
1108      applyButton.requestFocus();
1109
1110      final AbstractAction closeAction = new AbstractAction("Cancel") {
1111        public void actionPerformed(ActionEvent e) {
1112          dialog.dispose();
1113        }
1114      };
1115
1116      final AbstractAction okAction = new AbstractAction("Ok") {
1117        public void actionPerformed(ActionEvent e) {
1118          if (oh.checkValues()) {
1119            oh.commitValues();
1120            dialog.dispose();
1121          }
1122        }
1123      };
1124
1125      final AbstractAction resetAction = new AbstractAction("Reset") {
1126        public void actionPerformed(ActionEvent e) {
1127          oh.resetValues();
1128        }
1129      };
1130
1131      final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
1132      buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 11, 5));
1133      buttonPanel.add(createButton(okAction, listener));
1134      buttonPanel.add(createButton(closeAction, listener));
1135      buttonPanel.add(applyButton);
1136      buttonPanel.add(createButton(resetAction, listener));
1137
1138      final Editor editor = getEditorFactory().createEditor(oh);
1139      final JComponent editorComponent = editor.getComponent();
1140      if (editorComponent instanceof JPanel) {
1141        editorComponent.setBorder(BorderFactory.createEmptyBorder(11, 5, 5, 5));
1142      } else {
1143        editorComponent.setBorder(BorderFactory.createEmptyBorder(9, 9, 15, 9));
1144      }
1145
1146      final JPanel contentPanel = new JPanel(new BorderLayout(0, 0));
1147      contentPanel.add(editorComponent, BorderLayout.CENTER);
1148      contentPanel.add(buttonPanel, BorderLayout.SOUTH);
1149
1150      dialog.setContentPane(contentPanel);
1151      dialog.getRootPane().getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "APPLY_ACTION");
1152      dialog.getRootPane().getActionMap().put("APPLY_ACTION", applyAction);
1153      dialog.getRootPane().getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "CANCEL_ACTION");
1154      dialog.getRootPane().getActionMap().put("CANCEL_ACTION", closeAction);
1155      dialog.getRootPane().setDefaultButton(applyButton);
1156      dialog.pack();
1157      dialog.setSize(dialog.getPreferredSize());
1158      dialog.setResizable(false);
1159      dialog.setLocationRelativeTo(owner);
1160
1161      return dialog;
1162    }
1163
1164    protected EditorFactory getEditorFactory() {
1165      return editorFactory;
1166    }
1167
1168    String getTitle(OptionHandler oh) {
1169      final Object title = oh.getAttribute(OptionHandler.ATTRIBUTE_TITLE);
1170      if (title instanceof String) {
1171        return (String) title;
1172      }
1173
1174      final Object titleKey = oh.getAttribute(OptionHandler.ATTRIBUTE_TITLE_KEY);
1175      if (titleKey instanceof String) {
1176        return getTitle(oh, (String) titleKey);
1177      } else {
1178        return getTitle(oh, oh.getName());
1179      }
1180    }
1181
1182    static String getTitle(OptionHandler oh, String title) {
1183      final GuiFactory guiFactory = oh.getGuiFactory();
1184      return guiFactory == null ? title : guiFactory.getString(title);
1185    }
1186
1187    static JButton createButton(AbstractAction action, ActionListener listener) {
1188      final JButton button = new JButton(action);
1189      button.addActionListener(listener);
1190      return button;
1191
1192    }
1193  }
1194}
1195