X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Fnet%2Fsf%2Fopenrocket%2Fgui%2Fmain%2FBasicFrame.java;h=a2a18e14a369bf739a6bc116bb2de2c2420d1a46;hb=198227dc14b96901f3105fd816b6a9b84993adef;hp=4bd73ca1d3a51c170e3a0d1c59bad2bb32079fef;hpb=720d4935bc6bec453e6478ad5227356c626610a2;p=debian%2Fopenrocket diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index 4bd73ca1..a2a18e14 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -3,6 +3,7 @@ package net.sf.openrocket.gui.main; import java.awt.Dimension; import java.awt.Font; import java.awt.Toolkit; +import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; @@ -14,9 +15,17 @@ import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; import java.util.ArrayList; -import java.util.Iterator; +import java.util.concurrent.ExecutionException; import javax.swing.Action; import javax.swing.InputMap; @@ -33,12 +42,13 @@ import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; +import javax.swing.JTextField; import javax.swing.KeyStroke; -import javax.swing.LookAndFeel; +import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.ToolTipManager; -import javax.swing.UIManager; import javax.swing.border.TitledBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; @@ -48,25 +58,41 @@ import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.communication.UpdateInfo; +import net.sf.openrocket.communication.UpdateInfoRetriever; +import net.sf.openrocket.database.Databases; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.GeneralRocketLoader; -import net.sf.openrocket.file.OpenRocketSaver; import net.sf.openrocket.file.RocketLoadException; import net.sf.openrocket.file.RocketLoader; import net.sf.openrocket.file.RocketSaver; -import net.sf.openrocket.gui.ComponentAnalysisDialog; -import net.sf.openrocket.gui.PreferencesDialog; +import net.sf.openrocket.file.openrocket.OpenRocketSaver; import net.sf.openrocket.gui.StorageOptionChooser; import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; +import net.sf.openrocket.gui.dialogs.AboutDialog; +import net.sf.openrocket.gui.dialogs.BugReportDialog; +import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog; +import net.sf.openrocket.gui.dialogs.ExampleDesignDialog; +import net.sf.openrocket.gui.dialogs.LicenseDialog; +import net.sf.openrocket.gui.dialogs.SwingWorkerDialog; +import net.sf.openrocket.gui.dialogs.UpdateInfoDialog; +import net.sf.openrocket.gui.dialogs.WarningDialog; +import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog; import net.sf.openrocket.gui.scalefigure.RocketPanel; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.GUIUtil; import net.sf.openrocket.util.Icons; +import net.sf.openrocket.util.OpenFileWorker; import net.sf.openrocket.util.Prefs; +import net.sf.openrocket.util.Reflection; +import net.sf.openrocket.util.SaveFileWorker; +import net.sf.openrocket.util.TestRockets; public class BasicFrame extends JFrame { private static final long serialVersionUID = 1L; @@ -75,23 +101,26 @@ public class BasicFrame extends JFrame { * The RocketLoader instance used for loading all rocket designs. */ private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader(); + + private static final RocketSaver ROCKET_SAVER = new OpenRocketSaver(); - /** - * File filter for filtering only rocket designs. - */ - private static final FileFilter ROCKET_DESIGN_FILTER = new FileFilter() { - @Override - public String getDescription() { - return "OpenRocket designs (*.ork)"; - } - @Override - public boolean accept(File f) { - String name = f.getName().toLowerCase(); - return name.endsWith(".ork") || name.endsWith(".ork.gz"); - } - }; + // FileFilters for different types of rocket design files + private static final FileFilter ALL_DESIGNS_FILTER = + new SimpleFileFilter("All rocket designs (*.ork; *.rkt)", + ".ork", ".ork.gz", ".rkt", ".rkt.gz"); + + private static final FileFilter OPENROCKET_DESIGN_FILTER = + new SimpleFileFilter("OpenRocket designs (*.ork)", ".ork", ".ork.gz"); + + private static final FileFilter ROCKSIM_DESIGN_FILTER = + new SimpleFileFilter("RockSim designs (*.rkt)", ".rkt", ".rkt.gz"); + + + + public static final int COMPONENT_TAB = 0; + public static final int SIMULATION_TAB = 1; /** @@ -115,9 +144,13 @@ public class BasicFrame extends JFrame { private final OpenRocketDocument document; private final Rocket rocket; + private JTabbedPane tabbedPane; private RocketPanel rocketpanel; private ComponentTree tree = null; - private final TreeSelectionModel selectionModel; + + private final DocumentSelectionModel selectionModel; + private final TreeSelectionModel componentSelectionModel; + private final ListSelectionModel simulationSelectionModel; /** Actions available for rocket modifications */ private final RocketActions actions; @@ -146,9 +179,19 @@ public class BasicFrame extends JFrame { }); - // Create the selection model that will be used - selectionModel = new DefaultTreeSelectionModel(); - selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + // Create the component tree selection model that will be used + componentSelectionModel = new DefaultTreeSelectionModel(); + componentSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + + // Obtain the simulation selection model that will be used + SimulationPanel simulationPanel = new SimulationPanel(document); + simulationSelectionModel = simulationPanel.getSimulationListSelectionModel(); + + // Combine into a DocumentSelectionModel + selectionModel = new DocumentSelectionModel(document); + selectionModel.attachComponentTreeSelectionModel(componentSelectionModel); + selectionModel.attachSimulationListSelectionModel(simulationSelectionModel); + actions = new RocketActions(document, selectionModel, this); @@ -160,11 +203,11 @@ public class BasicFrame extends JFrame { // The top tabbed pane - JTabbedPane tabbed = new JTabbedPane(); - tabbed.addTab("Rocket design", null, designTab()); - tabbed.addTab("Flight simulations", null, simulationsTab()); + tabbedPane = new JTabbedPane(); + tabbedPane.addTab("Rocket design", null, designTab()); + tabbedPane.addTab("Flight simulations", null, simulationPanel); - vertical.setTopComponent(tabbed); + vertical.setTopComponent(tabbedPane); @@ -202,7 +245,9 @@ public class BasicFrame extends JFrame { } }); this.setLocationByPlatform(true); - + + GUIUtil.setWindowIcons(this); + this.validate(); vertical.setDividerLocation(0.4); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); @@ -213,7 +258,6 @@ public class BasicFrame extends JFrame { } }); frames.add(this); - } @@ -232,7 +276,7 @@ public class BasicFrame extends JFrame { JPanel panel = new JPanel(new MigLayout("fill, flowy","","[grow]")); tree = new ComponentTree(rocket); - tree.setSelectionModel(selectionModel); + tree.setSelectionModel(componentSelectionModel); // Remove JTree key events that interfere with menu accelerators InputMap im = SwingUtilities.getUIInputMap(tree, JComponent.WHEN_FOCUSED); @@ -253,7 +297,7 @@ public class BasicFrame extends JFrame { int selRow = tree.getRowForLocation(e.getX(), e.getY()); TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); if(selRow != -1) { - if(e.getClickCount() == 2) { + if((e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) { // Double-click RocketComponent c = (RocketComponent)selPath.getLastPathComponent(); ComponentConfigDialog.showDialog(BasicFrame.this, @@ -265,10 +309,10 @@ public class BasicFrame extends JFrame { tree.addMouseListener(ml); // Update dialog when selection is changed - selectionModel.addTreeSelectionListener(new TreeSelectionListener() { + componentSelectionModel.addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { // Scroll tree to the selected item - TreePath path = selectionModel.getSelectionPath(); + TreePath path = componentSelectionModel.getSelectionPath(); if (path == null) return; tree.scrollPathToVisible(path); @@ -313,7 +357,7 @@ public class BasicFrame extends JFrame { scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scroll.setViewportView(new ComponentAddButtons(document, selectionModel, + scroll.setViewportView(new ComponentAddButtons(document, componentSelectionModel, scroll.getViewport())); scroll.setBorder(null); scroll.setViewportBorder(null); @@ -330,15 +374,6 @@ public class BasicFrame extends JFrame { } - /** - * Construct the "Flight simulations" tab. - * @return - */ - private JComponent simulationsTab() { - return new SimulationPanel(document); - } - - /** * Creates the menu for the window. @@ -379,6 +414,23 @@ public class BasicFrame extends JFrame { }); menu.add(item); + item = new JMenuItem("Open example..."); + item.getAccessibleContext().setAccessibleDescription("Open an example rocket design"); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, + ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK)); + item.setIcon(Icons.FILE_OPEN_EXAMPLE); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + URL[] urls = ExampleDesignDialog.selectExampleDesigns(BasicFrame.this); + if (urls != null) { + for (URL u: urls) { + open(u, BasicFrame.this); + } + } + } + }); + menu.add(item); + menu.addSeparator(); item = new JMenuItem("Save",KeyEvent.VK_S); @@ -505,6 +557,13 @@ public class BasicFrame extends JFrame { menu.add(item); + //// Debug + // (shown if openrocket.debug.menu is defined) + if (System.getProperty("openrocket.debug.menu") != null) { + menubar.add(makeDebugMenu()); + } + + //// Help @@ -513,6 +572,8 @@ public class BasicFrame extends JFrame { menu.getAccessibleContext().setAccessibleDescription("Information about OpenRocket"); menubar.add(menu); + + item = new JMenuItem("License",KeyEvent.VK_L); item.getAccessibleContext().setAccessibleDescription("OpenRocket license information"); item.addActionListener(new ActionListener() { @@ -522,6 +583,17 @@ public class BasicFrame extends JFrame { }); menu.add(item); + item = new JMenuItem("Bug report",KeyEvent.VK_B); + item.getAccessibleContext().setAccessibleDescription("Information about reporting " + + "bugs in OpenRocket"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { +// new BugDialog(BasicFrame.this).setVisible(true); + BugReportDialog.showBugReportDialog(BasicFrame.this); + } + }); + menu.add(item); + item = new JMenuItem("About",KeyEvent.VK_A); item.getAccessibleContext().setAccessibleDescription("About OpenRocket"); item.addActionListener(new ActionListener() { @@ -536,65 +608,327 @@ public class BasicFrame extends JFrame { } + private JMenu makeDebugMenu() { + JMenu menu; + JMenuItem item; + + //// Debug menu + menu = new JMenu("Debug"); + menu.getAccessibleContext().setAccessibleDescription("OpenRocket debugging tasks"); + + item = new JMenuItem("What is this menu?"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JOptionPane.showMessageDialog(BasicFrame.this, + new Object[] { + "The 'Debug' menu includes actions for testing and debugging " + + "OpenRocket.", " ", + "The menu is made visible by defining the system property " + + "'openrocket.debug.menu' when starting OpenRocket.", + "It should not be visible by default." }, + "Debug menu", JOptionPane.INFORMATION_MESSAGE); + } + }); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Create test rocket"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JTextField field = new JTextField(); + int sel = JOptionPane.showOptionDialog(BasicFrame.this, new Object[] { + "Input text key to generate random rocket:", + field + }, "Generate random test rocket", JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, null, new Object[] { + "Random", "OK" + }, "OK"); + + Rocket r; + if (sel == 0) { + r = new TestRockets(null).makeTestRocket(); + } else if (sel == 1) { + r = new TestRockets(field.getText()).makeTestRocket(); + } else { + return; + } + + OpenRocketDocument doc = new OpenRocketDocument(r); + doc.setSaved(true); + BasicFrame frame = new BasicFrame(doc); + frame.setVisible(true); + } + }); + menu.add(item); + + + + item = new JMenuItem("Create 'Iso-Haisu'"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Rocket r = TestRockets.makeIsoHaisu(); + OpenRocketDocument doc = new OpenRocketDocument(r); + doc.setSaved(true); + BasicFrame frame = new BasicFrame(doc); + frame.setVisible(true); + } + }); + menu.add(item); + + + item = new JMenuItem("Create 'Big Blue'"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Rocket r = TestRockets.makeBigBlue(); + OpenRocketDocument doc = new OpenRocketDocument(r); + doc.setSaved(true); + BasicFrame frame = new BasicFrame(doc); + frame.setVisible(true); + } + }); + menu.add(item); + + + + menu.addSeparator(); + + item = new JMenuItem("Exception here"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + throw new RuntimeException("Testing exception from menu action listener"); + } + }); + menu.add(item); + + item = new JMenuItem("Exception from EDT"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + throw new RuntimeException("Testing exception from " + + "later invoked EDT thread"); + } + }); + } + }); + menu.add(item); + + item = new JMenuItem("Exception from other thread"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new Thread() { + @Override + public void run() { + throw new RuntimeException("Testing exception from " + + "newly created thread"); + } + }.start(); + } + }); + menu.add(item); + + + + return menu; + } + + + + /** + * Select the tab on the main pane. + * + * @param tab one of {@link #COMPONENT_TAB} or {@link #SIMULATION_TAB}. + */ + public void selectTab(int tab) { + tabbedPane.setSelectedIndex(tab); + } + - // TODO: HIGH: Remember last directory on open/save private void openAction() { JFileChooser chooser = new JFileChooser(); - chooser.setFileFilter(ROCKET_DESIGN_FILTER); + + chooser.addChoosableFileFilter(ALL_DESIGNS_FILTER); + chooser.addChoosableFileFilter(OPENROCKET_DESIGN_FILTER); + chooser.addChoosableFileFilter(ROCKSIM_DESIGN_FILTER); + chooser.setFileFilter(ALL_DESIGNS_FILTER); + + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); chooser.setMultiSelectionEnabled(true); chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); - if (chooser.showOpenDialog(BasicFrame.this) != JFileChooser.APPROVE_OPTION) + if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return; Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); File[] files = chooser.getSelectedFiles(); - boolean opened = false; for (File file: files) { System.out.println("Opening file: " + file); - if (open(file)) { - opened = true; + if (open(file, this)) { + + // Close previous window if replacing + if (replaceable && document.isSaved()) { + closeAction(); + replaceable = false; + } } } + } + + + + private static boolean open(URL url, BasicFrame parent) { + String filename = null; + + // Try using URI.getPath(); + try { + URI uri = url.toURI(); + filename = uri.getPath(); + } catch (URISyntaxException ignore) { } - // Close this frame if replaceable and file opened successfully - if (replaceable && opened) { - closeAction(); + // Try URL-decoding the URL + if (filename == null) { + try { + filename = URLDecoder.decode(url.toString(), "UTF-8"); + } catch (UnsupportedEncodingException ignore) { } + } + + // Last resort + if (filename == null) { + filename = ""; + } + + // Remove path from filename + if (filename.lastIndexOf('/') >= 0) { + filename = filename.substring(filename.lastIndexOf('/')+1); + } + + try { + InputStream is = url.openStream(); + if (open(is, filename, parent)) { + // Close previous window if replacing + if (parent.replaceable && parent.document.isSaved()) { + parent.closeAction(); + parent.replaceable = false; + } + } + } catch (IOException e) { + JOptionPane.showMessageDialog(parent, + "An error occurred while opening the file " + filename, + "Error loading file", JOptionPane.ERROR_MESSAGE); } + + return false; } /** - * Open the specified file in a new design frame. If an error occurs, an error dialog - * is shown and false is returned. + * Open the specified file from an InputStream in a new design frame. If an error + * occurs, an error dialog is shown and false is returned. * - * @param file the file to open. - * @return whether the file was successfully loaded and opened. + * @param stream the stream to load from. + * @param filename the file name to display in dialogs (not set to the document). + * @param parent the parent component for which a progress dialog is opened. + * @return whether the file was successfully loaded and opened. */ - private static boolean open(File file) { - OpenRocketDocument doc = null; - try { - doc = ROCKET_LOADER.load(file); - } catch (RocketLoadException e) { - JOptionPane.showMessageDialog(null, "Unable to open file '" + file.getName() - +"': " + e.getMessage(), "Error opening file", JOptionPane.ERROR_MESSAGE); - e.printStackTrace(); + private static boolean open(InputStream stream, String filename, Window parent) { + OpenFileWorker worker = new OpenFileWorker(stream, ROCKET_LOADER); + return open(worker, filename, null, parent); + } + + + /** + * Open the specified file in a new design frame. If an error occurs, an error + * dialog is shown and false is returned. + * + * @param file the file to open. + * @param parent the parent component for which a progress dialog is opened. + * @return whether the file was successfully loaded and opened. + */ + private static boolean open(File file, Window parent) { + OpenFileWorker worker = new OpenFileWorker(file, ROCKET_LOADER); + return open(worker, file.getName(), file, parent); + } + + + /** + * Open the specified file using the provided worker. + * + * @param worker the OpenFileWorker that loads the file. + * @param filename the file name to display in dialogs. + * @param file the File to set the document to (may be null). + * @param parent + * @return + */ + private static boolean open(OpenFileWorker worker, String filename, File file, + Window parent) { + + // Open the file in a Swing worker thread + if (!SwingWorkerDialog.runWorker(parent, "Opening file", + "Reading " + filename + "...", worker)) { + + // User cancelled the operation return false; } + + + // Handle the document + OpenRocketDocument doc = null; + try { + + doc = worker.get(); + + } catch (ExecutionException e) { + + Throwable cause = e.getCause(); + + if (cause instanceof FileNotFoundException) { + + JOptionPane.showMessageDialog(parent, + "File not found: " + filename, + "Error opening file", JOptionPane.ERROR_MESSAGE); + return false; + + } else if (cause instanceof RocketLoadException) { + + JOptionPane.showMessageDialog(parent, + "Unable to open file '" + filename +"': " + + cause.getMessage(), + "Error opening file", JOptionPane.ERROR_MESSAGE); + return false; + + } else { + + throw new BugException("Unknown error when opening file", e); + + } + + } catch (InterruptedException e) { + throw new BugException("EDT was interrupted", e); + } + + if (doc == null) { + throw new BugException("BUG: Document loader returned null"); + } + - if (doc == null) { - throw new RuntimeException("BUG: Rocket loader returned null"); - } - // Show warnings - Iterator warns = ROCKET_LOADER.getWarnings().iterator(); - System.out.println("Warnings:"); - while (warns.hasNext()) { - System.out.println(" "+warns.next()); - // TODO: HIGH: dialog - } + WarningSet warnings = worker.getRocketLoader().getWarnings(); + if (!warnings.isEmpty()) { + WarningDialog.showWarnings(parent, + new Object[] { + "The following problems were encountered while opening " + filename + ".", + "Some design features may not have been loaded correctly." + }, + "Warnings while opening file", warnings); + } + // Set document state doc.setFile(file); @@ -609,22 +943,45 @@ public class BasicFrame extends JFrame { + + + + + + private boolean saveAction() { File file = document.getFile(); if (file==null) { return saveAsAction(); - } else { - return saveAs(file); } + + // Saving RockSim designs is not supported + if (ROCKSIM_DESIGN_FILTER.accept(file)) { + file = new File(file.getAbsolutePath().replaceAll(".[rR][kK][tT](.[gG][zZ])?$", + ".ork")); + + int option = JOptionPane.showConfirmDialog(this, new Object[] { + "Saving designs in RockSim format is not supported.", + "Save in OpenRocket format instead ("+file.getName()+")?" + }, "Save "+file.getName(), JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null); + if (option != JOptionPane.YES_OPTION) + return false; + + document.setFile(file); + } + return saveAs(file); } + private boolean saveAsAction() { File file = null; while (file == null) { + // TODO: HIGH: what if *.rkt chosen? StorageOptionChooser storageChooser = - new StorageOptionChooser(document.getDefaultStorageOptions()); + new StorageOptionChooser(document, document.getDefaultStorageOptions()); JFileChooser chooser = new JFileChooser(); - chooser.setFileFilter(ROCKET_DESIGN_FILTER); + chooser.setFileFilter(OPENROCKET_DESIGN_FILTER); chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); chooser.setAccessory(storageChooser); if (document.getFile() != null) @@ -668,18 +1025,40 @@ public class BasicFrame extends JFrame { return false; } - RocketSaver saver = new OpenRocketSaver(); - try { - saver.save(file, document); - document.setFile(file); - document.setSaved(true); - saved = true; - } catch (IOException e) { - JOptionPane.showMessageDialog(this, new String[] { - "An I/O error occurred while saving:", - e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); + + SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER); + + if (!SwingWorkerDialog.runWorker(this, "Saving file", + "Writing " + file.getName() + "...", worker)) { + + // User cancelled the save + file.delete(); + return false; } - setTitle(); + + try { + worker.get(); + document.setFile(file); + document.setSaved(true); + saved = true; + setTitle(); + } catch (ExecutionException e) { + + Throwable cause = e.getCause(); + + if (cause instanceof IOException) { + JOptionPane.showMessageDialog(this, new String[] { + "An I/O error occurred while saving:", + e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); + return false; + } else { + Reflection.handleWrappedException(e); + } + + } catch (InterruptedException e) { + throw new BugException("EDT was interrupted", e); + } + return saved; } @@ -717,6 +1096,16 @@ public class BasicFrame extends JFrame { return true; } + + /** + * Closes this frame if it is replaceable. + */ + public void closeIfReplaceable() { + if (this.replaceable && document.isSaved()) { + closeAction(); + } + } + /** * Open a new design window with a basic rocket+stage. */ @@ -770,71 +1159,164 @@ public class BasicFrame extends JFrame { + /** + * Find a currently open BasicFrame containing the specified rocket. This method + * can be used to map a Rocket to a BasicFrame from GUI methods. + * + * @param rocket the Rocket. + * @return the corresponding BasicFrame, or null if none found. + */ + public static BasicFrame findFrame(Rocket rocket) { + for (BasicFrame f: frames) { + if (f.rocket == rocket) + return f; + } + return null; + } + /** + * Find a currently open document by the rocket object. This method can be used + * to map a Rocket to OpenRocketDocument from GUI methods. + * + * @param rocket the Rocket. + * @return the corresponding OpenRocketDocument, or null if not found. + */ + public static OpenRocketDocument findDocument(Rocket rocket) { + for (BasicFrame f: frames) { + if (f.rocket == rocket) + return f.document; + } + return null; + } - public static void main(String[] args) { + public static void main(final String[] args) { - /* - * Set the look-and-feel. On Linux, Motif/Metal is sometimes incorrectly used - * which is butt-ugly, so if the system l&f is Motif/Metal, we search for a few - * other alternatives. - */ + // Run the actual startup method in the EDT since it can use progress dialogs etc. try { - UIManager.LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels(); -// System.out.println("Available look-and-feels:"); -// for (int i=0; i