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