DGP - 1st printing
[debian/openrocket] / src / net / sf / openrocket / gui / main / BasicFrame.java
1 package net.sf.openrocket.gui.main;
2
3 import net.miginfocom.swing.MigLayout;
4 import net.sf.openrocket.aerodynamics.WarningSet;
5 import net.sf.openrocket.communication.UpdateInfo;
6 import net.sf.openrocket.communication.UpdateInfoRetriever;
7 import net.sf.openrocket.database.Databases;
8 import net.sf.openrocket.document.OpenRocketDocument;
9 import net.sf.openrocket.file.GeneralRocketLoader;
10 import net.sf.openrocket.file.RocketLoadException;
11 import net.sf.openrocket.file.RocketLoader;
12 import net.sf.openrocket.file.RocketSaver;
13 import net.sf.openrocket.file.openrocket.OpenRocketSaver;
14 import net.sf.openrocket.gui.StorageOptionChooser;
15 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
16 import net.sf.openrocket.gui.dialogs.AboutDialog;
17 import net.sf.openrocket.gui.dialogs.BugReportDialog;
18 import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
19 import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
20 import net.sf.openrocket.gui.dialogs.LicenseDialog;
21 import net.sf.openrocket.gui.dialogs.MotorDatabaseLoadingDialog;
22 import net.sf.openrocket.gui.dialogs.PrintDialog;
23 import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
24 import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
25 import net.sf.openrocket.gui.dialogs.WarningDialog;
26 import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
27 import net.sf.openrocket.gui.scalefigure.RocketPanel;
28 import net.sf.openrocket.logging.LogHelper;
29 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
30 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
31 import net.sf.openrocket.rocketcomponent.Rocket;
32 import net.sf.openrocket.rocketcomponent.RocketComponent;
33 import net.sf.openrocket.rocketcomponent.Stage;
34 import net.sf.openrocket.startup.Application;
35 import net.sf.openrocket.util.BugException;
36 import net.sf.openrocket.util.GUIUtil;
37 import net.sf.openrocket.util.Icons;
38 import net.sf.openrocket.util.OpenFileWorker;
39 import net.sf.openrocket.util.Prefs;
40 import net.sf.openrocket.util.Reflection;
41 import net.sf.openrocket.util.SaveFileWorker;
42 import net.sf.openrocket.util.TestRockets;
43
44 import javax.swing.Action;
45 import javax.swing.InputMap;
46 import javax.swing.JButton;
47 import javax.swing.JComponent;
48 import javax.swing.JFileChooser;
49 import javax.swing.JFrame;
50 import javax.swing.JMenu;
51 import javax.swing.JMenuBar;
52 import javax.swing.JMenuItem;
53 import javax.swing.JOptionPane;
54 import javax.swing.JPanel;
55 import javax.swing.JScrollPane;
56 import javax.swing.JSeparator;
57 import javax.swing.JSplitPane;
58 import javax.swing.JTabbedPane;
59 import javax.swing.JTextField;
60 import javax.swing.KeyStroke;
61 import javax.swing.ListSelectionModel;
62 import javax.swing.ScrollPaneConstants;
63 import javax.swing.SwingUtilities;
64 import javax.swing.Timer;
65 import javax.swing.ToolTipManager;
66 import javax.swing.border.TitledBorder;
67 import javax.swing.event.TreeSelectionEvent;
68 import javax.swing.event.TreeSelectionListener;
69 import javax.swing.filechooser.FileFilter;
70 import javax.swing.tree.DefaultTreeSelectionModel;
71 import javax.swing.tree.TreePath;
72 import javax.swing.tree.TreeSelectionModel;
73 import java.awt.Dimension;
74 import java.awt.Font;
75 import java.awt.Toolkit;
76 import java.awt.Window;
77 import java.awt.event.ActionEvent;
78 import java.awt.event.ActionListener;
79 import java.awt.event.ComponentAdapter;
80 import java.awt.event.ComponentEvent;
81 import java.awt.event.KeyEvent;
82 import java.awt.event.MouseAdapter;
83 import java.awt.event.MouseEvent;
84 import java.awt.event.MouseListener;
85 import java.awt.event.WindowAdapter;
86 import java.awt.event.WindowEvent;
87 import java.io.File;
88 import java.io.FileNotFoundException;
89 import java.io.IOException;
90 import java.io.InputStream;
91 import java.io.UnsupportedEncodingException;
92 import java.lang.reflect.InvocationTargetException;
93 import java.net.URI;
94 import java.net.URISyntaxException;
95 import java.net.URL;
96 import java.net.URLDecoder;
97 import java.util.ArrayList;
98 import java.util.concurrent.ExecutionException;
99
100 public class BasicFrame extends JFrame {
101         private static final LogHelper log = Application.getLogger();
102         
103         /**
104          * The RocketLoader instance used for loading all rocket designs.
105          */
106         private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader();
107         
108         private static final RocketSaver ROCKET_SAVER = new OpenRocketSaver();
109         
110
111         // FileFilters for different types of rocket design files
112         private static final FileFilter ALL_DESIGNS_FILTER =
113                         new SimpleFileFilter("All rocket designs (*.ork; *.rkt)",
114                                         ".ork", ".ork.gz", ".rkt", ".rkt.gz");
115         
116         private static final FileFilter OPENROCKET_DESIGN_FILTER =
117                         new SimpleFileFilter("OpenRocket designs (*.ork)", ".ork", ".ork.gz");
118         
119         private static final FileFilter ROCKSIM_DESIGN_FILTER =
120                         new SimpleFileFilter("RockSim designs (*.rkt)", ".rkt", ".rkt.gz");
121         
122
123
124
125         public static final int COMPONENT_TAB = 0;
126         public static final int SIMULATION_TAB = 1;
127         
128
129         /**
130          * List of currently open frames.  When the list goes empty
131          * it is time to exit the application.
132          */
133         private static final ArrayList<BasicFrame> frames = new ArrayList<BasicFrame>();
134         
135
136
137
138
139         /**
140          * Whether "New" and "Open" should replace this frame.
141          * Should be set to false on the first rocket modification.
142          */
143         private boolean replaceable = false;
144         
145
146
147         private final OpenRocketDocument document;
148         private final Rocket rocket;
149         
150         private JTabbedPane tabbedPane;
151         private RocketPanel rocketpanel;
152         private ComponentTree tree = null;
153         
154         private final DocumentSelectionModel selectionModel;
155         private final TreeSelectionModel componentSelectionModel;
156         private final ListSelectionModel simulationSelectionModel;
157         
158         /** Actions available for rocket modifications */
159         private final RocketActions actions;
160         
161         
162
163         /**
164          * Sole constructor.  Creates a new frame based on the supplied document
165          * and adds it to the current frames list.
166          * 
167          * @param document      the document to show.
168          */
169         public BasicFrame(OpenRocketDocument document) {
170                 log.debug("Instantiating new BasicFrame");
171                 
172                 this.document = document;
173                 this.rocket = document.getRocket();
174                 this.rocket.getDefaultConfiguration().setAllStages();
175                 
176
177                 // Set replaceable flag to false at first modification
178                 rocket.addComponentChangeListener(new ComponentChangeListener() {
179                         public void componentChanged(ComponentChangeEvent e) {
180                                 replaceable = false;
181                                 BasicFrame.this.rocket.removeComponentChangeListener(this);
182                         }
183                 });
184                 
185
186                 // Create the component tree selection model that will be used
187                 componentSelectionModel = new DefaultTreeSelectionModel();
188                 componentSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
189                 
190                 // Obtain the simulation selection model that will be used
191                 SimulationPanel simulationPanel = new SimulationPanel(document);
192                 simulationSelectionModel = simulationPanel.getSimulationListSelectionModel();
193                 
194                 // Combine into a DocumentSelectionModel
195                 selectionModel = new DocumentSelectionModel(document);
196                 selectionModel.attachComponentTreeSelectionModel(componentSelectionModel);
197                 selectionModel.attachSimulationListSelectionModel(simulationSelectionModel);
198                 
199
200                 actions = new RocketActions(document, selectionModel, this);
201                 
202
203                 log.debug("Constructing the BasicFrame UI");
204                 
205                 // The main vertical split pane         
206                 JSplitPane vertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
207                 vertical.setResizeWeight(0.5);
208                 this.add(vertical);
209                 
210
211                 // The top tabbed pane
212                 tabbedPane = new JTabbedPane();
213                 tabbedPane.addTab("Rocket design", null, designTab());
214                 tabbedPane.addTab("Flight simulations", null, simulationPanel);
215                 
216                 vertical.setTopComponent(tabbedPane);
217                 
218
219
220                 //  Bottom segment, rocket figure
221                 
222                 rocketpanel = new RocketPanel(document);
223                 vertical.setBottomComponent(rocketpanel);
224                 
225                 rocketpanel.setSelectionModel(tree.getSelectionModel());
226                 
227
228                 createMenu();
229                 
230
231                 rocket.addComponentChangeListener(new ComponentChangeListener() {
232                         public void componentChanged(ComponentChangeEvent e) {
233                                 setTitle();
234                         }
235                 });
236                 
237                 setTitle();
238                 this.pack();
239                 
240                 Dimension size = Prefs.getWindowSize(this.getClass());
241                 if (size == null) {
242                         size = Toolkit.getDefaultToolkit().getScreenSize();
243                         size.width = size.width * 9 / 10;
244                         size.height = size.height * 9 / 10;
245                 }
246                 this.setSize(size);
247                 this.addComponentListener(new ComponentAdapter() {
248                         @Override
249                         public void componentResized(ComponentEvent e) {
250                                 Prefs.setWindowSize(BasicFrame.this.getClass(), BasicFrame.this.getSize());
251                         }
252                 });
253                 this.setLocationByPlatform(true);
254                 
255                 GUIUtil.setWindowIcons(this);
256                 
257                 this.validate();
258                 vertical.setDividerLocation(0.4);
259                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
260                 addWindowListener(new WindowAdapter() {
261                         @Override
262                         public void windowClosing(WindowEvent e) {
263                                 closeAction();
264                         }
265                 });
266                 frames.add(this);
267                 
268                 log.debug("BasicFrame instantiation complete");
269         }
270         
271         
272         /**
273          * Construct the "Rocket design" tab.  This contains a horizontal split pane
274          * with the left component the design tree and the right component buttons
275          * for adding components.
276          */
277         private JComponent designTab() {
278                 JSplitPane horizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
279                 horizontal.setResizeWeight(0.5);
280                 
281
282                 //  Upper-left segment, component tree
283                 
284                 JPanel panel = new JPanel(new MigLayout("fill, flowy", "", "[grow]"));
285                 
286                 tree = new ComponentTree(rocket);
287                 tree.setSelectionModel(componentSelectionModel);
288                 
289                 // Remove JTree key events that interfere with menu accelerators
290                 InputMap im = SwingUtilities.getUIInputMap(tree, JComponent.WHEN_FOCUSED);
291                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK), null);
292                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK), null);
293                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK), null);
294                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.CTRL_MASK), null);
295                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK), null);
296                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK), null);
297                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK), null);
298                 
299
300
301                 // Double-click opens config dialog
302                 MouseListener ml = new MouseAdapter() {
303                         @Override
304                         public void mousePressed(MouseEvent e) {
305                                 int selRow = tree.getRowForLocation(e.getX(), e.getY());
306                                 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
307                                 if (selRow != -1) {
308                                         if ((e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) {
309                                                 // Double-click
310                                                 RocketComponent c = (RocketComponent) selPath.getLastPathComponent();
311                                                 ComponentConfigDialog.showDialog(BasicFrame.this,
312                                                                 BasicFrame.this.document, c);
313                                         }
314                                 }
315                         }
316                 };
317                 tree.addMouseListener(ml);
318                 
319                 // Update dialog when selection is changed
320                 componentSelectionModel.addTreeSelectionListener(new TreeSelectionListener() {
321                         public void valueChanged(TreeSelectionEvent e) {
322                                 // Scroll tree to the selected item
323                                 TreePath path = componentSelectionModel.getSelectionPath();
324                                 if (path == null)
325                                         return;
326                                 tree.scrollPathToVisible(path);
327                                 
328                                 if (!ComponentConfigDialog.isDialogVisible())
329                                         return;
330                                 RocketComponent c = (RocketComponent) path.getLastPathComponent();
331                                 ComponentConfigDialog.showDialog(BasicFrame.this,
332                                                 BasicFrame.this.document, c);
333                         }
334                 });
335                 
336                 // Place tree inside scroll pane
337                 JScrollPane scroll = new JScrollPane(tree);
338                 panel.add(scroll, "spany, grow, wrap");
339                 
340
341                 // Buttons
342                 JButton button = new JButton(actions.getMoveUpAction());
343                 panel.add(button, "sizegroup buttons, aligny 65%");
344                 
345                 button = new JButton(actions.getMoveDownAction());
346                 panel.add(button, "sizegroup buttons, aligny 0%");
347                 
348                 button = new JButton(actions.getEditAction());
349                 panel.add(button, "sizegroup buttons");
350                 
351                 button = new JButton(actions.getNewStageAction());
352                 panel.add(button, "sizegroup buttons");
353                 
354                 button = new JButton(actions.getDeleteAction());
355                 button.setIcon(null);
356                 button.setMnemonic(0);
357                 panel.add(button, "sizegroup buttons");
358                 
359                 horizontal.setLeftComponent(panel);
360                 
361
362                 //  Upper-right segment, component addition buttons
363                 
364                 panel = new JPanel(new MigLayout("fill, insets 0", "[0::]"));
365                 
366                 scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
367                                 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
368                 scroll.setViewportView(new ComponentAddButtons(document, componentSelectionModel,
369                                 scroll.getViewport()));
370                 scroll.setBorder(null);
371                 scroll.setViewportBorder(null);
372                 
373                 TitledBorder border = new TitledBorder("Add new component");
374                 border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD));
375                 scroll.setBorder(border);
376                 
377                 panel.add(scroll, "grow");
378                 
379                 horizontal.setRightComponent(panel);
380                 
381                 return horizontal;
382         }
383         
384         
385
386         /**
387          * Creates the menu for the window.
388          */
389         private void createMenu() {
390                 JMenuBar menubar = new JMenuBar();
391                 JMenu menu;
392                 JMenuItem item;
393                 
394                 ////  File
395                 menu = new JMenu("File");
396                 menu.setMnemonic(KeyEvent.VK_F);
397                 menu.getAccessibleContext().setAccessibleDescription("File-handling related tasks");
398                 menubar.add(menu);
399                 
400                 item = new JMenuItem("New", KeyEvent.VK_N);
401                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
402                 item.setMnemonic(KeyEvent.VK_N);
403                 item.getAccessibleContext().setAccessibleDescription("Create a new rocket design");
404                 item.setIcon(Icons.FILE_NEW);
405                 item.addActionListener(new ActionListener() {
406                         public void actionPerformed(ActionEvent e) {
407                                 newAction();
408                                 if (replaceable)
409                                         closeAction();
410                         }
411                 });
412                 menu.add(item);
413                 
414                 item = new JMenuItem("Open...", KeyEvent.VK_O);
415                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
416                 item.getAccessibleContext().setAccessibleDescription("Open a rocket design");
417                 item.setIcon(Icons.FILE_OPEN);
418                 item.addActionListener(new ActionListener() {
419                         public void actionPerformed(ActionEvent e) {
420                                 openAction();
421                         }
422                 });
423                 menu.add(item);
424                 
425                 item = new JMenuItem("Open example...");
426                 item.getAccessibleContext().setAccessibleDescription("Open an example rocket design");
427                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
428                                 ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
429                 item.setIcon(Icons.FILE_OPEN_EXAMPLE);
430                 item.addActionListener(new ActionListener() {
431                         public void actionPerformed(ActionEvent e) {
432                                 URL[] urls = ExampleDesignDialog.selectExampleDesigns(BasicFrame.this);
433                                 if (urls != null) {
434                                         for (URL u : urls) {
435                                                 open(u, BasicFrame.this);
436                                         }
437                                 }
438                         }
439                 });
440                 menu.add(item);
441                 
442                 menu.addSeparator();
443                 
444                 item = new JMenuItem("Save", KeyEvent.VK_S);
445                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
446                 item.getAccessibleContext().setAccessibleDescription("Save the current rocket design");
447                 item.setIcon(Icons.FILE_SAVE);
448                 item.addActionListener(new ActionListener() {
449                         public void actionPerformed(ActionEvent e) {
450                                 saveAction();
451                         }
452                 });
453                 menu.add(item);
454                 
455                 item = new JMenuItem("Save as...", KeyEvent.VK_A);
456                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
457                                 ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
458                 item.getAccessibleContext().setAccessibleDescription("Save the current rocket design " +
459                                 "to a new file");
460                 item.setIcon(Icons.FILE_SAVE_AS);
461                 item.addActionListener(new ActionListener() {
462                         public void actionPerformed(ActionEvent e) {
463                                 saveAsAction();
464                         }
465                 });
466                 menu.add(item);
467                 
468                 //              menu.addSeparator();
469                 menu.add(new JSeparator());
470                 
471                 item = new JMenuItem("Close", KeyEvent.VK_C);
472                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK));
473                 item.getAccessibleContext().setAccessibleDescription("Close the current rocket design");
474                 item.setIcon(Icons.FILE_CLOSE);
475                 item.addActionListener(new ActionListener() {
476                         public void actionPerformed(ActionEvent e) {
477                                 closeAction();
478                         }
479                 });
480                 menu.add(item);
481                 
482                 menu.addSeparator();
483
484
485         item = new JMenuItem("Print...", KeyEvent.VK_P);
486         item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
487         item.getAccessibleContext().setAccessibleDescription("Print parts list and fin template");
488         item.addActionListener(new ActionListener() {
489             public void actionPerformed(ActionEvent e) {
490                 printAction();
491             }
492         });
493         menu.add(item);
494         
495                 item = new JMenuItem("Quit", KeyEvent.VK_Q);
496                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
497                 item.getAccessibleContext().setAccessibleDescription("Quit the program");
498                 item.setIcon(Icons.FILE_QUIT);
499                 item.addActionListener(new ActionListener() {
500                         public void actionPerformed(ActionEvent e) {
501                                 quitAction();
502                         }
503                 });
504                 menu.add(item);
505                 
506
507
508                 ////  Edit
509                 menu = new JMenu("Edit");
510                 menu.setMnemonic(KeyEvent.VK_E);
511                 menu.getAccessibleContext().setAccessibleDescription("Rocket editing");
512                 menubar.add(menu);
513                 
514
515                 Action action = document.getUndoAction();
516                 item = new JMenuItem(action);
517                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
518                 item.setMnemonic(KeyEvent.VK_U);
519                 item.getAccessibleContext().setAccessibleDescription("Undo the previous operation");
520                 
521                 menu.add(item);
522                 
523                 action = document.getRedoAction();
524                 item = new JMenuItem(action);
525                 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
526                 item.setMnemonic(KeyEvent.VK_R);
527                 item.getAccessibleContext().setAccessibleDescription("Redo the previously undone " +
528                                 "operation");
529                 menu.add(item);
530                 
531                 menu.addSeparator();
532                 
533
534                 item = new JMenuItem(actions.getCutAction());
535                 menu.add(item);
536                 
537                 item = new JMenuItem(actions.getCopyAction());
538                 menu.add(item);
539                 
540                 item = new JMenuItem(actions.getPasteAction());
541                 menu.add(item);
542                 
543                 item = new JMenuItem(actions.getDeleteAction());
544                 menu.add(item);
545                 
546                 menu.addSeparator();
547                 
548                 item = new JMenuItem("Preferences");
549                 item.setIcon(Icons.PREFERENCES);
550                 item.getAccessibleContext().setAccessibleDescription("Setup the application " +
551                                 "preferences");
552                 item.addActionListener(new ActionListener() {
553                         public void actionPerformed(ActionEvent e) {
554                                 PreferencesDialog.showPreferences();
555                         }
556                 });
557                 menu.add(item);
558                 
559
560
561
562                 ////  Analyze
563                 menu = new JMenu("Analyze");
564                 menu.setMnemonic(KeyEvent.VK_A);
565                 menu.getAccessibleContext().setAccessibleDescription("Analyzing the rocket");
566                 menubar.add(menu);
567                 
568                 item = new JMenuItem("Component analysis", KeyEvent.VK_C);
569                 item.getAccessibleContext().setAccessibleDescription("Analyze the rocket components " +
570                                 "separately");
571                 item.addActionListener(new ActionListener() {
572                         public void actionPerformed(ActionEvent e) {
573                                 ComponentAnalysisDialog.showDialog(rocketpanel);
574                         }
575                 });
576                 menu.add(item);
577                 
578
579                 ////  Debug
580                 // (shown if openrocket.debug.menu is defined)
581                 if (System.getProperty("openrocket.debug.menu") != null) {
582                         menubar.add(makeDebugMenu());
583                 }
584                 
585
586
587                 ////  Help
588                 
589                 menu = new JMenu("Help");
590                 menu.setMnemonic(KeyEvent.VK_H);
591                 menu.getAccessibleContext().setAccessibleDescription("Information about OpenRocket");
592                 menubar.add(menu);
593                 
594
595
596                 item = new JMenuItem("License", KeyEvent.VK_L);
597                 item.getAccessibleContext().setAccessibleDescription("OpenRocket license information");
598                 item.addActionListener(new ActionListener() {
599                         public void actionPerformed(ActionEvent e) {
600                                 new LicenseDialog(BasicFrame.this).setVisible(true);
601                         }
602                 });
603                 menu.add(item);
604                 
605                 item = new JMenuItem("Bug report", KeyEvent.VK_B);
606                 item.getAccessibleContext().setAccessibleDescription("Information about reporting " +
607                                 "bugs in OpenRocket");
608                 item.addActionListener(new ActionListener() {
609                         public void actionPerformed(ActionEvent e) {
610                                 //                              new BugDialog(BasicFrame.this).setVisible(true);
611                                 BugReportDialog.showBugReportDialog(BasicFrame.this);
612                         }
613                 });
614                 menu.add(item);
615                 
616                 item = new JMenuItem("About", KeyEvent.VK_A);
617                 item.getAccessibleContext().setAccessibleDescription("About OpenRocket");
618                 item.addActionListener(new ActionListener() {
619                         public void actionPerformed(ActionEvent e) {
620                                 new AboutDialog(BasicFrame.this).setVisible(true);
621                         }
622                 });
623                 menu.add(item);
624                 
625
626                 this.setJMenuBar(menubar);
627         }
628         
629         
630         private JMenu makeDebugMenu() {
631                 JMenu menu;
632                 JMenuItem item;
633                 
634                 ////  Debug menu
635                 menu = new JMenu("Debug");
636                 menu.getAccessibleContext().setAccessibleDescription("OpenRocket debugging tasks");
637                 
638                 item = new JMenuItem("What is this menu?");
639                 item.addActionListener(new ActionListener() {
640                         public void actionPerformed(ActionEvent e) {
641                                 JOptionPane.showMessageDialog(BasicFrame.this,
642                                                 new Object[] {
643                                                                 "The 'Debug' menu includes actions for testing and debugging " +
644                                                                                 "OpenRocket.", " ",
645                                                                 "The menu is made visible by defining the system property " +
646                                                                                 "'openrocket.debug.menu' when starting OpenRocket.",
647                                                                 "It should not be visible by default." },
648                                                 "Debug menu", JOptionPane.INFORMATION_MESSAGE);
649                         }
650                 });
651                 menu.add(item);
652                 
653                 menu.addSeparator();
654                 
655                 item = new JMenuItem("Create test rocket");
656                 item.addActionListener(new ActionListener() {
657                         @Override
658                         public void actionPerformed(ActionEvent e) {
659                                 JTextField field = new JTextField();
660                                 int sel = JOptionPane.showOptionDialog(BasicFrame.this, new Object[] {
661                                                 "Input text key to generate random rocket:",
662                                                 field
663                                         }, "Generate random test rocket", JOptionPane.DEFAULT_OPTION,
664                                                 JOptionPane.QUESTION_MESSAGE, null, new Object[] {
665                                                                 "Random", "OK"
666                                 }, "OK");
667                                 
668                                 Rocket r;
669                                 if (sel == 0) {
670                                         r = new TestRockets(null).makeTestRocket();
671                                 } else if (sel == 1) {
672                                         r = new TestRockets(field.getText()).makeTestRocket();
673                                 } else {
674                                         return;
675                                 }
676                                 
677                                 OpenRocketDocument doc = new OpenRocketDocument(r);
678                                 doc.setSaved(true);
679                                 BasicFrame frame = new BasicFrame(doc);
680                                 frame.setVisible(true);
681                         }
682                 });
683                 menu.add(item);
684                 
685
686
687                 item = new JMenuItem("Create 'Iso-Haisu'");
688                 item.addActionListener(new ActionListener() {
689                         @Override
690                         public void actionPerformed(ActionEvent e) {
691                                 Rocket r = TestRockets.makeIsoHaisu();
692                                 OpenRocketDocument doc = new OpenRocketDocument(r);
693                                 doc.setSaved(true);
694                                 BasicFrame frame = new BasicFrame(doc);
695                                 frame.setVisible(true);
696                         }
697                 });
698                 menu.add(item);
699                 
700
701                 item = new JMenuItem("Create 'Big Blue'");
702                 item.addActionListener(new ActionListener() {
703                         @Override
704                         public void actionPerformed(ActionEvent e) {
705                                 Rocket r = TestRockets.makeBigBlue();
706                                 OpenRocketDocument doc = new OpenRocketDocument(r);
707                                 doc.setSaved(true);
708                                 BasicFrame frame = new BasicFrame(doc);
709                                 frame.setVisible(true);
710                         }
711                 });
712                 menu.add(item);
713                 
714
715
716                 menu.addSeparator();
717                 
718                 item = new JMenuItem("Exception here");
719                 item.addActionListener(new ActionListener() {
720                         public void actionPerformed(ActionEvent e) {
721                                 throw new RuntimeException("Testing exception from menu action listener");
722                         }
723                 });
724                 menu.add(item);
725                 
726                 item = new JMenuItem("Exception from EDT");
727                 item.addActionListener(new ActionListener() {
728                         public void actionPerformed(ActionEvent e) {
729                                 SwingUtilities.invokeLater(new Runnable() {
730                                         @Override
731                                         public void run() {
732                                                 throw new RuntimeException("Testing exception from " +
733                                                                 "later invoked EDT thread");
734                                         }
735                                 });
736                         }
737                 });
738                 menu.add(item);
739                 
740                 item = new JMenuItem("Exception from other thread");
741                 item.addActionListener(new ActionListener() {
742                         public void actionPerformed(ActionEvent e) {
743                                 new Thread() {
744                                         @Override
745                                         public void run() {
746                                                 throw new RuntimeException("Testing exception from " +
747                                                                 "newly created thread");
748                                         }
749                                 }.start();
750                         }
751                 });
752                 menu.add(item);
753                 
754
755
756                 return menu;
757         }
758         
759         
760
761         /**
762          * Select the tab on the main pane.
763          * 
764          * @param tab   one of {@link #COMPONENT_TAB} or {@link #SIMULATION_TAB}.
765          */
766         public void selectTab(int tab) {
767                 tabbedPane.setSelectedIndex(tab);
768         }
769         
770         
771
772         private void openAction() {
773                 JFileChooser chooser = new JFileChooser();
774                 
775                 chooser.addChoosableFileFilter(ALL_DESIGNS_FILTER);
776                 chooser.addChoosableFileFilter(OPENROCKET_DESIGN_FILTER);
777                 chooser.addChoosableFileFilter(ROCKSIM_DESIGN_FILTER);
778                 chooser.setFileFilter(ALL_DESIGNS_FILTER);
779                 
780                 chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
781                 chooser.setMultiSelectionEnabled(true);
782                 chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
783                 if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION)
784                         return;
785                 
786                 Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
787                 
788                 File[] files = chooser.getSelectedFiles();
789                 
790                 for (File file : files) {
791                         System.out.println("Opening file: " + file);
792                         if (open(file, this)) {
793                                 
794                                 // Close previous window if replacing
795                                 if (replaceable && document.isSaved()) {
796                                         closeAction();
797                                         replaceable = false;
798                                 }
799                         }
800                 }
801         }
802         
803         
804
805         private static boolean open(URL url, BasicFrame parent) {
806                 String filename = null;
807                 
808                 // Try using URI.getPath();
809                 try {
810                         URI uri = url.toURI();
811                         filename = uri.getPath();
812                 } catch (URISyntaxException ignore) {
813                 }
814                 
815                 // Try URL-decoding the URL
816                 if (filename == null) {
817                         try {
818                                 filename = URLDecoder.decode(url.toString(), "UTF-8");
819                         } catch (UnsupportedEncodingException ignore) {
820                         }
821                 }
822                 
823                 // Last resort
824                 if (filename == null) {
825                         filename = "";
826                 }
827                 
828                 // Remove path from filename
829                 if (filename.lastIndexOf('/') >= 0) {
830                         filename = filename.substring(filename.lastIndexOf('/') + 1);
831                 }
832                 
833                 try {
834                         InputStream is = url.openStream();
835                         if (open(is, filename, parent)) {
836                                 // Close previous window if replacing
837                                 if (parent.replaceable && parent.document.isSaved()) {
838                                         parent.closeAction();
839                                         parent.replaceable = false;
840                                 }
841                         }
842                 } catch (IOException e) {
843                         JOptionPane.showMessageDialog(parent,
844                                         "An error occurred while opening the file " + filename,
845                                         "Error loading file", JOptionPane.ERROR_MESSAGE);
846                 }
847                 
848                 return false;
849         }
850         
851         
852         /**
853          * Open the specified file from an InputStream in a new design frame.  If an error
854          * occurs, an error dialog is shown and <code>false</code> is returned.
855          * 
856          * @param stream        the stream to load from.
857          * @param filename      the file name to display in dialogs (not set to the document).
858          * @param parent        the parent component for which a progress dialog is opened.
859          * @return                      whether the file was successfully loaded and opened.
860          */
861         private static boolean open(InputStream stream, String filename, Window parent) {
862                 OpenFileWorker worker = new OpenFileWorker(stream, ROCKET_LOADER);
863                 return open(worker, filename, null, parent);
864         }
865         
866         
867         /**
868          * Open the specified file in a new design frame.  If an error occurs, an error
869          * dialog is shown and <code>false</code> is returned.
870          * 
871          * @param file          the file to open.
872          * @param parent        the parent component for which a progress dialog is opened.
873          * @return                      whether the file was successfully loaded and opened.
874          */
875         public static boolean open(File file, Window parent) {
876                 OpenFileWorker worker = new OpenFileWorker(file, ROCKET_LOADER);
877                 return open(worker, file.getName(), file, parent);
878         }
879         
880         
881         /**
882          * Open the specified file using the provided worker.
883          * 
884          * @param worker        the OpenFileWorker that loads the file.
885          * @param filename      the file name to display in dialogs.
886          * @param file          the File to set the document to (may be null).
887          * @param parent
888          * @return
889          */
890         private static boolean open(OpenFileWorker worker, String filename, File file,
891                         Window parent) {
892                 
893                 MotorDatabaseLoadingDialog.check(null);
894                 
895                 // Open the file in a Swing worker thread
896                 if (!SwingWorkerDialog.runWorker(parent, "Opening file",
897                                 "Reading " + filename + "...", worker)) {
898                         
899                         // User cancelled the operation
900                         return false;
901                 }
902                 
903
904                 // Handle the document
905                 OpenRocketDocument doc = null;
906                 try {
907                         
908                         doc = worker.get();
909                         
910                 } catch (ExecutionException e) {
911                         
912                         Throwable cause = e.getCause();
913                         
914                         if (cause instanceof FileNotFoundException) {
915                                 
916                                 JOptionPane.showMessageDialog(parent,
917                                                 "File not found: " + filename,
918                                                 "Error opening file", JOptionPane.ERROR_MESSAGE);
919                                 return false;
920                                 
921                         } else if (cause instanceof RocketLoadException) {
922                                 
923                                 JOptionPane.showMessageDialog(parent,
924                                                 "Unable to open file '" + filename + "': "
925                                                                 + cause.getMessage(),
926                                                 "Error opening file", JOptionPane.ERROR_MESSAGE);
927                                 return false;
928                                 
929                         } else {
930                                 
931                                 throw new BugException("Unknown error when opening file", e);
932                                 
933                         }
934                         
935                 } catch (InterruptedException e) {
936                         throw new BugException("EDT was interrupted", e);
937                 }
938                 
939                 if (doc == null) {
940                         throw new BugException("BUG: Document loader returned null");
941                 }
942                 
943
944                 // Show warnings
945                 WarningSet warnings = worker.getRocketLoader().getWarnings();
946                 if (!warnings.isEmpty()) {
947                         WarningDialog.showWarnings(parent,
948                                         new Object[] {
949                                                         "The following problems were encountered while opening " + filename + ".",
950                                                         "Some design features may not have been loaded correctly."
951                                         },
952                                         "Warnings while opening file", warnings);
953                 }
954                 
955
956                 // Set document state
957                 doc.setFile(file);
958                 doc.setSaved(true);
959                 
960                 // Open the frame
961                 BasicFrame frame = new BasicFrame(doc);
962                 frame.setVisible(true);
963                 
964                 return true;
965         }
966         
967         
968
969
970
971         private boolean saveAction() {
972                 File file = document.getFile();
973                 if (file == null) {
974                         return saveAsAction();
975                 }
976                 
977                 // Saving RockSim designs is not supported
978                 if (ROCKSIM_DESIGN_FILTER.accept(file)) {
979                         file = new File(file.getAbsolutePath().replaceAll(".[rR][kK][tT](.[gG][zZ])?$",
980                                         ".ork"));
981                         
982                         int option = JOptionPane.showConfirmDialog(this, new Object[] {
983                                         "Saving designs in RockSim format is not supported.",
984                                         "Save in OpenRocket format instead (" + file.getName() + ")?"
985                                 }, "Save " + file.getName(), JOptionPane.YES_NO_OPTION,
986                                         JOptionPane.QUESTION_MESSAGE, null);
987                         if (option != JOptionPane.YES_OPTION)
988                                 return false;
989                         
990                         document.setFile(file);
991                 }
992                 return saveAs(file);
993         }
994         
995         
996         private boolean saveAsAction() {
997                 File file = null;
998                 while (file == null) {
999                         // TODO: HIGH: what if *.rkt chosen?
1000                         StorageOptionChooser storageChooser =
1001                                         new StorageOptionChooser(document, document.getDefaultStorageOptions());
1002                         JFileChooser chooser = new JFileChooser();
1003                         chooser.setFileFilter(OPENROCKET_DESIGN_FILTER);
1004                         chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
1005                         chooser.setAccessory(storageChooser);
1006                         if (document.getFile() != null)
1007                                 chooser.setSelectedFile(document.getFile());
1008                         
1009                         if (chooser.showSaveDialog(BasicFrame.this) != JFileChooser.APPROVE_OPTION)
1010                                 return false;
1011                         
1012                         file = chooser.getSelectedFile();
1013                         if (file == null)
1014                                 return false;
1015                         
1016                         Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
1017                         storageChooser.storeOptions(document.getDefaultStorageOptions());
1018                         
1019                         if (file.getName().indexOf('.') < 0) {
1020                                 String name = file.getAbsolutePath();
1021                                 name = name + ".ork";
1022                                 file = new File(name);
1023                         }
1024                         
1025                         if (file.exists()) {
1026                                 int result = JOptionPane.showConfirmDialog(this,
1027                                                 "File '" + file.getName() + "' exists.  Do you want to overwrite it?",
1028                                                 "File exists", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
1029                                 if (result != JOptionPane.YES_OPTION)
1030                                         return false;
1031                         }
1032                 }
1033                 saveAs(file);
1034                 return true;
1035         }
1036         
1037         
1038         private boolean saveAs(File file) {
1039                 System.out.println("Saving to file: " + file.getName());
1040                 boolean saved = false;
1041                 
1042                 if (!StorageOptionChooser.verifyStorageOptions(document, this)) {
1043                         // User cancelled the dialog
1044                         return false;
1045                 }
1046                 
1047
1048                 SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER);
1049                 
1050                 if (!SwingWorkerDialog.runWorker(this, "Saving file",
1051                                 "Writing " + file.getName() + "...", worker)) {
1052                         
1053                         // User cancelled the save
1054                         file.delete();
1055                         return false;
1056                 }
1057                 
1058                 try {
1059                         worker.get();
1060                         document.setFile(file);
1061                         document.setSaved(true);
1062                         saved = true;
1063                         setTitle();
1064                 } catch (ExecutionException e) {
1065                         
1066                         Throwable cause = e.getCause();
1067                         
1068                         if (cause instanceof IOException) {
1069                                 JOptionPane.showMessageDialog(this, new String[] {
1070                                                 "An I/O error occurred while saving:",
1071                                                 e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
1072                                 return false;
1073                         } else {
1074                                 Reflection.handleWrappedException(e);
1075                         }
1076                         
1077                 } catch (InterruptedException e) {
1078                         throw new BugException("EDT was interrupted", e);
1079                 }
1080                 
1081                 return saved;
1082         }
1083         
1084         
1085         private boolean closeAction() {
1086                 if (!document.isSaved()) {
1087                         ComponentConfigDialog.hideDialog();
1088                         int result = JOptionPane.showConfirmDialog(this,
1089                                         "Design '" + rocket.getName() + "' has not been saved.  " +
1090                                                         "Do you want to save it?",
1091                                         "Design not saved", JOptionPane.YES_NO_CANCEL_OPTION,
1092                                         JOptionPane.QUESTION_MESSAGE);
1093                         if (result == JOptionPane.YES_OPTION) {
1094                                 // Save
1095                                 if (!saveAction())
1096                                         return false; // If save was interrupted
1097                         } else if (result == JOptionPane.NO_OPTION) {
1098                                 // Don't save: No-op
1099                         } else {
1100                                 // Cancel or close
1101                                 return false;
1102                         }
1103                 }
1104                 
1105                 // Rocket has been saved or discarded
1106                 this.dispose();
1107                 
1108                 // TODO: LOW: Close only dialogs that have this frame as their parent
1109                 ComponentConfigDialog.hideDialog();
1110                 ComponentAnalysisDialog.hideDialog();
1111                 
1112                 frames.remove(this);
1113                 if (frames.isEmpty())
1114                         System.exit(0);
1115                 return true;
1116         }
1117         
1118         
1119         /**
1120          * Closes this frame if it is replaceable.
1121          */
1122         public void closeIfReplaceable() {
1123                 if (this.replaceable && document.isSaved()) {
1124                         closeAction();
1125                 }
1126         }
1127
1128     /**
1129      * 
1130      */
1131     public void printAction() {
1132         new PrintDialog(document);
1133     }
1134     
1135         /**
1136          * Open a new design window with a basic rocket+stage.
1137          */
1138         public static void newAction() {
1139                 log.debug("New action initiated");
1140                 Rocket rocket = new Rocket();
1141                 Stage stage = new Stage();
1142                 stage.setName("Sustainer");
1143                 rocket.addChild(stage);
1144                 OpenRocketDocument doc = new OpenRocketDocument(rocket);
1145                 doc.setSaved(true);
1146                 
1147                 BasicFrame frame = new BasicFrame(doc);
1148                 frame.replaceable = true;
1149                 frame.setVisible(true);
1150                 ComponentConfigDialog.showDialog(frame, doc, rocket);
1151         }
1152         
1153         /**
1154          * Quit the application.  Confirms saving unsaved designs.  The action of File->Quit.
1155          */
1156         public static void quitAction() {
1157                 for (int i = frames.size() - 1; i >= 0; i--) {
1158                         if (!frames.get(i).closeAction()) {
1159                                 // Close canceled
1160                                 return;
1161                         }
1162                 }
1163                 // Should not be reached, but just in case
1164                 System.exit(0);
1165         }
1166         
1167         
1168         /**
1169          * Set the title of the frame, taking into account the name of the rocket, file it 
1170          * has been saved to (if any) and saved status.
1171          */
1172         private void setTitle() {
1173                 File file = document.getFile();
1174                 boolean saved = document.isSaved();
1175                 String title;
1176                 
1177                 title = rocket.getName();
1178                 if (file != null) {
1179                         title = title + " (" + file.getName() + ")";
1180                 }
1181                 if (!saved)
1182                         title = "*" + title;
1183                 
1184                 setTitle(title);
1185         }
1186         
1187         
1188
1189         /**
1190          * Find a currently open BasicFrame containing the specified rocket.  This method
1191          * can be used to map a Rocket to a BasicFrame from GUI methods.
1192          * 
1193          * @param rocket the Rocket.
1194          * @return               the corresponding BasicFrame, or <code>null</code> if none found.
1195          */
1196         public static BasicFrame findFrame(Rocket rocket) {
1197                 for (BasicFrame f : frames) {
1198                         if (f.rocket == rocket)
1199                                 return f;
1200                 }
1201                 return null;
1202         }
1203         
1204         /**
1205          * Find a currently open document by the rocket object.  This method can be used
1206          * to map a Rocket to OpenRocketDocument from GUI methods.
1207          * 
1208          * @param rocket the Rocket.
1209          * @return               the corresponding OpenRocketDocument, or <code>null</code> if not found.
1210          */
1211         public static OpenRocketDocument findDocument(Rocket rocket) {
1212                 for (BasicFrame f : frames) {
1213                         if (f.rocket == rocket)
1214                                 return f.document;
1215                 }
1216                 return null;
1217         }
1218         
1219         
1220         public static void main(final String[] args) {
1221                 
1222                 // Run the actual startup method in the EDT since it can use progress dialogs etc.
1223                 try {
1224                         SwingUtilities.invokeAndWait(new Runnable() {
1225                                 @Override
1226                                 public void run() {
1227                                         runMain(args);
1228                                 }
1229                         });
1230                 } catch (InterruptedException e) {
1231                         e.printStackTrace();
1232                 } catch (InvocationTargetException e) {
1233                         e.printStackTrace();
1234                 }
1235                 
1236         }
1237         
1238         
1239         private static void runMain(String[] args) {
1240                 
1241                 // Initialize the splash screen with version info
1242                 Splash.init();
1243                 
1244
1245                 // Start update info fetching
1246                 final UpdateInfoRetriever updateInfo;
1247                 if (Prefs.getCheckUpdates()) {
1248                         updateInfo = new UpdateInfoRetriever();
1249                         updateInfo.start();
1250                 } else {
1251                         updateInfo = null;
1252                 }
1253                 
1254
1255                 // Set the best available look-and-feel
1256                 GUIUtil.setBestLAF();
1257                 
1258                 // Set tooltip delay time.  Tooltips are used in MotorChooserDialog extensively.
1259                 ToolTipManager.sharedInstance().setDismissDelay(30000);
1260                 
1261
1262                 // Setup the uncaught exception handler
1263                 ExceptionHandler.registerExceptionHandler();
1264                 
1265
1266                 // Load defaults
1267                 Prefs.loadDefaultUnits();
1268                 
1269
1270                 // Load motors etc.
1271                 Databases.fakeMethod();
1272                 
1273                 // Starting action (load files or open new document)
1274                 if (!handleCommandLine(args)) {
1275                         newAction();
1276                 }
1277                 
1278
1279                 // Check whether update info has been fetched or whether it needs more time
1280                 checkUpdateStatus(updateInfo);
1281         }
1282         
1283         
1284         private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
1285                 if (updateInfo == null)
1286                         return;
1287                 
1288                 int delay = 1000;
1289                 if (!updateInfo.isRunning())
1290                         delay = 100;
1291                 
1292                 final Timer timer = new Timer(delay, null);
1293                 
1294                 ActionListener listener = new ActionListener() {
1295                         private int count = 5;
1296                         
1297                         @Override
1298                         public void actionPerformed(ActionEvent e) {
1299                                 if (!updateInfo.isRunning()) {
1300                                         timer.stop();
1301                                         
1302                                         String current = Prefs.getVersion();
1303                                         String last = Prefs.getString(Prefs.LAST_UPDATE, "");
1304                                         
1305                                         UpdateInfo info = updateInfo.getUpdateInfo();
1306                                         if (info != null && info.getLatestVersion() != null &&
1307                                                         !current.equals(info.getLatestVersion()) &&
1308                                                         !last.equals(info.getLatestVersion())) {
1309                                                 
1310                                                 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
1311                                                 infoDialog.setVisible(true);
1312                                                 if (infoDialog.isReminderSelected()) {
1313                                                         Prefs.putString(Prefs.LAST_UPDATE, "");
1314                                                 } else {
1315                                                         Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
1316                                                 }
1317                                         }
1318                                 }
1319                                 count--;
1320                                 if (count <= 0)
1321                                         timer.stop();
1322                         }
1323                 };
1324                 timer.addActionListener(listener);
1325                 timer.start();
1326         }
1327         
1328         
1329         /**
1330          * Handles arguments passed from the command line.  This may be used either
1331          * when starting the first instance of OpenRocket or later when OpenRocket is
1332          * executed again while running.
1333          * 
1334          * @param args  the command-line arguments.
1335          * @return              whether a new frame was opened or similar user desired action was
1336          *                              performed as a result.
1337          */
1338         public static boolean handleCommandLine(String[] args) {
1339                 
1340                 // Check command-line for files
1341                 boolean opened = false;
1342                 for (String file : args) {
1343                         if (open(new File(file), null)) {
1344                                 opened = true;
1345                         }
1346                 }
1347                 return opened;
1348         }
1349         
1350 }