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