X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Fnet%2Fsf%2Fopenrocket%2Fgui%2Fmain%2FBasicFrame.java;h=5f2d92e016b255116fad7d889e44c1c6f241b12a;hb=3cf305a033d347380020d857e82ff1ddd6a4ef41;hp=c2d0d3367a62b866572745a181a118bf921a82e5;hpb=74b1644fb1780ad83c7c7015a989fbc098e485c2;p=debian%2Fopenrocket diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index c2d0d336..5f2d92e0 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -6,8 +6,6 @@ import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -19,15 +17,18 @@ 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.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.concurrent.ExecutionException; import javax.swing.Action; +import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; @@ -38,95 +39,85 @@ import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; -import javax.swing.JSeparator; +import javax.swing.JSpinner; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; -import javax.swing.LookAndFeel; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; -import javax.swing.Timer; -import javax.swing.ToolTipManager; -import javax.swing.UIManager; +import javax.swing.border.BevelBorder; import javax.swing.border.TitledBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; -import javax.swing.filechooser.FileFilter; import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import net.miginfocom.swing.MigLayout; 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.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.DebugLogDialog; +import net.sf.openrocket.gui.dialogs.DetailDialog; import net.sf.openrocket.gui.dialogs.ExampleDesignDialog; import net.sf.openrocket.gui.dialogs.LicenseDialog; +import net.sf.openrocket.gui.dialogs.MotorDatabaseLoadingDialog; +import net.sf.openrocket.gui.dialogs.PrintDialog; +import net.sf.openrocket.gui.dialogs.ScaleDialog; 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.optimization.GeneralOptimizationDialog; import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog; +import net.sf.openrocket.gui.main.componenttree.ComponentTree; import net.sf.openrocket.gui.scalefigure.RocketPanel; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.LogHelper; 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.startup.Application; +import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.FileHelper; import net.sf.openrocket.util.GUIUtil; import net.sf.openrocket.util.Icons; +import net.sf.openrocket.util.MemoryManagement; +import net.sf.openrocket.util.MemoryManagement.MemoryData; 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; - + private static final LogHelper log = Application.getLogger(); + /** * 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) { - if (f.isDirectory()) - return true; - String name = f.getName().toLowerCase(); - return name.endsWith(".ork") || name.endsWith(".ork.gz"); - } - }; - - - - public static final int COMPONENT_TAB = 0; - public static final int SIMULATION_TAB = 1; - + private static final Translator trans = Application.getTranslator(); + + public static final int COMPONENT_TAB = 0; + public static final int SIMULATION_TAB = 1; + /** * List of currently open frames. When the list goes empty @@ -134,18 +125,15 @@ public class BasicFrame extends JFrame { */ private static final ArrayList frames = new ArrayList(); - - - - + /** * Whether "New" and "Open" should replace this frame. * Should be set to false on the first rocket modification. */ private boolean replaceable = false; - - + + private final OpenRocketDocument document; private final Rocket rocket; @@ -161,7 +149,7 @@ public class BasicFrame extends JFrame { private final RocketActions actions; - + /** * Sole constructor. Creates a new frame based on the supplied document * and adds it to the current frames list. @@ -169,21 +157,23 @@ public class BasicFrame extends JFrame { * @param document the document to show. */ public BasicFrame(OpenRocketDocument document) { - + log.debug("Instantiating new BasicFrame"); + this.document = document; this.rocket = document.getRocket(); this.rocket.getDefaultConfiguration().setAllStages(); - + // Set replaceable flag to false at first modification rocket.addComponentChangeListener(new ComponentChangeListener() { + @Override public void componentChanged(ComponentChangeEvent e) { replaceable = false; BasicFrame.this.rocket.removeComponentChangeListener(this); } }); - + // Create the component tree selection model that will be used componentSelectionModel = new DefaultTreeSelectionModel(); componentSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); @@ -197,20 +187,24 @@ public class BasicFrame extends JFrame { selectionModel.attachComponentTreeSelectionModel(componentSelectionModel); selectionModel.attachSimulationListSelectionModel(simulationSelectionModel); - + actions = new RocketActions(document, selectionModel, this); + + log.debug("Constructing the BasicFrame UI"); // The main vertical split pane JSplitPane vertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true); vertical.setResizeWeight(0.5); this.add(vertical); - + // The top tabbed pane tabbedPane = new JTabbedPane(); - tabbedPane.addTab("Rocket design", null, designTab()); - tabbedPane.addTab("Flight simulations", null, simulationPanel); + //// Rocket design + tabbedPane.addTab(trans.get("BasicFrame.tab.Rocketdesign"), null, designTab()); + //// Flight simulations + tabbedPane.addTab(trans.get("BasicFrame.tab.Flightsim"), null, simulationPanel); vertical.setTopComponent(tabbedPane); @@ -220,14 +214,15 @@ public class BasicFrame extends JFrame { rocketpanel = new RocketPanel(document); vertical.setBottomComponent(rocketpanel); - + rocketpanel.setSelectionModel(tree.getSelectionModel()); + - createMenu(); - + rocket.addComponentChangeListener(new ComponentChangeListener() { + @Override public void componentChanged(ComponentChangeEvent e) { setTitle(); } @@ -235,22 +230,19 @@ public class BasicFrame extends JFrame { setTitle(); this.pack(); + - Dimension size = Prefs.getWindowSize(this.getClass()); - if (size == null) { - size = Toolkit.getDefaultToolkit().getScreenSize(); - size.width = size.width*9/10; - size.height = size.height*9/10; - } + // Set initial window size + Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); + size.width = size.width * 9 / 10; + size.height = size.height * 9 / 10; this.setSize(size); - this.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - Prefs.setWindowSize(BasicFrame.this.getClass(), BasicFrame.this.getSize()); - } - }); + + // Remember changed size + GUIUtil.rememberWindowSize(this); + this.setLocationByPlatform(true); - + GUIUtil.setWindowIcons(this); this.validate(); @@ -262,7 +254,9 @@ public class BasicFrame extends JFrame { closeAction(); } }); + frames.add(this); + log.debug("BasicFrame instantiation complete"); } @@ -272,17 +266,17 @@ public class BasicFrame extends JFrame { * for adding components. */ private JComponent designTab() { - JSplitPane horizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true); + JSplitPane horizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true); horizontal.setResizeWeight(0.5); - + // Upper-left segment, component tree - - JPanel panel = new JPanel(new MigLayout("fill, flowy","","[grow]")); - - tree = new ComponentTree(rocket); + + JPanel panel = new JPanel(new MigLayout("fill, flowy", "", "[grow]")); + + tree = new ComponentTree(document); tree.setSelectionModel(componentSelectionModel); - + // Remove JTree key events that interfere with menu accelerators InputMap im = SwingUtilities.getUIInputMap(tree, JComponent.WHEN_FOCUSED); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK), null); @@ -292,29 +286,30 @@ public class BasicFrame extends JFrame { im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK), null); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK), null); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK), null); + - // Double-click opens config dialog MouseListener ml = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { int selRow = tree.getRowForLocation(e.getX(), e.getY()); TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); - if(selRow != -1) { - if((e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) { + if (selRow != -1) { + if ((e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) { // Double-click - RocketComponent c = (RocketComponent)selPath.getLastPathComponent(); - ComponentConfigDialog.showDialog(BasicFrame.this, + RocketComponent c = (RocketComponent) selPath.getLastPathComponent(); + ComponentConfigDialog.showDialog(BasicFrame.this, BasicFrame.this.document, c); } } } }; tree.addMouseListener(ml); - + // Update dialog when selection is changed componentSelectionModel.addTreeSelectionListener(new TreeSelectionListener() { + @Override public void valueChanged(TreeSelectionEvent e) { // Scroll tree to the selected item TreePath path = componentSelectionModel.getSelectionPath(); @@ -324,61 +319,74 @@ public class BasicFrame extends JFrame { if (!ComponentConfigDialog.isDialogVisible()) return; - RocketComponent c = (RocketComponent)path.getLastPathComponent(); - ComponentConfigDialog.showDialog(BasicFrame.this, + RocketComponent c = (RocketComponent) path.getLastPathComponent(); + ComponentConfigDialog.showDialog(BasicFrame.this, BasicFrame.this.document, c); } }); - + // Place tree inside scroll pane JScrollPane scroll = new JScrollPane(tree); - panel.add(scroll,"spany, grow, wrap"); - + panel.add(scroll, "spany, grow, wrap"); + // Buttons JButton button = new JButton(actions.getMoveUpAction()); - panel.add(button,"sizegroup buttons, aligny 65%"); + panel.add(button, "sizegroup buttons, aligny 65%"); button = new JButton(actions.getMoveDownAction()); - panel.add(button,"sizegroup buttons, aligny 0%"); + panel.add(button, "sizegroup buttons, aligny 0%"); button = new JButton(actions.getEditAction()); panel.add(button, "sizegroup buttons"); button = new JButton(actions.getNewStageAction()); - panel.add(button,"sizegroup buttons"); + panel.add(button, "sizegroup buttons"); button = new JButton(actions.getDeleteAction()); button.setIcon(null); button.setMnemonic(0); - panel.add(button,"sizegroup buttons"); - + panel.add(button, "sizegroup buttons"); + horizontal.setLeftComponent(panel); - + // Upper-right segment, component addition buttons - - panel = new JPanel(new MigLayout("fill, insets 0","[0::]")); - + + panel = new JPanel(new MigLayout("fill, insets 0", "[0::]")); + scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scroll.setViewportView(new ComponentAddButtons(document, componentSelectionModel, scroll.getViewport())); scroll.setBorder(null); scroll.setViewportBorder(null); - - TitledBorder border = new TitledBorder("Add new component"); - border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD)); + + TitledBorder border = BorderFactory.createTitledBorder(trans.get("BasicFrame.title.Addnewcomp")); + GUIUtil.changeFontStyle(border, Font.BOLD); scroll.setBorder(border); - - panel.add(scroll,"grow"); - + + panel.add(scroll, "grow"); + horizontal.setRightComponent(panel); - + return horizontal; } + + /** + * Return the currently selected rocket component, or null if none selected. + */ + private RocketComponent getSelectedComponent() { + TreePath path = componentSelectionModel.getSelectionPath(); + if (path == null) + return null; + tree.scrollPathToVisible(path); + + return (RocketComponent) path.getLastPathComponent(); + } + /** * Creates the menu for the window. @@ -389,46 +397,62 @@ public class BasicFrame extends JFrame { JMenuItem item; //// File - menu = new JMenu("File"); + menu = new JMenu(trans.get("main.menu.file")); menu.setMnemonic(KeyEvent.VK_F); - menu.getAccessibleContext().setAccessibleDescription("File-handling related tasks"); + //// File-handling related tasks + menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.desc")); menubar.add(menu); - item = new JMenuItem("New",KeyEvent.VK_N); + //// New + item = new JMenuItem(trans.get("main.menu.file.new"), KeyEvent.VK_N); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)); item.setMnemonic(KeyEvent.VK_N); - item.getAccessibleContext().setAccessibleDescription("Create a new rocket design"); + //// Create a new rocket design + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.new.desc")); item.setIcon(Icons.FILE_NEW); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("New... selected"); newAction(); - if (replaceable) + if (replaceable) { + log.info("Closing previous window"); closeAction(); + } } }); menu.add(item); - item = new JMenuItem("Open...",KeyEvent.VK_O); + //// Open... + item = new JMenuItem(trans.get("main.menu.file.open"), KeyEvent.VK_O); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK)); - item.getAccessibleContext().setAccessibleDescription("Open a rocket design"); + //// Open a rocket design + item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Openrocketdesign")); item.setIcon(Icons.FILE_OPEN); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Open... selected"); openAction(); } }); menu.add(item); - item = new JMenuItem("Open example..."); - item.getAccessibleContext().setAccessibleDescription("Open an example rocket design"); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, + //// Open example... + item = new JMenuItem(trans.get("main.menu.file.openExample")); + //// Open an example rocket design + item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Openexamplerocketdesign")); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK)); item.setIcon(Icons.FILE_OPEN_EXAMPLE); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Open example... selected"); URL[] urls = ExampleDesignDialog.selectExampleDesigns(BasicFrame.this); if (urls != null) { - for (URL u: urls) { + for (URL u : urls) { + log.user("Opening example " + u); open(u, BasicFrame.this); } } @@ -438,91 +462,123 @@ public class BasicFrame extends JFrame { menu.addSeparator(); - item = new JMenuItem("Save",KeyEvent.VK_S); + //// Save + item = new JMenuItem(trans.get("main.menu.file.save"), KeyEvent.VK_S); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)); - item.getAccessibleContext().setAccessibleDescription("Save the current rocket design"); + //// Save the current rocket design + item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.SavecurRocketdesign")); item.setIcon(Icons.FILE_SAVE); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Save selected"); saveAction(); } }); menu.add(item); - item = new JMenuItem("Save as...",KeyEvent.VK_A); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, + //// Save as... + item = new JMenuItem(trans.get("main.menu.file.saveAs"), KeyEvent.VK_A); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK)); - item.getAccessibleContext().setAccessibleDescription("Save the current rocket design "+ - "to a new file"); + //// Save the current rocket design to a new file + item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.SavecurRocketdesnewfile")); item.setIcon(Icons.FILE_SAVE_AS); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Save as... selected"); saveAsAction(); } }); menu.add(item); -// menu.addSeparator(); - menu.add(new JSeparator()); + //// Print... + item = new JMenuItem(trans.get("main.menu.file.print"), KeyEvent.VK_P); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK)); + //// Print parts list and fin template + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.print.desc")); + item.setIcon(Icons.FILE_PRINT); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("Print action selected"); + printAction(); + } + }); + menu.add(item); + + + menu.addSeparator(); - item = new JMenuItem("Close",KeyEvent.VK_C); + //// Close + item = new JMenuItem(trans.get("main.menu.file.close"), KeyEvent.VK_C); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK)); - item.getAccessibleContext().setAccessibleDescription("Close the current rocket design"); + //// Close the current rocket design + item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Closedesign")); item.setIcon(Icons.FILE_CLOSE); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Close selected"); closeAction(); } }); menu.add(item); menu.addSeparator(); - - item = new JMenuItem("Quit",KeyEvent.VK_Q); + + //// Quit + item = new JMenuItem(trans.get("main.menu.file.quit"), KeyEvent.VK_Q); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK)); - item.getAccessibleContext().setAccessibleDescription("Quit the program"); + //// Quit the program + item.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.item.Quitprogram")); item.setIcon(Icons.FILE_QUIT); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Quit selected"); quitAction(); } }); menu.add(item); - + //// Edit - menu = new JMenu("Edit"); + menu = new JMenu(trans.get("main.menu.edit")); menu.setMnemonic(KeyEvent.VK_E); - menu.getAccessibleContext().setAccessibleDescription("Rocket editing"); + //// Rocket editing + menu.getAccessibleContext().setAccessibleDescription(trans.get("BasicFrame.menu.Rocketedt")); menubar.add(menu); - + Action action = document.getUndoAction(); item = new JMenuItem(action); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK)); item.setMnemonic(KeyEvent.VK_U); - item.getAccessibleContext().setAccessibleDescription("Undo the previous operation"); - + //// Undo the previous operation + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.undo.desc")); + menu.add(item); - + action = document.getRedoAction(); item = new JMenuItem(action); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK)); item.setMnemonic(KeyEvent.VK_R); - item.getAccessibleContext().setAccessibleDescription("Redo the previously undone " + - "operation"); + //// Redo the previously undone operation + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.redo.desc")); menu.add(item); menu.addSeparator(); - + item = new JMenuItem(actions.getCutAction()); menu.add(item); - + item = new JMenuItem(actions.getCopyAction()); menu.add(item); - + item = new JMenuItem(actions.getPasteAction()); menu.add(item); @@ -531,106 +587,177 @@ public class BasicFrame extends JFrame { menu.addSeparator(); - item = new JMenuItem("Preferences"); + + + item = new JMenuItem(trans.get("main.menu.edit.resize")); + item.setIcon(Icons.EDIT_SCALE); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.resize.desc")); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("Scale... selected"); + ScaleDialog dialog = new ScaleDialog(document, getSelectedComponent(), BasicFrame.this); + dialog.setVisible(true); + dialog.dispose(); + } + }); + menu.add(item); + + + + //// Preferences + item = new JMenuItem(trans.get("main.menu.edit.preferences")); item.setIcon(Icons.PREFERENCES); - item.getAccessibleContext().setAccessibleDescription("Setup the application "+ - "preferences"); + //// Setup the application preferences + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.preferences.desc")); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { - PreferencesDialog.showPreferences(); + log.user("Preferences selected"); + PreferencesDialog.showPreferences(BasicFrame.this); } }); menu.add(item); - + //// Analyze - menu = new JMenu("Analyze"); + menu = new JMenu(trans.get("main.menu.analyze")); menu.setMnemonic(KeyEvent.VK_A); - menu.getAccessibleContext().setAccessibleDescription("Analyzing the rocket"); + //// Analyzing the rocket + menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.desc")); menubar.add(menu); - item = new JMenuItem("Component analysis",KeyEvent.VK_C); - item.getAccessibleContext().setAccessibleDescription("Analyze the rocket components " + - "separately"); + //// Component analysis + item = new JMenuItem(trans.get("main.menu.analyze.componentAnalysis"), KeyEvent.VK_C); + //// Analyze the rocket components separately + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.componentAnalysis.desc")); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Component analysis selected"); ComponentAnalysisDialog.showDialog(rocketpanel); } }); menu.add(item); + + item = new JMenuItem(trans.get("main.menu.analyze.optimization"), KeyEvent.VK_O); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.optimization.desc")); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("Rocket optimization selected"); + new GeneralOptimizationDialog(document, BasicFrame.this).setVisible(true); + } + }); + menu.add(item); + + //// Debug // (shown if openrocket.debug.menu is defined) if (System.getProperty("openrocket.debug.menu") != null) { menubar.add(makeDebugMenu()); } - - + + //// Help - menu = new JMenu("Help"); + menu = new JMenu(trans.get("main.menu.help")); menu.setMnemonic(KeyEvent.VK_H); - menu.getAccessibleContext().setAccessibleDescription("Information about OpenRocket"); + menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.desc")); menubar.add(menu); - - - item = new JMenuItem("License",KeyEvent.VK_L); - item.getAccessibleContext().setAccessibleDescription("OpenRocket license information"); + + //// License + item = new JMenuItem(trans.get("main.menu.help.license"), KeyEvent.VK_L); + item.setIcon(Icons.HELP_LICENSE); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.license.desc")); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("License selected"); new LicenseDialog(BasicFrame.this).setVisible(true); } }); menu.add(item); - item = new JMenuItem("Bug report",KeyEvent.VK_B); - item.getAccessibleContext().setAccessibleDescription("Information about reporting " + - "bugs in OpenRocket"); + menu.addSeparator(); + + //// Bug report + item = new JMenuItem(trans.get("main.menu.help.bugReport"), KeyEvent.VK_B); + item.setIcon(Icons.HELP_BUG_REPORT); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.bugReport.desc")); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { -// new BugDialog(BasicFrame.this).setVisible(true); + log.user("Bug report selected"); BugReportDialog.showBugReportDialog(BasicFrame.this); } }); menu.add(item); - item = new JMenuItem("About",KeyEvent.VK_A); - item.getAccessibleContext().setAccessibleDescription("About OpenRocket"); + //// Debug log + item = new JMenuItem(trans.get("main.menu.help.debugLog")); + item.setIcon(Icons.HELP_DEBUG_LOG); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.debugLog.desc")); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { - new AboutDialog(BasicFrame.this).setVisible(true); + log.user("Debug log selected"); + new DebugLogDialog(BasicFrame.this).setVisible(true); } }); menu.add(item); + menu.addSeparator(); + + //// About + item = new JMenuItem(trans.get("main.menu.help.about"), KeyEvent.VK_A); + item.setIcon(Icons.HELP_ABOUT); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.about.desc")); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("About selected"); + new AboutDialog(BasicFrame.this).setVisible(true); + } + }); + menu.add(item); + this.setJMenuBar(menubar); } - private JMenu makeDebugMenu() { JMenu menu; JMenuItem item; + /* + * This menu is intentionally left untranslated. + */ + //// Debug menu menu = new JMenu("Debug"); + //// OpenRocket debugging tasks menu.getAccessibleContext().setAccessibleDescription("OpenRocket debugging tasks"); + //// What is this menu? item = new JMenuItem("What is this menu?"); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("What is this menu? selected"); 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." }, + "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); } }); @@ -638,17 +765,19 @@ public class BasicFrame extends JFrame { menu.addSeparator(); + //// Create test rocket item = new JMenuItem("Create test rocket"); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + log.user("Create test rocket selected"); 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" + }, "Generate random test rocket", JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, null, new Object[] { + "Random", "OK" }, "OK"); Rocket r; @@ -668,11 +797,120 @@ public class BasicFrame extends JFrame { }); menu.add(item); + + + item = new JMenuItem("Create 'Iso-Haisu'"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("Create Iso-Haisu selected"); + 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) { + log.user("Create Big Blue selected"); + 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("Memory statistics"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("Memory statistics selected"); + + // Get discarded but remaining objects (this also runs System.gc multiple times) + List objects = MemoryManagement.getRemainingCollectableObjects(); + StringBuilder sb = new StringBuilder(); + sb.append("Objects that should have been garbage-collected but have not been:\n"); + int count = 0; + for (MemoryData data : objects) { + Object o = data.getReference().get(); + if (o == null) + continue; + sb.append("Age ").append(System.currentTimeMillis() - data.getRegistrationTime()) + .append(" ms: ").append(o).append('\n'); + count++; + // Explicitly null the strong reference to avoid possibility of invisible references + o = null; + } + sb.append("Total: " + count); + + // Get basic memory stats + System.gc(); + long max = Runtime.getRuntime().maxMemory(); + long free = Runtime.getRuntime().freeMemory(); + long used = max - free; + String[] stats = new String[4]; + stats[0] = "Memory usage:"; + stats[1] = String.format(" Max memory: %.1f MB", max / 1024.0 / 1024.0); + stats[2] = String.format(" Used memory: %.1f MB (%.0f%%)", used / 1024.0 / 1024.0, 100.0 * used / max); + stats[3] = String.format(" Free memory: %.1f MB (%.0f%%)", free / 1024.0 / 1024.0, 100.0 * free / max); + + + DetailDialog.showDetailedMessageDialog(BasicFrame.this, stats, sb.toString(), + "Memory statistics", JOptionPane.INFORMATION_MESSAGE); + } + }); + menu.add(item); + + //// Exhaust memory + item = new JMenuItem("Exhaust memory"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("Exhaust memory selected"); + LinkedList data = new LinkedList(); + int count = 0; + final int bytesPerArray = 10240; + try { + while (true) { + byte[] array = new byte[bytesPerArray]; + for (int i = 0; i < bytesPerArray; i++) { + array[i] = (byte) i; + } + data.add(array); + count++; + } + } catch (OutOfMemoryError error) { + data = null; + long size = bytesPerArray * (long) count; + String s = String.format("OutOfMemory occurred after %d iterations (approx. %.1f MB consumed)", + count, size / 1024.0 / 1024.0); + log.debug(s, error); + JOptionPane.showMessageDialog(BasicFrame.this, s); + } + } + }); + menu.add(item); + + menu.addSeparator(); + //// Exception here item = new JMenuItem("Exception here"); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Exception here selected"); throw new RuntimeException("Testing exception from menu action listener"); } }); @@ -680,7 +918,9 @@ public class BasicFrame extends JFrame { item = new JMenuItem("Exception from EDT"); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Exception from EDT selected"); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -694,25 +934,56 @@ public class BasicFrame extends JFrame { item = new JMenuItem("Exception from other thread"); item.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + log.user("Exception from other thread selected"); new Thread() { @Override public void run() { - throw new RuntimeException("Testing exception from " + - "newly created thread"); + throw new RuntimeException("Testing exception from newly created thread"); } }.start(); } }); menu.add(item); + item = new JMenuItem("OutOfMemoryError here"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("OutOfMemoryError here selected"); + throw new OutOfMemoryError("Testing OutOfMemoryError from menu action listener"); + } + }); + menu.add(item); + + + menu.addSeparator(); + + item = new JMenuItem("Test popup"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.user("Test popup selected"); + JPanel panel = new JPanel(); + panel.add(new JTextField(40)); + panel.add(new JSpinner()); + JPopupMenu popup = new JPopupMenu(); + popup.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); + popup.add(panel); + popup.show(BasicFrame.this, -50, 100); + } + }); + menu.add(item); + + + return menu; } - /** * Select the tab on the main pane. * @@ -721,51 +992,74 @@ public class BasicFrame extends JFrame { public void selectTab(int tab) { tabbedPane.setSelectedIndex(tab); } - + private void openAction() { - JFileChooser chooser = new JFileChooser(); - chooser.setFileFilter(ROCKET_DESIGN_FILTER); - chooser.setMultiSelectionEnabled(true); - chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); - if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) - return; - - Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); - - File[] files = chooser.getSelectedFiles(); - - for (File file: files) { - System.out.println("Opening file: " + file); - if (open(file, this)) { - - // Close previous window if replacing - if (replaceable && document.isSaved()) { - closeAction(); - replaceable = false; - } - } - } + JFileChooser chooser = new JFileChooser(); + + chooser.addChoosableFileFilter(FileHelper.ALL_DESIGNS_FILTER); + chooser.addChoosableFileFilter(FileHelper.OPENROCKET_DESIGN_FILTER); + chooser.addChoosableFileFilter(FileHelper.ROCKSIM_DESIGN_FILTER); + chooser.setFileFilter(FileHelper.ALL_DESIGNS_FILTER); + + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + chooser.setMultiSelectionEnabled(true); + chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); + int option = chooser.showOpenDialog(this); + if (option != JFileChooser.APPROVE_OPTION) { + log.user("Decided not to open files, option=" + option); + return; + } + + Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); + + File[] files = chooser.getSelectedFiles(); + log.user("Opening files " + Arrays.toString(files)); + + for (File file : files) { + log.info("Opening file: " + file); + if (open(file, this)) { + + // Close previous window if replacing + if (replaceable && document.isSaved()) { + // We are replacing the frame, make new window have current location + BasicFrame newFrame = frames.get(frames.size() - 1); + newFrame.setLocation(this.getLocation()); + + log.info("Closing window because it is replaceable"); + closeAction(); + replaceable = false; + } + } + } } - - + /** + * Open a file based on a URL. + * @param url the file to open. + * @param parent the parent window for dialogs. + * @return true if opened successfully. + */ private static boolean open(URL url, BasicFrame parent) { String filename = null; + // First figure out the file name from the URL + // Try using URI.getPath(); try { URI uri = url.toURI(); filename = uri.getPath(); - } catch (URISyntaxException ignore) { } - + } catch (URISyntaxException ignore) { + } + // Try URL-decoding the URL if (filename == null) { try { filename = URLDecoder.decode(url.toString(), "UTF-8"); - } catch (UnsupportedEncodingException ignore) { } + } catch (UnsupportedEncodingException ignore) { + } } // Last resort @@ -775,20 +1069,24 @@ public class BasicFrame extends JFrame { // Remove path from filename if (filename.lastIndexOf('/') >= 0) { - filename = filename.substring(filename.lastIndexOf('/')+1); + filename = filename.substring(filename.lastIndexOf('/') + 1); } + + // Open the file + log.info("Opening file from url=" + url + " filename=" + filename); 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; - } + // Close previous window if replacing + if (parent.replaceable && parent.document.isSaved()) { + parent.closeAction(); + parent.replaceable = false; + } } } catch (IOException e) { - JOptionPane.showMessageDialog(parent, + log.warn("Error opening file" + e); + JOptionPane.showMessageDialog(parent, "An error occurred while opening the file " + filename, "Error loading file", JOptionPane.ERROR_MESSAGE); } @@ -811,7 +1109,7 @@ public class BasicFrame extends JFrame { 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. @@ -820,12 +1118,12 @@ public class BasicFrame extends JFrame { * @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) { + public 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. * @@ -835,236 +1133,271 @@ public class BasicFrame extends JFrame { * @param parent * @return */ - private static boolean open(OpenFileWorker worker, String filename, File file, - Window parent) { - + private static boolean open(OpenFileWorker worker, String filename, File file, Window parent) { + + MotorDatabaseLoadingDialog.check(parent); + // Open the file in a Swing worker thread - if (!SwingWorkerDialog.runWorker(parent, "Opening file", - "Reading " + filename + "...", worker)) { - + log.info("Starting OpenFileWorker"); + if (!SwingWorkerDialog.runWorker(parent, "Opening file", "Reading " + filename + "...", worker)) { // User cancelled the operation + log.info("User cancelled the OpenFileWorker"); 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, + + log.warn("File not found", cause); + 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(), + + log.warn("Error loading the file", cause); + JOptionPane.showMessageDialog(parent, + "Unable to open file '" + filename + "': " + + cause.getMessage(), "Error opening file", JOptionPane.ERROR_MESSAGE); return false; - + } else { - - throw new RuntimeException("Unknown error when opening file", e); - + + throw new BugException("Unknown error when opening file", e); + } - + } catch (InterruptedException e) { - throw new RuntimeException("EDT was interrupted", e); + throw new BugException("EDT was interrupted", e); } if (doc == null) { - throw new RuntimeException("BUG: Document loader returned null"); + throw new BugException("Document loader returned null"); } - - // Show warnings + + // Show warnings WarningSet warnings = worker.getRocketLoader().getWarnings(); if (!warnings.isEmpty()) { + log.info("Warnings while reading file: " + warnings); WarningDialog.showWarnings(parent, new Object[] { - "The following problems were encountered while opening " + filename + ".", - "Some design features may not have been loaded correctly." + //// The following problems were encountered while opening + trans.get("BasicFrame.WarningDialog.txt1") + " " + filename + ".", + //// Some design features may not have been loaded correctly. + trans.get("BasicFrame.WarningDialog.txt2") }, - "Warnings while opening file", warnings); + //// Warnings while opening file + trans.get("BasicFrame.WarningDialog.title"), warnings); } - - // Set document state - doc.setFile(file); - doc.setSaved(true); - // Open the frame - BasicFrame frame = new BasicFrame(doc); - frame.setVisible(true); + // Set document state + doc.setFile(file); + doc.setSaved(true); + - return true; + // Open the frame + log.debug("Opening new frame with the document"); + BasicFrame frame = new BasicFrame(doc); + frame.setVisible(true); + + return true; } - - - - - - - + + + private boolean saveAction() { File file = document.getFile(); - if (file==null) { + if (file == null) { + log.info("Document does not contain file, opening save as dialog instead"); return saveAsAction(); - } else { - return saveAs(file); } + log.info("Saving document to " + file); + + // Saving RockSim designs is not supported + if (FileHelper.ROCKSIM_DESIGN_FILTER.accept(file)) { + file = new File(file.getAbsolutePath().replaceAll(".[rR][kK][tT](.[gG][zZ])?$", + ".ork")); + + log.info("Attempting to save in RockSim format, renaming to " + file); + 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) { + log.user("User chose not to save"); + return false; + } + + document.setFile(file); + } + return saveAs(file); } private boolean saveAsAction() { File file = null; - while (file == null) { - StorageOptionChooser storageChooser = + + // TODO: HIGH: what if *.rkt chosen? + StorageOptionChooser storageChooser = new StorageOptionChooser(document, document.getDefaultStorageOptions()); - JFileChooser chooser = new JFileChooser(); - chooser.setFileFilter(ROCKET_DESIGN_FILTER); - chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); - chooser.setAccessory(storageChooser); - if (document.getFile() != null) - chooser.setSelectedFile(document.getFile()); - - if (chooser.showSaveDialog(BasicFrame.this) != JFileChooser.APPROVE_OPTION) - return false; - - file = chooser.getSelectedFile(); - if (file == null) - return false; - - Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); - storageChooser.storeOptions(document.getDefaultStorageOptions()); - - if (file.getName().indexOf('.') < 0) { - String name = file.getAbsolutePath(); - name = name + ".ork"; - file = new File(name); - } - - if (file.exists()) { - int result = JOptionPane.showConfirmDialog(this, - "File '"+file.getName()+"' exists. Do you want to overwrite it?", - "File exists", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (result != JOptionPane.YES_OPTION) - return false; - } + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(FileHelper.OPENROCKET_DESIGN_FILTER); + chooser.setCurrentDirectory(Prefs.getDefaultDirectory()); + chooser.setAccessory(storageChooser); + if (document.getFile() != null) + chooser.setSelectedFile(document.getFile()); + + int option = chooser.showSaveDialog(BasicFrame.this); + if (option != JFileChooser.APPROVE_OPTION) { + log.user("User decided not to save, option=" + option); + return false; + } + + file = chooser.getSelectedFile(); + if (file == null) { + log.user("User did not select a file"); + return false; } - saveAs(file); - return true; + + Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); + storageChooser.storeOptions(document.getDefaultStorageOptions()); + + file = FileHelper.ensureExtension(file, "ork"); + if (!FileHelper.confirmWrite(file, this)) { + return false; + } + + return saveAs(file); } - private boolean saveAs(File file) { - System.out.println("Saving to file: " + file.getName()); - boolean saved = false; - - if (!StorageOptionChooser.verifyStorageOptions(document, this)) { - // User cancelled the dialog - return false; - } - - - 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; - } - - try { + log.info("Saving document as " + file); + boolean saved = false; + + if (!StorageOptionChooser.verifyStorageOptions(document, this)) { + // User cancelled the dialog + log.user("User cancelled saving in storage options dialog"); + return false; + } + + + SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER); + + if (!SwingWorkerDialog.runWorker(this, "Saving file", + "Writing " + file.getName() + "...", worker)) { + + // User cancelled the save + log.user("User cancelled the save, deleting the file"); + file.delete(); + return false; + } + + try { worker.get(); document.setFile(file); document.setSaved(true); saved = true; - setTitle(); + 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; + log.warn("An I/O error occurred while saving " + file, cause); + JOptionPane.showMessageDialog(this, new String[] { + "An I/O error occurred while saving:", + e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE); + return false; } else { - throw new RuntimeException("Unknown error when saving file", e); + Reflection.handleWrappedException(e); } } catch (InterruptedException e) { - throw new RuntimeException("EDT was interrupted", e); + throw new BugException("EDT was interrupted", e); } - - return saved; + + return saved; } private boolean closeAction() { if (!document.isSaved()) { + log.info("Confirming whether to save the design"); ComponentConfigDialog.hideDialog(); - int result = JOptionPane.showConfirmDialog(this, - "Design '"+rocket.getName()+"' has not been saved. " + - "Do you want to save it?", - "Design not saved", JOptionPane.YES_NO_CANCEL_OPTION, + int result = JOptionPane.showConfirmDialog(this, + trans.get("BasicFrame.dlg.lbl1") + rocket.getName() + + trans.get("BasicFrame.dlg.lbl2") + " " + + trans.get("BasicFrame.dlg.lbl3"), + trans.get("BasicFrame.dlg.title"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (result == JOptionPane.YES_OPTION) { // Save - if (!saveAction()) - return false; // If save was interrupted + log.user("User requested file save"); + if (!saveAction()) { + log.info("File save was interrupted, not closing"); + return false; + } } else if (result == JOptionPane.NO_OPTION) { // Don't save: No-op + log.user("User requested to discard design"); } else { // Cancel or close + log.user("User cancelled closing, result=" + result); return false; } } // Rocket has been saved or discarded + log.debug("Disposing window"); this.dispose(); - - // TODO: LOW: Close only dialogs that have this frame as their parent + ComponentConfigDialog.hideDialog(); ComponentAnalysisDialog.hideDialog(); frames.remove(this); - if (frames.isEmpty()) + if (frames.isEmpty()) { + log.info("Last frame closed, exiting"); System.exit(0); + } return true; } + /** - * Closes this frame if it is replaceable. + * */ - public void closeIfReplaceable() { - if (this.replaceable && document.isSaved()) { - closeAction(); - } + public void printAction() { + new PrintDialog(this, document).setVisible(true); } /** * Open a new design window with a basic rocket+stage. */ public static void newAction() { + log.info("New action initiated"); + Rocket rocket = new Rocket(); Stage stage = new Stage(); - stage.setName("Sustainer"); + //// Sustainer + stage.setName(trans.get("BasicFrame.StageName.Sustainer")); rocket.addChild(stage); OpenRocketDocument doc = new OpenRocketDocument(rocket); doc.setSaved(true); @@ -1079,13 +1412,17 @@ public class BasicFrame extends JFrame { * Quit the application. Confirms saving unsaved designs. The action of File->Quit. */ public static void quitAction() { - for (int i=frames.size()-1; i>=0; i--) { + log.info("Quit action initiated"); + for (int i = frames.size() - 1; i >= 0; i--) { + log.debug("Closing frame " + frames.get(i)); if (!frames.get(i).closeAction()) { // Close canceled + log.info("Quit was cancelled"); return; } } // Should not be reached, but just in case + log.error("Should already have exited application"); System.exit(0); } @@ -1100,8 +1437,8 @@ public class BasicFrame extends JFrame { String title; title = rocket.getName(); - if (file!=null) { - title = title + " ("+file.getName()+")"; + if (file != null) { + title = title + " (" + file.getName() + ")"; } if (!saved) title = "*" + title; @@ -1110,7 +1447,7 @@ 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. @@ -1119,10 +1456,13 @@ public class BasicFrame extends JFrame { * @return the corresponding BasicFrame, or null if none found. */ public static BasicFrame findFrame(Rocket rocket) { - for (BasicFrame f: frames) { - if (f.rocket == rocket) + for (BasicFrame f : frames) { + if (f.rocket == rocket) { + log.debug("Found frame " + f + " for rocket " + rocket); return f; + } } + log.debug("Could not find frame for rocket " + rocket); return null; } @@ -1134,167 +1474,11 @@ public class BasicFrame extends JFrame { * @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(final String[] args) { - - // Run the actual startup method in the EDT since it can use progress dialogs etc. - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - runMain(args); - } - }); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - - } - - - private static void runMain(String[] args) { - - // Start update info fetching - final UpdateInfoRetriever updateInfo; - if (Prefs.getCheckUpdates()) { - updateInfo = new UpdateInfoRetriever(); - updateInfo.start(); + BasicFrame frame = findFrame(rocket); + if (frame != null) { + return frame.document; } else { - updateInfo = null; - } - - - - /* - * 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. - */ - try { - // Set system L&F - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - - // Check whether we have an ugly L&F - LookAndFeel laf = UIManager.getLookAndFeel(); - if (laf == null || - laf.getName().matches(".*[mM][oO][tT][iI][fF].*") || - laf.getName().matches(".*[mM][eE][tT][aA][lL].*")) { - - // Search for better LAF - UIManager.LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels(); - String lafNames[] = { - ".*[gG][tT][kK].*", - ".*[wW][iI][nN].*", - ".*[mM][aA][cC].*", - ".*[aA][qQ][uU][aA].*", - ".*[nN][iI][mM][bB].*" - }; - - lf: for (String lafName: lafNames) { - for (UIManager.LookAndFeelInfo l: info) { - if (l.getName().matches(lafName)) { - UIManager.setLookAndFeel(l.getClassName()); - break lf; - } - } - } - } - } catch (Exception e) { - System.err.println("Error setting LAF: " + e); + return null; } - - // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively. - ToolTipManager.sharedInstance().setDismissDelay(30000); - - - // Setup the uncaught exception handler - ExceptionHandler.registerExceptionHandler(); - - - // Load defaults - Prefs.loadDefaultUnits(); - - - // Load motors etc. - Databases.fakeMethod(); - - // Starting action (load files or open new document) - if (!handleCommandLine(args)) { - newAction(); - } - - - // Check whether update info has been fetched or whether it needs more time - checkUpdateStatus(updateInfo); } - - - private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) { - if (updateInfo == null) - return; - - int delay = 1000; - if (!updateInfo.isRunning()) - delay = 100; - - final Timer timer = new Timer(delay, null); - - ActionListener listener = new ActionListener() { - private int count = 5; - @Override - public void actionPerformed(ActionEvent e) { - if (!updateInfo.isRunning()) { - timer.stop(); - - String current = Prefs.getVersion(); - String last = Prefs.getString(Prefs.LAST_UPDATE, ""); - - UpdateInfo info = updateInfo.getUpdateInfo(); - if (info != null && info.getLatestVersion() != null && - !current.equals(info.getLatestVersion()) && - !last.equals(info.getLatestVersion())) { - Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion()); - new UpdateInfoDialog(info).setVisible(true); - } - } - count--; - if (count <= 0) - timer.stop(); - } - }; - timer.addActionListener(listener); - timer.start(); - } - - - /** - * Handles arguments passed from the command line. This may be used either - * when starting the first instance of OpenRocket or later when OpenRocket is - * executed again while running. - * - * @param args the command-line arguments. - * @return whether a new frame was opened or similar user desired action was - * performed as a result. - */ - public static boolean handleCommandLine(String[] args) { - - // Check command-line for files - boolean opened = false; - for (String file: args) { - if (open(new File(file), null)) { - opened = true; - } - } - return opened; - } - }