Bug fixes and startup checks
[debian/openrocket] / src / net / sf / openrocket / gui / main / BasicFrame.java
1 package net.sf.openrocket.gui.main;
2
3 import java.awt.Dimension;
4 import java.awt.Font;
5 import java.awt.Toolkit;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.event.ComponentAdapter;
9 import java.awt.event.ComponentEvent;
10 import java.awt.event.KeyEvent;
11 import java.awt.event.MouseAdapter;
12 import java.awt.event.MouseEvent;
13 import java.awt.event.MouseListener;
14 import java.awt.event.WindowAdapter;
15 import java.awt.event.WindowEvent;
16 import java.io.File;
17 import java.io.IOException;
18 import java.util.ArrayList;
19 import java.util.Iterator;
20
21 import javax.swing.Action;
22 import javax.swing.InputMap;
23 import javax.swing.JButton;
24 import javax.swing.JComponent;
25 import javax.swing.JFileChooser;
26 import javax.swing.JFrame;
27 import javax.swing.JMenu;
28 import javax.swing.JMenuBar;
29 import javax.swing.JMenuItem;
30 import javax.swing.JOptionPane;
31 import javax.swing.JPanel;
32 import javax.swing.JScrollPane;
33 import javax.swing.JSeparator;
34 import javax.swing.JSplitPane;
35 import javax.swing.JTabbedPane;
36 import javax.swing.KeyStroke;
37 import javax.swing.LookAndFeel;
38 import javax.swing.ScrollPaneConstants;
39 import javax.swing.SwingUtilities;
40 import javax.swing.ToolTipManager;
41 import javax.swing.UIManager;
42 import javax.swing.border.TitledBorder;
43 import javax.swing.event.TreeSelectionEvent;
44 import javax.swing.event.TreeSelectionListener;
45 import javax.swing.filechooser.FileFilter;
46 import javax.swing.tree.DefaultTreeSelectionModel;
47 import javax.swing.tree.TreePath;
48 import javax.swing.tree.TreeSelectionModel;
49
50 import net.miginfocom.swing.MigLayout;
51 import net.sf.openrocket.aerodynamics.Warning;
52 import net.sf.openrocket.document.OpenRocketDocument;
53 import net.sf.openrocket.file.GeneralRocketLoader;
54 import net.sf.openrocket.file.OpenRocketSaver;
55 import net.sf.openrocket.file.RocketLoadException;
56 import net.sf.openrocket.file.RocketLoader;
57 import net.sf.openrocket.file.RocketSaver;
58 import net.sf.openrocket.gui.ComponentAnalysisDialog;
59 import net.sf.openrocket.gui.PreferencesDialog;
60 import net.sf.openrocket.gui.StorageOptionChooser;
61 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
62 import net.sf.openrocket.gui.dialogs.BugDialog;
63 import net.sf.openrocket.gui.scalefigure.RocketPanel;
64 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
65 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
66 import net.sf.openrocket.rocketcomponent.Rocket;
67 import net.sf.openrocket.rocketcomponent.RocketComponent;
68 import net.sf.openrocket.rocketcomponent.Stage;
69 import net.sf.openrocket.util.Icons;
70 import net.sf.openrocket.util.Prefs;
71
72 public class BasicFrame extends JFrame {
73         private static final long serialVersionUID = 1L;
74
75         /**
76          * The RocketLoader instance used for loading all rocket designs.
77          */
78         private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader();
79
80         
81         /**
82          * File filter for filtering only rocket designs.
83          */
84         private static final FileFilter ROCKET_DESIGN_FILTER = new FileFilter() {
85                 @Override
86                 public String getDescription() {
87                         return "OpenRocket designs (*.ork)";
88                 }
89                 @Override
90                 public boolean accept(File f) {
91                         if (f.isDirectory())
92                                 return true;
93                         String name = f.getName().toLowerCase();
94                         return name.endsWith(".ork") || name.endsWith(".ork.gz");
95                 }
96     };
97     
98     
99
100         /**
101          * List of currently open frames.  When the list goes empty
102          * it is time to exit the application.
103          */
104         private static final ArrayList<BasicFrame> frames = new ArrayList<BasicFrame>();
105         
106         
107         
108         
109         
110         /**
111          * Whether "New" and "Open" should replace this frame.
112          * Should be set to false on the first rocket modification.
113          */
114         private boolean replaceable = false;
115         
116         
117         
118         private final OpenRocketDocument document;
119         private final Rocket rocket;
120         
121         private RocketPanel rocketpanel;
122         private ComponentTree tree = null;
123         private final TreeSelectionModel componentSelectionModel;
124         // private final ListSelectionModel simulationSelectionModel; ...
125         
126         /** Actions available for rocket modifications */
127         private final RocketActions actions;
128         
129         
130         
131         /**
132          * Sole constructor.  Creates a new frame based on the supplied document
133          * and adds it to the current frames list.
134          * 
135          * @param document      the document to show.
136          */
137         public BasicFrame(OpenRocketDocument document) {
138
139                 this.document = document;
140                 this.rocket = document.getRocket();
141                 this.rocket.getDefaultConfiguration().setAllStages();
142                 
143                 
144                 // Set replaceable flag to false at first modification
145                 rocket.addComponentChangeListener(new ComponentChangeListener() {
146                         public void componentChanged(ComponentChangeEvent e) {
147                                 replaceable = false;
148                                 BasicFrame.this.rocket.removeComponentChangeListener(this);
149                         }
150                 });
151                 
152                 
153                 // Create the selection model that will be used
154                 componentSelectionModel = new DefaultTreeSelectionModel();
155                 componentSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
156                 
157                 actions = new RocketActions(document, componentSelectionModel, this);
158                 
159                 
160                 // The main vertical split pane         
161                 JSplitPane vertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
162                 vertical.setResizeWeight(0.5);
163                 this.add(vertical);
164
165                 
166                 // The top tabbed pane
167                 JTabbedPane tabbed = new JTabbedPane();
168                 tabbed.addTab("Rocket design", null, designTab());
169                 tabbed.addTab("Flight simulations", null, simulationsTab());
170                 
171                 vertical.setTopComponent(tabbed);
172                 
173
174
175                 //  Bottom segment, rocket figure
176                 
177                 rocketpanel = new RocketPanel(document);
178                 vertical.setBottomComponent(rocketpanel);
179
180                 rocketpanel.setSelectionModel(tree.getSelectionModel());
181
182                                         
183                 createMenu();
184                 
185                 
186                 rocket.addComponentChangeListener(new ComponentChangeListener() {
187                         public void componentChanged(ComponentChangeEvent e) {
188                                 setTitle();
189                         }
190                 });
191                 
192                 setTitle();
193                 this.pack();
194
195                 Dimension size = Prefs.getWindowSize(this.getClass());
196                 if (size == null) {
197                         size = Toolkit.getDefaultToolkit().getScreenSize();
198                         size.width = size.width*9/10;
199                         size.height = size.height*9/10;
200                 }
201                 this.setSize(size);
202                 this.addComponentListener(new ComponentAdapter() {
203                         @Override
204                         public void componentResized(ComponentEvent e) {
205                                 Prefs.setWindowSize(BasicFrame.this.getClass(), BasicFrame.this.getSize());
206                         }
207                 });
208                 this.setLocationByPlatform(true);
209                                 
210                 this.validate();
211                 vertical.setDividerLocation(0.4);
212                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
213                 addWindowListener(new WindowAdapter() {
214                         @Override
215                         public void windowClosing(WindowEvent e) {
216                                 closeAction();
217                         }
218                 });
219                 frames.add(this);
220                 
221         }
222         
223         
224         /**
225          * Construct the "Rocket design" tab.  This contains a horizontal split pane
226          * with the left component the design tree and the right component buttons
227          * for adding components.
228          */
229         private JComponent designTab() {
230                 JSplitPane horizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true);
231                 horizontal.setResizeWeight(0.5);
232
233
234                 //  Upper-left segment, component tree
235
236                 JPanel panel = new JPanel(new MigLayout("fill, flowy","","[grow]"));
237
238                 tree = new ComponentTree(rocket);
239                 tree.setSelectionModel(componentSelectionModel);
240
241                 // Remove JTree key events that interfere with menu accelerators
242                 InputMap im = SwingUtilities.getUIInputMap(tree, JComponent.WHEN_FOCUSED);
243                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK), null);
244                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK), null);
245                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK), null);
246                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.CTRL_MASK), null);
247                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK), null);
248                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK), null);
249                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK), null);
250
251
252                 
253                 // Double-click opens config dialog
254                 MouseListener ml = new MouseAdapter() {
255                         @Override
256                         public void mousePressed(MouseEvent e) {
257                                 int selRow = tree.getRowForLocation(e.getX(), e.getY());
258                                 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
259                                 if(selRow != -1) {
260                                         if(e.getClickCount() == 2) {
261                                                 // Double-click
262                                                 RocketComponent c = (RocketComponent)selPath.getLastPathComponent();
263                                                 ComponentConfigDialog.showDialog(BasicFrame.this, 
264                                                                 BasicFrame.this.document, c);
265                                         }
266                                 }
267                         }
268                 };
269                 tree.addMouseListener(ml);
270
271                 // Update dialog when selection is changed
272                 componentSelectionModel.addTreeSelectionListener(new TreeSelectionListener() {
273                         public void valueChanged(TreeSelectionEvent e) {
274                                 // Scroll tree to the selected item
275                                 TreePath path = componentSelectionModel.getSelectionPath();
276                                 if (path == null)
277                                         return;
278                                 tree.scrollPathToVisible(path);
279                                 
280                                 if (!ComponentConfigDialog.isDialogVisible())
281                                         return;
282                                 RocketComponent c = (RocketComponent)path.getLastPathComponent();
283                                 ComponentConfigDialog.showDialog(BasicFrame.this, 
284                                                 BasicFrame.this.document, c);
285                         }
286                 });
287
288                 // Place tree inside scroll pane
289                 JScrollPane scroll = new JScrollPane(tree);
290                 panel.add(scroll,"spany, grow, wrap");
291                 
292                 
293                 // Buttons
294                 JButton button = new JButton(actions.getMoveUpAction());
295                 panel.add(button,"sizegroup buttons, aligny 65%");
296                 
297                 button = new JButton(actions.getMoveDownAction());
298                 panel.add(button,"sizegroup buttons, aligny 0%");
299                 
300                 button = new JButton(actions.getEditAction());
301                 panel.add(button, "sizegroup buttons");
302                 
303                 button = new JButton(actions.getNewStageAction());
304                 panel.add(button,"sizegroup buttons");
305                 
306                 button = new JButton(actions.getDeleteAction());
307                 button.setIcon(null);
308                 button.setMnemonic(0);
309                 panel.add(button,"sizegroup buttons");
310
311                 horizontal.setLeftComponent(panel);
312
313
314                 //  Upper-right segment, component addition buttons
315
316                 panel = new JPanel(new MigLayout("fill, insets 0","[0::]"));
317
318                 scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
319                                 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
320                 scroll.setViewportView(new ComponentAddButtons(document, componentSelectionModel,
321                                 scroll.getViewport()));
322                 scroll.setBorder(null);
323                 scroll.setViewportBorder(null);
324
325                 TitledBorder border = new TitledBorder("Add new component");
326                 border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD));
327                 scroll.setBorder(border);
328
329                 panel.add(scroll,"grow");
330
331                 horizontal.setRightComponent(panel);
332
333                 return horizontal;
334         }
335         
336         
337         /**
338          * Construct the "Flight simulations" tab.
339          * @return
340          */
341         private JComponent simulationsTab() {
342                 return new SimulationPanel(document);
343         }
344         
345         
346         
347         /**
348          * Creates the menu for the window.
349          */
350         private void createMenu() {
351                 JMenuBar menubar = new JMenuBar();
352                 JMenu menu;
353                 JMenuItem item;
354                 
355                 ////  File
356                 menu = new JMenu("File");
357                 menu.setMnemonic(KeyEvent.VK_F);
358                 menu.getAccessibleContext().setAccessibleDescription("File-handling related tasks");
359                 menubar.add(menu);
360                 
361                 item = new JMenuItem("New",KeyEvent.VK_N);
362                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
363                 item.setMnemonic(KeyEvent.VK_N);
364                 item.getAccessibleContext().setAccessibleDescription("Create a new rocket design");
365                 item.setIcon(Icons.FILE_NEW);
366                 item.addActionListener(new ActionListener() {
367                         public void actionPerformed(ActionEvent e) {
368                                 newAction();
369                                 if (replaceable)
370                                         closeAction();
371                         }
372                 });
373                 menu.add(item);
374                 
375                 item = new JMenuItem("Open...",KeyEvent.VK_O);
376                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
377                 item.getAccessibleContext().setAccessibleDescription("Open a rocket design");
378                 item.setIcon(Icons.FILE_OPEN);
379                 item.addActionListener(new ActionListener() {
380                         public void actionPerformed(ActionEvent e) {
381                                 openAction();
382                         }
383                 });
384                 menu.add(item);
385                 
386                 menu.addSeparator();
387                 
388                 item = new JMenuItem("Save",KeyEvent.VK_S);
389                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
390                 item.getAccessibleContext().setAccessibleDescription("Save the current rocket design");
391                 item.setIcon(Icons.FILE_SAVE);
392                 item.addActionListener(new ActionListener() {
393                         public void actionPerformed(ActionEvent e) {
394                                 saveAction();
395                         }
396                 });
397                 menu.add(item);
398                 
399                 item = new JMenuItem("Save as...",KeyEvent.VK_A);
400                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, 
401                                 ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
402                 item.getAccessibleContext().setAccessibleDescription("Save the current rocket design "+
403                                 "to a new file");
404                 item.setIcon(Icons.FILE_SAVE_AS);
405                 item.addActionListener(new ActionListener() {
406                         public void actionPerformed(ActionEvent e) {
407                                 saveAsAction();
408                         }
409                 });
410                 menu.add(item);
411                 
412 //              menu.addSeparator();
413                 menu.add(new JSeparator());
414                 
415                 item = new JMenuItem("Close",KeyEvent.VK_C);
416                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK));
417                 item.getAccessibleContext().setAccessibleDescription("Close the current rocket design");
418                 item.setIcon(Icons.FILE_CLOSE);
419                 item.addActionListener(new ActionListener() {
420                         public void actionPerformed(ActionEvent e) {
421                                 closeAction();
422                         }
423                 });
424                 menu.add(item);
425                 
426                 menu.addSeparator();
427
428                 item = new JMenuItem("Quit",KeyEvent.VK_Q);
429                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
430                 item.getAccessibleContext().setAccessibleDescription("Quit the program");
431                 item.setIcon(Icons.FILE_QUIT);
432                 item.addActionListener(new ActionListener() {
433                         public void actionPerformed(ActionEvent e) {
434                                 quitAction();
435                         }
436                 });
437                 menu.add(item);
438                 
439                 
440
441                 ////  Edit
442                 menu = new JMenu("Edit");
443                 menu.setMnemonic(KeyEvent.VK_E);
444                 menu.getAccessibleContext().setAccessibleDescription("Rocket editing");
445                 menubar.add(menu);
446                 
447                 
448                 Action action = document.getUndoAction();
449                 item = new JMenuItem(action);
450                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
451                 item.setMnemonic(KeyEvent.VK_U);
452                 item.getAccessibleContext().setAccessibleDescription("Undo the previous operation");
453
454                 menu.add(item);
455
456                 action = document.getRedoAction();
457                 item = new JMenuItem(action);
458                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
459                 item.setMnemonic(KeyEvent.VK_R);
460                 item.getAccessibleContext().setAccessibleDescription("Redo the previously undone " +
461                                 "operation");
462                 menu.add(item);
463                 
464                 menu.addSeparator();
465                 
466                 
467                 item = new JMenuItem(actions.getCutAction());
468                 menu.add(item);
469         
470                 item = new JMenuItem(actions.getCopyAction());
471                 menu.add(item);
472         
473                 item = new JMenuItem(actions.getPasteAction());
474                 menu.add(item);
475                 
476                 item = new JMenuItem(actions.getDeleteAction());
477                 menu.add(item);
478                 
479                 menu.addSeparator();
480                 
481                 item = new JMenuItem("Preferences");
482                 item.setIcon(Icons.PREFERENCES);
483                 item.getAccessibleContext().setAccessibleDescription("Setup the application "+
484                                 "preferences");
485                 item.addActionListener(new ActionListener() {
486                         public void actionPerformed(ActionEvent e) {
487                                 PreferencesDialog.showPreferences();
488                         }
489                 });
490                 menu.add(item);
491         
492                 
493
494
495                 ////  Analyze
496                 menu = new JMenu("Analyze");
497                 menu.setMnemonic(KeyEvent.VK_A);
498                 menu.getAccessibleContext().setAccessibleDescription("Analyzing the rocket");
499                 menubar.add(menu);
500                 
501                 item = new JMenuItem("Component analysis",KeyEvent.VK_C);
502                 item.getAccessibleContext().setAccessibleDescription("Analyze the rocket components " +
503                                 "separately");
504                 item.addActionListener(new ActionListener() {
505                         public void actionPerformed(ActionEvent e) {
506                                 ComponentAnalysisDialog.showDialog(rocketpanel);
507                         }
508                 });
509                 menu.add(item);
510                 
511                 
512                 
513                 ////  Help
514                 
515                 menu = new JMenu("Help");
516                 menu.setMnemonic(KeyEvent.VK_H);
517                 menu.getAccessibleContext().setAccessibleDescription("Information about OpenRocket");
518                 menubar.add(menu);
519                 
520                 
521                 
522                 item = new JMenuItem("License",KeyEvent.VK_L);
523                 item.getAccessibleContext().setAccessibleDescription("OpenRocket license information");
524                 item.addActionListener(new ActionListener() {
525                         public void actionPerformed(ActionEvent e) {
526                                 new LicenseDialog(BasicFrame.this).setVisible(true);
527                         }
528                 });
529                 menu.add(item);
530                 
531                 item = new JMenuItem("Bug report",KeyEvent.VK_B);
532                 item.getAccessibleContext().setAccessibleDescription("Information about reporting " +
533                                 "bugs in OpenRocket");
534                 item.addActionListener(new ActionListener() {
535                         public void actionPerformed(ActionEvent e) {
536                                 new BugDialog(BasicFrame.this).setVisible(true);
537                         }
538                 });
539                 menu.add(item);
540                 
541                 item = new JMenuItem("About",KeyEvent.VK_A);
542                 item.getAccessibleContext().setAccessibleDescription("About OpenRocket");
543                 item.addActionListener(new ActionListener() {
544                         public void actionPerformed(ActionEvent e) {
545                                 new AboutDialog(BasicFrame.this).setVisible(true);
546                         }
547                 });
548                 menu.add(item);
549                 
550                 
551                 this.setJMenuBar(menubar);
552         }
553         
554         
555         
556         
557         private void openAction() {
558             JFileChooser chooser = new JFileChooser();
559             chooser.setFileFilter(ROCKET_DESIGN_FILTER);
560             chooser.setMultiSelectionEnabled(true);
561             chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
562             if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION)
563                 return;
564             
565             Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
566
567             File[] files = chooser.getSelectedFiles();
568             boolean opened = false;
569             
570             for (File file: files) {
571                 System.out.println("Opening file: " + file);
572                 if (open(file)) {
573                         opened = true;
574                 }
575             }
576
577             // Close this frame if replaceable and file opened successfully
578                 if (replaceable && opened) {
579                         closeAction();
580                 }
581         }
582         
583         
584         /**
585          * Open the specified file in a new design frame.  If an error occurs, an error dialog
586          * is shown and <code>false</code> is returned.
587          * 
588          * @param file  the file to open.
589          * @return              whether the file was successfully loaded and opened.
590          */
591         private static boolean open(File file) {
592             OpenRocketDocument doc = null;
593                 try {
594                         doc = ROCKET_LOADER.load(file);
595                 } catch (RocketLoadException e) {
596                         JOptionPane.showMessageDialog(null, "Unable to open file '" + file.getName() 
597                                         +"': " + e.getMessage(), "Error opening file", JOptionPane.ERROR_MESSAGE);
598                         e.printStackTrace();
599                         return false;
600                 }
601                 
602             if (doc == null) {
603                 throw new RuntimeException("BUG: Rocket loader returned null");
604             }       
605             
606             // Show warnings
607             Iterator<Warning> warns = ROCKET_LOADER.getWarnings().iterator();
608             System.out.println("Warnings:");
609             while (warns.hasNext()) {
610                 System.out.println("  "+warns.next());
611                 // TODO: HIGH: dialog
612             }
613             
614             // Set document state
615             doc.setFile(file);
616             doc.setSaved(true);
617
618             // Open the frame
619             BasicFrame frame = new BasicFrame(doc);
620             frame.setVisible(true);
621
622             return true;
623         }
624         
625         
626         
627         private boolean saveAction() {
628                 File file = document.getFile();
629                 if (file==null) {
630                         return saveAsAction();
631                 } else {
632                         return saveAs(file);
633                 }
634         }
635         
636         private boolean saveAsAction() {
637                 File file = null;
638                 while (file == null) {
639                         StorageOptionChooser storageChooser = 
640                                 new StorageOptionChooser(document.getDefaultStorageOptions());
641                         JFileChooser chooser = new JFileChooser();
642                         chooser.setFileFilter(ROCKET_DESIGN_FILTER);
643                         chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
644                         chooser.setAccessory(storageChooser);
645                         if (document.getFile() != null)
646                                 chooser.setSelectedFile(document.getFile());
647                         
648                         if (chooser.showSaveDialog(BasicFrame.this) != JFileChooser.APPROVE_OPTION)
649                                 return false;
650                         
651                         file = chooser.getSelectedFile();
652                         if (file == null)
653                                 return false;
654
655                         Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
656                         storageChooser.storeOptions(document.getDefaultStorageOptions());
657                         
658                         if (file.getName().indexOf('.') < 0) {
659                                 String name = file.getAbsolutePath();
660                                 name = name + ".ork";
661                                 file = new File(name);
662                         }
663                         
664                         if (file.exists()) {
665                                 int result = JOptionPane.showConfirmDialog(this, 
666                                                 "File '"+file.getName()+"' exists.  Do you want to overwrite it?", 
667                                                 "File exists", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
668                                 if (result != JOptionPane.YES_OPTION)
669                                         return false;
670                         }
671                 }
672             saveAs(file);
673             return true;
674         }
675         
676         
677         private boolean saveAs(File file) {
678             System.out.println("Saving to file: " + file.getName());
679             boolean saved = false;
680             
681             if (!StorageOptionChooser.verifyStorageOptions(document, this)) {
682                 // User cancelled the dialog
683                 return false;
684             }
685
686             RocketSaver saver = new OpenRocketSaver();
687             try {
688                 saver.save(file, document);
689                 document.setFile(file);
690                 document.setSaved(true);
691                 saved = true;
692             } catch (IOException e) {
693                 JOptionPane.showMessageDialog(this, new String[] { 
694                                 "An I/O error occurred while saving:",
695                                 e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
696             }
697             setTitle();
698             return saved;
699         }
700         
701         
702         private boolean closeAction() {
703                 if (!document.isSaved()) {
704                         ComponentConfigDialog.hideDialog();
705                         int result = JOptionPane.showConfirmDialog(this, 
706                                         "Design '"+rocket.getName()+"' has not been saved.  " +
707                                                         "Do you want to save it?", 
708                                         "Design not saved", JOptionPane.YES_NO_CANCEL_OPTION, 
709                                         JOptionPane.QUESTION_MESSAGE);
710                         if (result == JOptionPane.YES_OPTION) {
711                                 // Save
712                                 if (!saveAction())
713                                         return false;  // If save was interrupted
714                         } else if (result == JOptionPane.NO_OPTION) {
715                                 // Don't save: No-op
716                         } else {
717                                 // Cancel or close
718                                 return false;
719                         }
720                 }
721                 
722                 // Rocket has been saved or discarded
723                 this.dispose();
724
725                 // TODO: LOW: Close only dialogs that have this frame as their parent
726                 ComponentConfigDialog.hideDialog();
727                 ComponentAnalysisDialog.hideDialog();
728                 
729                 frames.remove(this);
730                 if (frames.isEmpty())
731                         System.exit(0);
732                 return true;
733         }
734         
735         /**
736          * Open a new design window with a basic rocket+stage.
737          */
738         public static void newAction() {
739                 Rocket rocket = new Rocket();
740                 Stage stage = new Stage();
741                 stage.setName("Sustainer");
742                 rocket.addChild(stage);
743                 OpenRocketDocument doc = new OpenRocketDocument(rocket);
744                 doc.setSaved(true);
745                 
746                 BasicFrame frame = new BasicFrame(doc);
747                 frame.replaceable = true;
748                 frame.setVisible(true);
749                 ComponentConfigDialog.showDialog(frame, doc, rocket);
750         }
751         
752         /**
753          * Quit the application.  Confirms saving unsaved designs.  The action of File->Quit.
754          */
755         public static void quitAction() {
756                 for (int i=frames.size()-1; i>=0; i--) {
757                         if (!frames.get(i).closeAction()) {
758                                 // Close canceled
759                                 return;
760                         }
761                 }
762                 // Should not be reached, but just in case
763                 System.exit(0);
764         }
765         
766         
767         /**
768          * Set the title of the frame, taking into account the name of the rocket, file it 
769          * has been saved to (if any) and saved status.
770          */
771         private void setTitle() {
772                 File file = document.getFile();
773                 boolean saved = document.isSaved();
774                 String title;
775                 
776                 title = rocket.getName();
777                 if (file!=null) {
778                         title = title + " ("+file.getName()+")";
779                 }
780                 if (!saved)
781                         title = "*" + title;
782                 
783                 setTitle(title);
784         }
785         
786         
787         
788         
789         
790         
791         public static void main(String[] args) {
792                 
793                 /*
794                  * Set the look-and-feel.  On Linux, Motif/Metal is sometimes incorrectly used 
795                  * which is butt-ugly, so if the system l&f is Motif/Metal, we search for a few
796                  * other alternatives.
797                  */
798                 try {
799                         UIManager.LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels();
800 //                      System.out.println("Available look-and-feels:");
801 //                      for (int i=0; i<info.length; i++) {
802 //                              System.out.println("  "+info[i]);
803 //                      }
804
805                         // Set system L&F
806                         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
807                         
808                         // Check whether we have an ugly L&F
809                         LookAndFeel laf = UIManager.getLookAndFeel();
810                         if (laf == null ||
811                                         laf.getName().matches(".*[mM][oO][tT][iI][fF].*") ||
812                                         laf.getName().matches(".*[mM][eE][tT][aA][lL].*")) {
813                                 
814                                 // Search for better LAF
815                                 for (UIManager.LookAndFeelInfo l: info) {
816                                         if (l.getName().matches(".*[gG][tT][kK].*")) {
817                                                 UIManager.setLookAndFeel(l.getClassName());
818                                                 break;
819                                         }
820                                         if (l.getName().contains(".*[wW][iI][nN].*")) {
821                                                 UIManager.setLookAndFeel(l.getClassName());
822                                                 break;
823                                         }
824                                         if (l.getName().contains(".*[mM][aA][cC].*")) {
825                                                 UIManager.setLookAndFeel(l.getClassName());
826                                                 break;
827                                         }
828                                 }
829                         }
830                 } catch (Exception e) {
831                         System.err.println("Error setting LAF: " + e);
832                 }
833
834                 // Set tooltip delay time.  Tooltips are used in MotorChooserDialog extensively.
835                 ToolTipManager.sharedInstance().setDismissDelay(30000);
836                 
837                 
838                 // Load defaults
839                 Prefs.loadDefaultUnits();
840
841                 
842                 // Check command-line for files
843                 boolean opened = false;
844                 for (String file: args) {
845                         if (open(new File(file))) {
846                                 opened = true;
847                         }
848                 }
849                 
850                 if (!opened) {
851                         newAction();
852                 }
853         }
854
855 }