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