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
178
179
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
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
215
216 rightScrollPane = new JScrollPane();
217 rightScrollPane.setBorder(
218 BorderFactory.createTitledBorder("File"));
219 rightScrollPane.getViewport().add(filePanel);
220
221
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
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
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
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=<file name>
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 }