View Javadoc

1   package de.matthias_burbach.mosaique.swing;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Event;
5   import java.awt.event.ActionEvent;
6   import java.awt.event.KeyEvent;
7   import java.awt.event.WindowAdapter;
8   import java.awt.event.WindowEvent;
9   import java.io.File;
10  import java.util.HashMap;
11  import java.util.Map;
12  
13  import javax.swing.AbstractAction;
14  import javax.swing.Action;
15  import javax.swing.BorderFactory;
16  import javax.swing.Icon;
17  import javax.swing.JFileChooser;
18  import javax.swing.JFrame;
19  import javax.swing.JOptionPane;
20  import javax.swing.JScrollPane;
21  import javax.swing.JSplitPane;
22  import javax.swing.JToolBar;
23  import javax.swing.KeyStroke;
24  import javax.swing.SwingUtilities;
25  import javax.swing.UIManager;
26  import javax.swing.WindowConstants;
27  import javax.swing.tree.DefaultTreeModel;
28  import javax.swing.tree.TreeNode;
29  
30  import de.matthias_burbach.mosaique.core.Mosaique;
31  import de.matthias_burbach.mosaique.core.model.StrutsConfig;
32  import de.matthias_burbach.mosaique.core.util.BrowserLauncher;
33  import de.matthias_burbach.mosaique.core.util.FileUtils;
34  import de.matthias_burbach.mosaique.core.util.SimpleLog;
35  
36  /***
37   * Is the main GUI class of Mosaique.
38   * Holds a map of actions that can be triggered
39   * by menu bar, tool bar and popup menu items outside of this class.
40   *
41   * @author Matthias Burbach
42   */
43  public class MosaiqueFrame extends JFrame {
44      /***
45       * The 'open Maven file' action.
46       */
47      public static final String ACTION_OPEN =
48          "Open";
49  
50      /***
51       * The 'exit application' action.
52       */
53      public static final String ACTION_EXIT =
54          "Exit";
55  
56      /***
57       * The 'Homepage' action.
58       */
59      public static final String ACTION_HOMEPAGE =
60          "Homepage";
61  
62      /***
63       * The 'Release Notes' action.
64       */
65      public static final String ACTION_RELEASE_NOTES =
66          "ReleaseNotes";
67  
68      /***
69       * The 'show about dialog' action.
70       */
71      public static final String ACTION_SHOW_ABOUT =
72          "ShowAbout";
73  
74      /***
75       * The mosaique core application object.
76       */
77      private Mosaique mosaique;
78  
79      /***
80       * The map of supported actions of type ActionListener.
81       */
82      private Map actionListeners;
83  
84      /***
85       * The tool bar.
86       */
87      private JToolBar toolBar;
88  
89      /***
90       * The cached file chooser for open and save actions.
91       * Must only be created after the look and feel has been set!
92       */
93      private JFileChooser fileChooser;
94  
95      /***
96       * The log panel at the bottom of the main frame. Used to display messages.
97       */
98      private MosaiqueLogPanel logPanel;
99  
100     /***
101      * The file panel at the right of the main frame. Used to display files.
102      */
103     private MosaiqueFilePanel filePanel;
104 
105     /***
106      * The right scroll pane around the panel that displays the current text
107      * file associated with the currently selected node in the explorer tree.
108      */
109     private JScrollPane rightScrollPane;
110 
111     /***
112      * The split pane separating the project from the repositories.
113      */
114     private JSplitPane horizontalSplitPane;
115 
116     /***
117      * The split pane separating project and repositories from the log.
118      */
119     private JSplitPane verticalSplitPane;
120 
121     /***
122      * The current tree.
123      */
124     private MosaiqueTree currentMosaiqueTree;
125 
126     /***
127      * Constructs the frame.
128      *
129      * @param mosaique The core application.
130      * @throws Exception if anything goes unexpectedly wrong
131      */
132     public MosaiqueFrame(final Mosaique mosaique) throws Exception {
133         this.mosaique = mosaique;
134 
135         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
136 
137         actionListeners = createActions();
138         fileChooser = new JFileChooser();
139 
140         setTitle("Mosaique");
141         setJMenuBar(new MosaiqueMenuBar(this));
142         toolBar = createToolBar();
143 
144         int x = 1;
145         try {
146             x = Integer.parseInt(mosaique.getProperty("mosaiqueFrame.x"));
147         } catch (Exception e) {
148             e.printStackTrace();
149         }
150         int y = 1;
151         try {
152             y = Integer.parseInt(mosaique.getProperty("mosaiqueFrame.y"));
153         } catch (Exception e) {
154             e.printStackTrace();
155         }
156         final int defaultWidth = 650;
157         int width = defaultWidth;
158         try {
159             width = Integer.parseInt(
160                         mosaique.getProperty("mosaiqueFrame.width"));
161         } catch (Exception e) {
162             e.printStackTrace();
163         }
164         final int defaultHeight = 600;
165         int height = defaultHeight;
166         try {
167             height = Integer.parseInt(
168                         mosaique.getProperty("mosaiqueFrame.height"));
169         } catch (Exception e) {
170             e.printStackTrace();
171         }
172         setBounds(x, y, width, height);
173 
174         setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
175         WindowAdapter windowListener = new WindowAdapter() {
176             /*
177              * (non-Javadoc)
178              * @see java.awt.event.WindowListener#windowClosing(
179              *          java.awt.event.WindowEvent)
180              */
181             /***
182              * {@inheritDoc}
183              */
184             public void windowClosing(final WindowEvent event) {
185                 MosaiqueFrame.this.exit();
186             }
187         };
188         addWindowListener(windowListener);
189 
190         logPanel = new MosaiqueLogPanel();
191         mosaique.setLog(logPanel);
192 
193         filePanel = new MosaiqueFilePanel();
194 
195         updateContentPane();
196         setVisible(true);
197     }
198 
199     /***
200      * Fills the content pane.
201      */
202     private void updateContentPane() {
203         /*
204          * Create left scroll pane
205          */
206         JScrollPane leftScrollPane = new JScrollPane();
207         if (currentMosaiqueTree != null) {
208             leftScrollPane.getViewport().add(currentMosaiqueTree);
209         }
210         leftScrollPane.setBorder(
211             BorderFactory.createTitledBorder("Structure"));
212 
213         /*
214          * Create right scroll pane
215          */
216         rightScrollPane = new JScrollPane();
217         rightScrollPane.setBorder(
218             BorderFactory.createTitledBorder("File"));
219         rightScrollPane.getViewport().add(filePanel);
220         /*
221          * Create horizontal split pane
222          */
223         int horizontalDividerLocation = getWidth() / 2;
224         if (horizontalSplitPane != null) {
225             horizontalDividerLocation =
226                 horizontalSplitPane.getDividerLocation();
227         } else {
228             try {
229                 horizontalDividerLocation =
230                     Integer.parseInt(
231                         mosaique.getProperty(
232                           "mosaiqueFrame.horizontalSplitPane.dividerLocation"));
233             } catch (Exception e) {
234                 e.printStackTrace();
235             }
236         }
237         horizontalSplitPane =
238             new JSplitPane(
239                 JSplitPane.HORIZONTAL_SPLIT,
240                 leftScrollPane,
241                 rightScrollPane);
242         horizontalSplitPane.setDividerLocation(horizontalDividerLocation);
243 
244         /*
245          * Create vertical split pane
246          */
247         final int four = 4;
248         int verticalDividerLocation = getHeight() - (getHeight() / four);
249         if (verticalSplitPane != null) {
250             verticalDividerLocation = verticalSplitPane.getDividerLocation();
251         } else {
252             try {
253                 verticalDividerLocation =
254                     Integer.parseInt(
255                         mosaique.getProperty(
256                             "mosaiqueFrame.verticalSplitPane.dividerLocation"));
257             } catch (Exception e) {
258                 e.printStackTrace();
259             }
260         }
261         verticalSplitPane =
262             new JSplitPane(
263                 JSplitPane.VERTICAL_SPLIT,
264                 horizontalSplitPane,
265                 logPanel);
266         verticalSplitPane.setDividerLocation(verticalDividerLocation);
267 
268         /*
269          * Add everything to the content pane
270          */
271         getContentPane().removeAll();
272         getContentPane().add(toolBar, BorderLayout.NORTH);
273         getContentPane().add(verticalSplitPane, BorderLayout.CENTER);
274         validate();
275     }
276 
277     /***
278      * @return The tool bar created.
279      */
280     private JToolBar createToolBar() {
281         JToolBar result = new JToolBar();
282         result.add(getAction(ACTION_OPEN));
283         return result;
284     }
285 
286     /***
287      * @return The map of supported actions of type {@link Action}.
288      */
289     private Map createActions() {
290         Map result = new HashMap();
291         result.putAll(createFileMenuActions());
292         result.putAll(createEditMenuActions());
293         result.putAll(createViewMenuActions());
294         result.putAll(createHelpMenuActions());
295         return result;
296     }
297 
298     /***
299      * @return The map of supported file actions of type {@link Action}.
300      */
301     private Map createFileMenuActions() {
302         Map result = new HashMap();
303 
304         Icon icon = SwingHelper.createImageIcon("small-open.gif", "");
305         Action action = new AbstractAction("Open...", icon) {
306             public void actionPerformed(final ActionEvent event) {
307                 openStrutsConfig();
308            }
309         };
310         action.putValue(Action.SHORT_DESCRIPTION, "Open");
311         action.putValue(
312             Action.ACCELERATOR_KEY,
313             KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.CTRL_MASK));
314         result.put(ACTION_OPEN, action);
315 
316         action = new AbstractAction("Exit") {
317             public void actionPerformed(final ActionEvent event) {
318                 exit();
319            }
320         };
321         action.putValue(Action.SHORT_DESCRIPTION, "Exit");
322         result.put(ACTION_EXIT, action);
323 
324         return result;
325     }
326 
327     /***
328      * @return The map of supported edit actions of type {@link Action}.
329      */
330     private Map createEditMenuActions() {
331         Map result = new HashMap();
332         return result;
333     }
334 
335     /***
336      * @return The map of supported view actions of type {@link Action}.
337      */
338     private Map createViewMenuActions() {
339         Map result = new HashMap();
340         return result;
341     }
342 
343     /***
344      * @return The map of supported help actions of type {@link Action}.
345      */
346     private Map createHelpMenuActions() {
347         Map result = new HashMap();
348 
349         Action action = new AbstractAction("Homepage") {
350             public void actionPerformed(final ActionEvent event) {
351                 openUrl("http://mosaique.sourceforge.net/");
352            }
353         };
354         action.putValue(Action.SHORT_DESCRIPTION, "Homepage");
355         result.put(ACTION_HOMEPAGE, action);
356 
357         action = new AbstractAction("Release Notes") {
358             public void actionPerformed(final ActionEvent event) {
359                 openUrl("http://mosaique.sourceforge.net/changes-report.html");
360            }
361         };
362         action.putValue(Action.SHORT_DESCRIPTION, "Release Notes");
363         result.put(ACTION_RELEASE_NOTES, action);
364 
365         action = new AbstractAction("About Mosaique") {
366            public void actionPerformed(final ActionEvent event) {
367                showAbout();
368            }
369         };
370         result.put(ACTION_SHOW_ABOUT, action);
371 
372         return result;
373     }
374 
375     /***
376      * @param name The name of the action. Must be one of the
377      *             ACTION_* constants defined in this class.
378      * @return The action for the name supplied or <code>null</code>
379      *         if the name is invalid.
380      */
381     public Action getAction(final String name) {
382         return (Action) actionListeners.get(name);
383     }
384 
385     /***
386      * Opens the Struts config whose file name has been set using
387      * {@link Mosaique#setStrutsConfigFile(String)}.
388      */
389     private void openStrutsConfig() {
390         boolean doOpen = true;
391         if (doOpen) {
392             try {
393                 String projectFile = mosaique.getStrutsConfigFile();
394                 if (projectFile != null) {
395                     try {
396                         File file = new File(projectFile);
397                         fileChooser.setCurrentDirectory(file);
398                         fileChooser.setSelectedFile(file);
399                     } catch (Exception e) {
400                         //ignore
401                         e.printStackTrace();
402                     }
403                 }
404                 int result =
405                     fileChooser.showOpenDialog(this);
406                 if (result == JFileChooser.APPROVE_OPTION) {
407                     File file = fileChooser.getSelectedFile();
408                     if (isValidProjectFile(file)) {
409                         logPanel.clear();
410                         mosaique.setStrutsConfigFile(file.getAbsolutePath());
411                         doOpenStrutsConfig();
412                     }
413                 }
414             } catch (Exception e) {
415                 e.printStackTrace();
416                 JOptionPane.showMessageDialog(
417                     this,
418                     "Encountered '" + e.getMessage() + "'",
419                     "Error",
420                     JOptionPane.ERROR_MESSAGE);
421             }
422         }
423     }
424 
425     /***
426      * Checks the file name of a project about to be opened for validity.
427      * Warns if it seems to be suspicious.
428      *
429      * @param file The file to validate.
430      * @return <code>true</code> if the file is unsuspicious or if the user
431      *         insists to open it.
432      */
433     private boolean isValidProjectFile(final File file) {
434         boolean result = false;
435         String name = file.getName();
436         if (name.startsWith("struts-config") && name.endsWith(".xml")) {
437             result = true;
438         } else {
439             int selectedOption = JOptionPane.showOptionDialog(
440                 this,
441                 "Guessing from the name, the file you are about to open does "
442                 + "not seem to be a Struts configuration.\n"
443                 + " Do you still want to open it?",
444                 "Open " + name + "?",
445                 JOptionPane.YES_NO_OPTION,
446                 JOptionPane.QUESTION_MESSAGE,
447                 null,
448                 new Object[] {"Yes", "No"},
449                 null);
450             if (selectedOption == 0) {
451                 result = true;
452             }
453         }
454         return result;
455     }
456 
457     /***
458      * Really does attempt to open the Struts config.
459      *
460      * @throws Exception if anything goes unexpectedly wrong
461      */
462     private void doOpenStrutsConfig() throws Exception {
463         final WaitDialog waitDialog = new WaitDialog(this, true);
464         Runnable runnable = new Runnable() {
465             public void run() {
466                 try {
467                     final StrutsConfig strutsConfig =
468                         mosaique.openStrutsConfig();
469                     SwingUtilities.invokeAndWait(new Runnable() {
470                         public void run() {
471                             updateGuiAfterOpenProject(strutsConfig);
472                         }
473                     });
474                 } catch (Exception e) {
475                     e.printStackTrace();
476                     final String message = e.getMessage();
477                     try {
478                         SwingUtilities.invokeAndWait(new Runnable() {
479                             public void run() {
480                                 waitDialog.dispose();
481                                 JOptionPane.showMessageDialog(
482                                     MosaiqueFrame.this,
483                                     "Encountered '" + message + "'",
484                                     "Error",
485                                     JOptionPane.ERROR_MESSAGE);
486                             }
487                         });
488                     } catch (Exception f) {
489                         f.printStackTrace();
490                     }
491                 } finally {
492                     try {
493                         SwingUtilities.invokeAndWait(new Runnable() {
494                             public void run() {
495                                 waitDialog.dispose();
496                             }
497                         });
498                     } catch (Exception e) {
499                         e.printStackTrace();
500                     }
501                 }
502             }
503         };
504 
505         Thread thread = new Thread(runnable);
506         thread.start();
507         waitDialog.setVisible(true);
508     }
509 
510     /***
511      * Updates the GUI after a project was opened.
512      *
513      * @param strutsConfig The Struts config that was loaded.
514      */
515     private void updateGuiAfterOpenProject(final StrutsConfig strutsConfig) {
516         DefaultTreeModel treeModel = new DefaultTreeModel(null);
517         TreeNode rootNode =
518             new StrutsConfigNode(
519                 strutsConfig,
520                 treeModel);
521         treeModel.setRoot(rootNode);
522         currentMosaiqueTree =
523             new MosaiqueTree(
524                 mosaique,
525                 MosaiqueFrame.this,
526                 treeModel);
527         final int rulesNodeRow = 1;
528         currentMosaiqueTree.expandRow(rulesNodeRow);
529         updateContentPane();
530     }
531 
532     /***
533      * Exits the Mosaique application.
534      */
535     private void exit() {
536         boolean doExit = true;
537         if (doExit) {
538             mosaique.setProperty("mosaiqueFrame.x", getX() + "");
539             mosaique.setProperty("mosaiqueFrame.y", getY() + "");
540             mosaique.setProperty("mosaiqueFrame.height", getHeight() + "");
541             mosaique.setProperty("mosaiqueFrame.width", getWidth() + "");
542             mosaique.setProperty(
543                 "mosaiqueFrame.horizontalSplitPane.dividerLocation",
544                 horizontalSplitPane.getDividerLocation() + "");
545             mosaique.setProperty(
546                 "mosaiqueFrame.verticalSplitPane.dividerLocation",
547                 verticalSplitPane.getDividerLocation() + "");
548             mosaique.exitApplication();
549         }
550     }
551 
552     /***
553      * Launches a browser to display a URL.
554      *
555      * @param url The URL to display.
556      */
557     private void openUrl(final String url) {
558         BrowserLauncher.openUrl(url);
559     }
560 
561     /***
562      * Shows the about dialog of Deputy.
563      */
564     private void showAbout() {
565         JOptionPane.showMessageDialog(
566             this,
567             "Version: " + mosaique.getVersion() + "\n\n"
568             + "(c) 2005 Matthias Burbach. All rights reserved.\n\n"
569             + "This open source product is licensed under the BSD licence\n"
570             + "and includes software developed by other open source "
571             + "initiatives."
572             ,
573             "About Mosaique",
574             JOptionPane.INFORMATION_MESSAGE);
575     }
576 
577     /***
578      * Opens the file associated with the currently selected tree node in the
579      * right panel.
580      *
581      * @param filePath The full path of the file to open.
582      * @param primarySearchText The text to search for in the file to display in
583      *                          order to scroll to the text found.
584      *                          Will be ignored if beginSelection is greater -1
585      *                          and endSelection is greater -1.
586      * @param secondarySearchText Can be <code>null</code>.
587      *                            If not <code>null</code> a second search
588      *                            relative to the found primarySearchText will
589      *                            be performed to determine the text to scroll
590      *                            to. Will be ignored if primarySearchText will
591      *                            ignored.
592      * @param beginSelection Marks the character position to begin text
593      *                       selection at. Will be ignored if equal to -1.
594      * @param endSelection Marks the character position to end text
595      *                     selection at.
596      *                     Will be ignored if beginSelection will
597      *                     be ignored.
598      */
599     public void openFile(
600             final String filePath,
601             final String primarySearchText,
602             final String secondarySearchText,
603             final int beginSelection,
604             final int endSelection) {
605         String pathOnly = FileUtils.getPathWithoutName(filePath);
606         String nameOnly = FileUtils.getNameWithoutPath(filePath);
607         filePanel.setFile(pathOnly, nameOnly, primarySearchText,
608                 secondarySearchText, beginSelection, endSelection);
609         rightScrollPane.setBorder(
610                 BorderFactory.createTitledBorder("File: " + filePath));
611     }
612 
613     /***
614      * Starts the application.
615      *
616      * @param args Optional command line argument is -project=&lt;file name&gt;
617      *             which will immediately load that file into Deputy on start
618      *             up.
619      */
620     public static void main(final String[] args) {
621         try {
622             Mosaique mosaique = new Mosaique(new SimpleLog());
623             MosaiqueFrame mosaiqueFrame = new MosaiqueFrame(mosaique);
624 
625             if (args.length > 0 && args[0] != null) {
626                 if (args[0].startsWith("-struts=")) {
627                     String strutsFileName =
628                         args[0].substring("-struts=".length());
629                     if ((new File(strutsFileName)).exists()) {
630                         mosaique.setStrutsConfigFile(strutsFileName);
631                         mosaiqueFrame.doOpenStrutsConfig();
632                     } else {
633                         throw new IllegalArgumentException();
634                     }
635                 } else {
636                     throw new IllegalArgumentException();
637                 }
638             }
639         } catch (IllegalArgumentException e) {
640             System.err.println(
641                 "Invalid arguments, usage is: "
642                 + MosaiqueFrame.class.getName()
643                 + " [-struts=<absolute path of Struts config file>]");
644         } catch (Exception e) {
645             e.printStackTrace();
646         }
647     }
648 }