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