00001
00056 package com.arcle.rmt.j2se.swing;
00057
00058 import javax.swing.*;
00059 import javax.swing.filechooser.*;
00060 import javax.swing.event.*;
00061 import java.awt.Frame;
00062 import java.awt.Graphics;
00063 import java.awt.Container;
00064 import java.awt.Toolkit;
00065 import java.awt.Dimension;
00066 import java.awt.BorderLayout;
00067 import java.awt.Component;
00068 import java.awt.event.KeyEvent;
00069 import java.awt.event.WindowAdapter;
00070 import java.awt.event.WindowEvent;
00071 import java.awt.event.ActionEvent;
00072 import java.beans.PropertyChangeEvent;
00073 import java.beans.PropertyChangeListener;
00074 import java.util.Observer;
00075 import java.util.Observable;
00076 import java.util.List;
00077 import java.util.ArrayList;
00078 import java.util.Collection;
00079 import java.util.Iterator;
00080 import java.util.Map;
00081 import java.util.Hashtable;
00082 import java.io.File;
00083 import java.io.IOException;
00084
00085 import com.arcle.rmt.j2se.swing.ui.MoreSwingUtilities;
00086 import com.arcle.rmt.j2se.swing.ui.LookAndFeelSelector;
00087 import com.arcle.rmt.j2se.swing.ui.ExceptionSafeActionWrapper;
00088 import com.arcle.rmt.j2se.swing.vc.AbstractDesktop;
00089 import com.arcle.rmt.j2se.util.ExceptionWrapper;
00090 import com.arcle.rmt.j2se.util.MoreCollections;
00091 import com.arcle.rmt.j2se.util.CollectionVisitor;
00092 import com.arcle.rmt.j2se.framework.Document;
00093
00099 public abstract class AbstractMainWindow extends JApplet implements Observer {
00100
00101
00102
00103
00110 protected void cmFileNew() {
00111 if (!doAskSave()) {
00112 return;
00113 }
00114 doNewDocument();
00115 updateTitle();
00116 }
00117
00126 protected void cmFileOpen() {
00127 JFileChooser fc = getFileChooser();
00128 int option = fc.showOpenDialog(this);
00129 File selectedFile = fc.getSelectedFile();
00130 switch (option) {
00131 case JFileChooser.APPROVE_OPTION:
00132 if (selectedFile != null) {
00133 setDocumentFile(getCurrentFileHandler().filterFile(selectedFile));
00134 doOpenDocument();
00135 } else {
00136 JOptionPane.showMessageDialog(this,
00137 "You didn't specify a file. Open aborted.",
00138 "Open File",
00139 JOptionPane.ERROR_MESSAGE);
00140 }
00141 break;
00142 case JFileChooser.CANCEL_OPTION:
00143 break;
00144 case JFileChooser.ERROR_OPTION:
00145 break;
00146 }
00147 updateTitle();
00148 }
00149
00159 protected void cmFileSave() {
00160 if (getDocumentFile() == null) {
00161 cmFileSaveAs();
00162 return;
00163 }
00164 doSaveDocument();
00165 updateTitle();
00166 }
00167
00177 protected void cmFileSaveAs() {
00178 JFileChooser fc = getFileChooser();
00179 int option = fc.showSaveDialog(this);
00180 File selectedFile = fc.getSelectedFile();
00181 switch (option) {
00182 case JFileChooser.APPROVE_OPTION:
00183 if (selectedFile != null) {
00184 setDocumentFile(getCurrentFileHandler().filterFile(selectedFile));
00185 doSaveDocument();
00186 } else {
00187 JOptionPane.showMessageDialog(this,
00188 "You didn't specify a file name. Save aborted.",
00189 "Save As",
00190 JOptionPane.ERROR_MESSAGE);
00191 }
00192 break;
00193 case JFileChooser.CANCEL_OPTION:
00194 break;
00195 case JFileChooser.ERROR_OPTION:
00196 break;
00197 }
00198 updateTitle();
00199 }
00200
00207 protected void cmFileRevert() {
00208 File docFile = getDocumentFile();
00209 if (docFile == null) {
00210 return;
00211 }
00212
00213 int answer = JOptionPane.showConfirmDialog(this,
00214 "Do you want to re-load the document from disk and lose your changes?",
00215 "Revert to Saved",
00216 JOptionPane.YES_NO_OPTION);
00217 switch (answer) {
00218 case JOptionPane.YES_OPTION:
00219 doOpenDocument();
00220 break;
00221 case JOptionPane.NO_OPTION:
00222
00223 break;
00224 default:
00225 throw new IllegalStateException(
00226 "Unknown value returned from JOptionPane.showConfirmDialog(): "
00227 + answer);
00228 }
00229 }
00230
00236 protected void cmFileExit() {
00237 if (!doAskSave()) {
00238 return;
00239 }
00240 doExit();
00241 }
00242
00243
00244
00245
00249 protected abstract void doNewDocument();
00250
00254 protected void doSaveDocument() {
00255 try {
00256 FileHandler handler = getCurrentFileHandler();
00257 StringBuffer log = new StringBuffer();
00258
00259 handler.saveFile(getDocument(), getDocumentFile(), log);
00260 if (log.length() > 0) {
00261 MoreSwingUtilities.showTextDialog(this, log.toString(),
00262 "Error(s) occured while saving document",
00263 JOptionPane.ERROR_MESSAGE);
00264 } else {
00265 setDirty(false);
00266 }
00267 } catch(ExceptionWrapper e) {
00268 showException(e);
00269 }
00270 }
00271
00275 protected void doOpenDocument() {
00276 try {
00277 FileHandler handler = getCurrentFileHandler();
00278 StringBuffer log = new StringBuffer();
00279 setDocument(handler.openFile(getDocumentFile(), log));
00280 if (log.length() > 0) {
00281 MoreSwingUtilities.showTextDialog(this, log.toString(),
00282 "Error(s) occured while opening document",
00283 JOptionPane.ERROR_MESSAGE);
00284 } else {
00285 setDirty(false);
00286 }
00287 } catch(ExceptionWrapper e) {
00288 showException(e);
00289 }
00290 }
00291
00292
00300 protected boolean doAskSave() {
00301 if (isDirty()) {
00302
00303 int answer = JOptionPane.showConfirmDialog(this,
00304 "The current document has been changed, do you want to save it?",
00305 "Save Document",
00306 JOptionPane.YES_NO_CANCEL_OPTION);
00307 switch (answer) {
00308 case JOptionPane.YES_OPTION:
00309 cmFileSave();
00310 break;
00311 case JOptionPane.NO_OPTION:
00312
00313 break;
00314 case JOptionPane.CANCEL_OPTION:
00315 return false;
00316 default:
00317 throw new IllegalStateException(
00318 "Unknown value returned from JOptionPane.showConfirmDialog(): "
00319 + answer);
00320 }
00321 }
00322 return true;
00323 }
00324
00328 protected void doExit() {
00329
00330 if (applicationFrame != null) {
00331 applicationFrame.dispose();
00332
00333 SwingUtilities.invokeLater(new Runnable() {
00334 public void run() {
00335 System.exit(0);
00336 }
00337 });
00338 }
00342 }
00343
00344
00345
00346
00347 public void init() {
00348
00349 Container cp = getContentPane();
00350 cp.setLayout(new BorderLayout());
00351 cp.add(getDesktop(), BorderLayout.CENTER);
00352 initMenu();
00353
00354 SwingUtilities.invokeLater(new Runnable() {
00355 public void run() {
00356 try {
00357 cmFileNew();
00358 System.gc();
00359 } catch(Exception e) {
00360 showException(e);
00361 } catch(NoClassDefFoundError e) {
00362
00363
00364 MoreSwingUtilities.showThrowable(AbstractMainWindow.this,
00365 e, "Fatal Error - Application Component Not Found");
00366 }
00367 }
00368 });
00369 }
00370
00371 public void start() {
00372
00373
00374
00375 SwingUtilities.invokeLater(new Runnable() {
00376 public void run() {
00377 updateTitle();
00378 }
00379 });
00380 }
00381
00382 public void stop() { }
00383
00384 public void destroy() { }
00385
00386
00387
00388
00389 public void update(Observable source, Object param) {
00390 if (source == getDocument()) {
00391
00392 SwingUtilities.invokeLater(new Runnable() {
00393 public void run() {
00394 setDirty(true);
00395 }
00396 });
00397 }
00398 }
00399
00400
00401
00402
00403
00404 protected void updateTitle() {
00405 if (applicationFrame != null) {
00406 File doc = getDocumentFile();
00407 String title = APPLICATION_TITLE;
00408 String fileName = doc != null ? " - [" + doc.getName() + "] " : FILE_UNTITILED;
00409 String modified = !isDirty() && doc != null ? FILE_UNMODIFIED : "";
00410 applicationFrame.setTitle(title + fileName + modified);
00411 }
00412 }
00413
00418 protected void showException(Exception e) {
00419 MoreSwingUtilities.showException(this, e);
00420 }
00421
00422
00423
00424
00425
00429 protected void initMenu() {
00430 JMenuBar mb = createMenuBar();
00431 JMenu file = createMenu();
00432 JMenu requirement = createMenu();
00433 JMenu tools = createMenu();
00434 JMenu help = createMenu();
00435
00436
00437 file.setText("File");
00438 requirement.setText("Requirement");
00439 tools.setText("Tools");
00440 help.setText("Help");
00441
00442 file.setMnemonic(KeyEvent.VK_F);
00443 requirement.setMnemonic(KeyEvent.VK_R);
00444 tools.setMnemonic(KeyEvent.VK_T);
00445 help.setMnemonic(KeyEvent.VK_H);
00446
00447
00448 initFileMenu(file);
00449 initRequirementMenu(requirement);
00450 initToolsMenu(tools);
00451 initHelpMenu(help);
00452
00453
00454 mb.add(file);
00455 mb.add(requirement);
00456 mb.add(tools);
00457 mb.add(help);
00458
00459
00460 setJMenuBar(mb);
00461 }
00462
00466 protected void initFileMenu(JMenu file) {
00467
00468 file.add(createFileNewAction());
00469 file.add(createFileOpenAction());
00470 file.add(createFileSaveAction());
00471 file.add(createFileSaveAsAction());
00472 file.addSeparator();
00473 file.add(createFileRevertAction());
00474 file.addSeparator();
00475 file.add(createFileExitAction());
00476 }
00477
00481 protected void initRequirementMenu(JMenu reqMenu) {
00482 Iterator iter = getDesktop().getController().getActions().iterator();
00483 while (iter.hasNext()) {
00484 reqMenu.add(createMenuItem((Action) iter.next()));
00485 }
00486 }
00487
00491 protected void initHelpMenu(JMenu help) {
00492 help.add(createHelpAboutAction());
00493 }
00494
00498 protected void initToolsMenu(JMenu tools) {
00499 JMenu selectorMenu = LookAndFeelSelector.getInstance().getSelectorMenu();
00500 final AbstractMainWindow mainWindow = this;
00501
00502 selectorMenu.addMenuListener(new MenuListener() {
00503 public void menuCanceled(MenuEvent e) { }
00504 public void menuDeselected(MenuEvent e) {
00505 SwingUtilities.invokeLater(new Runnable() {
00506 public void run() {
00507 LookAndFeel curLF = UIManager.getLookAndFeel();
00508 if (curLF != lastLF) {
00509
00510 SwingUtilities.updateComponentTreeUI(mainWindow);
00511 lastLF = curLF;
00512 _fileChooser = null;
00513 }
00514 }
00515 });
00516 }
00517 public void menuSelected(MenuEvent e) { }
00518 LookAndFeel lastLF = UIManager.getLookAndFeel();
00519 });
00520
00521 tools.add(selectorMenu);
00522 }
00523
00524
00528 protected void runAsApplication(String[] args) {
00529 final AbstractMainWindow app = this;
00530 applicationFrame = createApplicationFrame(app);
00531 applicationFrame .addWindowListener(new WindowAdapter() {
00532 public void windowActivated(WindowEvent e) {
00533 app.start();
00534 }
00535 public void windowDeactivated(WindowEvent e) {
00536 app.stop();
00537 }
00538 public void windowClosing(WindowEvent e) {
00539 app.cmFileExit();
00540 }
00541 public void windowClosed() {
00542 app.destroy();
00543 }
00544 });
00545 init();
00546
00547 applicationFrame.setVisible(true);
00548 }
00549
00550
00551
00552
00556 protected AbstractDesktop getDesktop() {
00557 if (_desktop == null) {
00558 _desktop = createDesktop();
00559 }
00560 return _desktop;
00561 }
00562
00565 public boolean isDirty() {
00566 return dirty;
00567 }
00568
00572 protected void setDirty(boolean d) {
00573 if (d != dirty) {
00574 boolean old = dirty;
00575 dirty = d;
00576 firePropertyChange("dirty", new Boolean(old), new Boolean(d));
00577 updateTitle();
00578 }
00579 }
00580
00584 protected Document getDocument() {
00585 return document;
00586 }
00587
00591 protected void setDocument(Document d) {
00592 Document old = document;
00593 if (document != null) {
00594
00595 document.deleteObserver(this);
00596 }
00597 document = d;
00598 if (document != null) {
00599
00600 document.addObserver(this);
00601 }
00602 getDesktop().setDocument(d);
00603 firePropertyChange("document", old, document);
00604 }
00605
00609 protected void setDocumentFile(File f) {
00610 File old = documentFile;
00611 documentFile = f;
00612 firePropertyChange("documentFile", old, documentFile);
00613 }
00614
00615 protected File getDocumentFile() {
00616 return documentFile;
00617 }
00618
00619 protected JFileChooser getFileChooser() {
00620 if (_fileChooser == null) {
00621 _fileChooser = createFileChooser();
00622 }
00623
00624 return _fileChooser;
00625 }
00626
00630 protected Map getFileHandlers() {
00631 if (_fileHandlers == null) {
00632 Collection handlers = createFileHandlers();
00633 _fileHandlers = new Hashtable(handlers.size(), 1);
00634 CollectionVisitor visitor = new CollectionVisitor() {
00635 public boolean visitCollection(Object member) {
00636 FileHandler fh = (FileHandler) member;
00637 _fileHandlers.put(fh.getFileFilter(), fh);
00638 return true;
00639 }
00640 };
00641 MoreCollections.visitCollection(handlers, visitor);
00642 }
00643 return _fileHandlers;
00644 }
00645
00649 protected abstract FileHandler getDefaultFileHandler();
00650
00655 protected Collection getFileFilters() {
00656 return getFileHandlers().keySet();
00657
00658 }
00659
00663 protected FileHandler getCurrentFileHandler() {
00664 return (FileHandler) getFileHandlers().get(getFileChooser()
00665 .getFileFilter());
00666 }
00667
00668
00669
00670
00671
00676 protected abstract AbstractDesktop createDesktop();
00677
00683 protected abstract Collection createFileHandlers();
00684
00692 protected Frame createApplicationFrame(Component content) {
00693
00694
00695 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
00696 int WIDTH = screenSize.width * 3 / 4;
00697 int HEIGHT = screenSize.height * 3 / 4;
00698
00699 JFrame applicationFrame = new JFrame();
00700 applicationFrame.getContentPane().add(content);
00701
00702
00703
00704
00705
00706
00707
00708 applicationFrame.pack();
00709
00710 applicationFrame.setSize(WIDTH, HEIGHT);
00711 applicationFrame.setLocation((screenSize.width - WIDTH)/2,
00712 (screenSize.height - HEIGHT)/2);
00713 return applicationFrame;
00714 }
00715
00720 protected JFileChooser createFileChooser() {
00721 Iterator filters = getFileFilters().iterator();
00722 JFileChooser chooser = new JFileChooser();
00723 while (filters.hasNext()) {
00724 chooser.addChoosableFileFilter((FileFilter) filters.next());
00725 }
00726 chooser.setFileFilter(getDefaultFileHandler().getFileFilter());
00727 chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
00728 chooser.setMultiSelectionEnabled(false);
00729 chooser.setAcceptAllFileFilterUsed(false);
00730 return chooser;
00731 }
00732
00733
00738 protected JMenuBar createMenuBar() {
00739 return new JMenuBar();
00740 }
00741
00745 protected JMenu createMenu() {
00746 return new JMenu();
00747 }
00748
00752 protected JMenuItem createMenuItem() {
00753 return new JMenuItem();
00754 }
00755
00759 protected JMenuItem createMenuItem(final Action action) {
00760 if (action == null) {
00761 throw new IllegalArgumentException("Parameter 'action' cannot be null.");
00762 }
00763 JMenuItem mi = new JMenuItem(action);
00764 return mi;
00765 }
00766
00770 protected Action createActionWrapper(Action wrapped) {
00771 return new ExceptionSafeActionWrapper(this, wrapped);
00772 }
00773
00777 protected Action createFileNewAction() {
00778 return createActionWrapper(new FileNewAction());
00779 }
00780
00784 protected Action createFileOpenAction() {
00785 return createActionWrapper(new FileOpenAction());
00786 }
00787
00791 protected Action createFileSaveAction() {
00792 return createActionWrapper(new FileSaveAction());
00793 }
00794
00798 protected Action createFileSaveAsAction() {
00799 return createActionWrapper(new FileSaveAsAction());
00800 }
00801
00805 protected Action createFileRevertAction() {
00806 return createActionWrapper(new FileRevertAction());
00807 }
00808
00812 protected Action createFileExitAction() {
00813 return createActionWrapper(new FileExitAction());
00814 }
00815
00819 protected Action createHelpAboutAction() {
00820 return createActionWrapper(new HelpAboutAction());
00821 }
00822
00823
00824
00825
00826 private AbstractDesktop _desktop = null;
00827
00831 private boolean dirty = false;
00832
00836 private Document document = null;
00837
00840 private File documentFile = null;
00841
00845 private Frame applicationFrame = null;
00846
00850 private JFileChooser _fileChooser = null;
00851
00855 private Map _fileHandlers = null;
00856
00860 protected static final String APPLICATION_TITLE = "Rambutan Desktop";
00861
00865 protected static final String FILE_UNMODIFIED = "(Unmodified)";
00866
00867 protected static final String FILE_UNTITILED = " -untitled- ";
00868
00869
00870
00871
00875 protected abstract class FileHandler {
00879 public abstract FileFilter getFileFilter();
00880
00884 public abstract Document openFile(File f, StringBuffer log)
00885 throws ExceptionWrapper;
00886
00890 public abstract void saveFile(Document doc, File f, StringBuffer log)
00891 throws ExceptionWrapper;
00892
00900 public abstract File filterFile(File inputFile);
00901
00911 protected File assignExtension(File file, String ext) {
00912 try {
00913
00914
00915
00916 if (file.exists() || file.getName().lastIndexOf('.') > 0) {
00917 return file;
00918 }
00919 String newName = file.getCanonicalPath() + '.' + ext;
00920 return new File(newName);
00921 } catch(IOException e) {
00922 showException(e);
00923 return file;
00924 }
00925 }
00926 }
00927
00928
00929
00930
00931
00932
00937 protected class FileNewAction extends AbstractAction {
00938 public FileNewAction() {
00939 putValue(NAME, "New");
00940 putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_N));
00941 putValue(SHORT_DESCRIPTION, "Creates a new blank document.");
00942 putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK));
00943 setEnabled(true);
00944 }
00945
00946
00947
00948
00949 public void actionPerformed(ActionEvent e) {
00950 cmFileNew();
00951 }
00952
00953 }
00954
00959 protected class FileOpenAction extends AbstractAction {
00960 public FileOpenAction() {
00961 putValue(NAME, "Open");
00962 putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
00963 putValue(SHORT_DESCRIPTION, "Opens a document from the disk.");
00964 putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK));
00965 setEnabled(true);
00966 }
00967
00968 public void actionPerformed(ActionEvent e) {
00969 cmFileOpen();
00970 }
00971 }
00972
00977 protected class FileSaveAction extends AbstractAction
00978 implements PropertyChangeListener {
00979 public FileSaveAction() {
00980 putValue(NAME, "Save");
00981 putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_S));
00982 putValue(SHORT_DESCRIPTION, "Saves the current document.");
00983 putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
00984 setEnabled(isDirty());
00985 AbstractMainWindow.this.addPropertyChangeListener(this);
00986 }
00987
00988 public void actionPerformed(ActionEvent e) {
00989 cmFileSave();
00990 }
00991
00992
00993
00994
00995 public void propertyChange(PropertyChangeEvent evt) {
00996 if (evt.getSource() == AbstractMainWindow.this) {
00997 if ("dirty".equals(evt.getPropertyName())) {
00998 boolean dirty =((Boolean)evt.getNewValue()).booleanValue();
00999 setEnabled(dirty);
01000 }
01001 }
01002 }
01003
01004 }
01005
01010 protected class FileSaveAsAction extends AbstractAction {
01011 public FileSaveAsAction() {
01012 putValue(NAME, "Save As");
01013 putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_A));
01014 putValue(SHORT_DESCRIPTION, "Saves the current document as a new file.");
01015 setEnabled(true);
01016 }
01017
01018 public void actionPerformed(ActionEvent e) {
01019 cmFileSaveAs();
01020 }
01021 }
01022
01027 protected class FileRevertAction extends AbstractAction
01028 implements PropertyChangeListener {
01029 public FileRevertAction() {
01030 putValue(NAME, "Revert to Saved");
01031 putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_R));
01032 putValue(SHORT_DESCRIPTION, "Re-loads the current document from disk.");
01033 AbstractMainWindow.this.addPropertyChangeListener(this);
01034 checkEnabled();
01035 }
01036
01037 public void actionPerformed(ActionEvent e) {
01038 cmFileSaveAs();
01039 }
01040
01041
01042
01043 protected void checkEnabled() {
01044 setEnabled(isDirty() && getDocumentFile() != null);
01045 }
01046
01047
01048
01049
01050 public void propertyChange(PropertyChangeEvent evt) {
01051 if (evt.getSource() == AbstractMainWindow.this) {
01052 if ("dirty".equals(evt.getPropertyName())
01053 || "document".equals(evt.getPropertyName())) {
01054 checkEnabled();
01055 }
01056 }
01057 }
01058 }
01059
01064 protected class FileExitAction extends AbstractAction {
01065 public FileExitAction () {
01066 putValue(NAME, "Exit");
01067 putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_X));
01068 putValue(SHORT_DESCRIPTION, "Terminates the current application.");
01069 setEnabled(applicationFrame != null);
01070 }
01071
01072 public void actionPerformed(ActionEvent e) {
01073 cmFileExit();
01074 }
01075 }
01076
01081 protected class HelpAboutAction extends AbstractAction {
01082 public HelpAboutAction() {
01083 putValue(NAME, "About");
01084 putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_A));
01085 putValue(SHORT_DESCRIPTION, "Displays version and copyright information.");
01086 setEnabled(true);
01087 }
01088
01089 public void actionPerformed(ActionEvent e) {
01090 new AboutDialog(AbstractMainWindow.this).show();
01091 }
01092 }
01093 }