1964 lines
63 KiB
Java

package net.sf.openrocket.gui.main;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
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.UnsupportedEncodingException;
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.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
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.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.event.ChangeEvent;
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.file.wavefrontobj.export.OBJExporterFactory;
import net.sf.openrocket.gui.configdialog.SaveDesignInfoPanel;
import net.sf.openrocket.gui.dialogs.ErrorWarningDialog;
import net.sf.openrocket.logging.ErrorSet;
import net.sf.openrocket.logging.WarningSet;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.OpenRocketDocumentFactory;
import net.sf.openrocket.document.StorageOptions;
import net.sf.openrocket.document.StorageOptions.FileType;
import net.sf.openrocket.document.events.DocumentChangeEvent;
import net.sf.openrocket.document.events.DocumentChangeListener;
import net.sf.openrocket.file.GeneralRocketSaver;
import net.sf.openrocket.file.RocketLoadException;
import net.sf.openrocket.file.rasaero.RASAeroCommonConstants;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
import net.sf.openrocket.gui.customexpression.CustomExpressionDialog;
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.DecalNotFoundDialog;
import net.sf.openrocket.gui.dialogs.DetailDialog;
import net.sf.openrocket.gui.dialogs.LicenseDialog;
import net.sf.openrocket.gui.dialogs.PrintDialog;
import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
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.figure3d.photo.PhotoFrame;
import net.sf.openrocket.gui.help.tours.GuidedTourSelectionDialog;
import net.sf.openrocket.gui.main.componenttree.ComponentTree;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
import net.sf.openrocket.gui.util.DummyFrameMenuOSX;
import net.sf.openrocket.gui.util.FileHelper;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.gui.util.OpenFileWorker;
import net.sf.openrocket.gui.util.SaveFileWorker;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.gui.util.URLUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.Markers;
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.startup.Application;
import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.DecalNotFoundException;
import net.sf.openrocket.util.MemoryManagement;
import net.sf.openrocket.util.MemoryManagement.MemoryData;
import net.sf.openrocket.util.Reflection;
import net.sf.openrocket.util.TestRockets;
import net.sf.openrocket.utils.ComponentPresetEditor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
public class BasicFrame extends JFrame {
private static final long serialVersionUID = 948877655223365313L;
private static final Logger log = LoggerFactory.getLogger(BasicFrame.class);
private static final GeneralRocketSaver ROCKET_SAVER = new GeneralRocketSaver();
private static final Translator trans = Application.getTranslator();
private static final Preferences prefs = Application.getPreferences();
public static final int SHORTCUT_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
public static final int SHIFT_SHORTCUT_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() |
SHIFT_DOWN_MASK;
public static final int DESIGN_TAB = 0;
public static final int FLIGHT_CONFIGURATION_TAB = 1;
public static final int SIMULATION_TAB = 2;
private int previousTab = DESIGN_TAB;
/**
* List of currently open frames. When the list goes empty
* it is time to exit the application.
*/
private static final List<BasicFrame> frames = new ArrayList<BasicFrame>();
private static BasicFrame startupFrame = null; // the frame that was created at startup
/**
* 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;
private JTabbedPane tabbedPane;
private RocketPanel rocketpanel;
private ComponentTree tree = null;
private final JPopupMenu popupMenu;
private final DocumentSelectionModel selectionModel;
private final TreeSelectionModel componentSelectionModel;
private final ListSelectionModel simulationSelectionModel;
/** Actions available for rocket modifications */
private final RocketActions actions;
private final DesignPanel designPanel;
private final FlightConfigurationPanel flightConfigurationPanel;
private final SimulationPanel simulationPanel;
public static BasicFrame lastFrameInstance = null; // Latest BasicFrame that was created
private static boolean quitCalled = false; // Keeps track whether the quit action has been called
/**
* Sole constructor. Creates a new frame based on the supplied document
* and adds it to the current frames list.
*
* @param document the document to show.
*/
public BasicFrame(OpenRocketDocument document) {
log.debug("Instantiating new BasicFrame");
this.document = document;
this.rocket = document.getRocket();
BasicFrame.lastFrameInstance = this;
// Create the component tree selection model that will be used
componentSelectionModel = new DefaultTreeSelectionModel();
componentSelectionModel.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
// ----- Create the different BasicFrame panels -----
log.debug("Constructing the BasicFrame UI");
//// Top segment, tabbed pane
simulationPanel = new SimulationPanel(document);
{
// Obtain the simulation selection model that will be used
simulationSelectionModel = simulationPanel.getSimulationListSelectionModel();
// Combine into a DocumentSelectionModel
selectionModel = new DocumentSelectionModel(document);
selectionModel.attachComponentTreeSelectionModel(componentSelectionModel);
selectionModel.attachSimulationListSelectionModel(simulationSelectionModel);
// Create RocketActions
actions = new RocketActions(document, selectionModel, this, simulationPanel);
}
{
// Create the component tree
tree = new ComponentTree(document);
tree.setSelectionModel(componentSelectionModel);
}
designPanel = new DesignPanel(this, document, tree);
flightConfigurationPanel = new FlightConfigurationPanel(this, document);
tabbedPane = new JTabbedPane();
tabbedPane.addTab(trans.get("BasicFrame.tab.Rocketdesign"), null, designPanel);
tabbedPane.addTab(trans.get("BasicFrame.tab.Flightconfig"), null, flightConfigurationPanel);
tabbedPane.addTab(trans.get("BasicFrame.tab.Flightsim"), null, simulationPanel);
// Add change listener to catch when the tabs are changed. This is to run simulations
// automatically when the simulation tab is selected.
tabbedPane.addChangeListener(new BasicFrame_changeAdapter(this));
//// Bottom segment, rocket figure
rocketpanel = new RocketPanel(document, this);
rocketpanel.setSelectionModel(tree.getSelectionModel());
//// The main vertical split pane
JSplitPane vertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
vertical.setResizeWeight(0.5);
vertical.setTopComponent(tabbedPane);
vertical.setBottomComponent(rocketpanel);
this.add(vertical);
// Populate the popup menu
{
popupMenu = new JPopupMenu();
popupMenu.add(actions.getEditAction());
popupMenu.add(actions.getCutAction());
popupMenu.add(actions.getCopyAction());
popupMenu.add(actions.getPasteAction());
popupMenu.add(actions.getDuplicateAction());
popupMenu.add(actions.getDeleteAction());
popupMenu.addSeparator();
JMenu selectMenu = new JMenu(trans.get("RocketActions.Select"));
selectMenu.add(actions.getSelectSameColorAction());
selectMenu.add(actions.getDeselectAllAction());
popupMenu.add(selectMenu);
popupMenu.addSeparator();
popupMenu.add(actions.getScaleAction());
}
createMenu();
rocket.addComponentChangeListener(new ComponentChangeListener() {
@Override
public void componentChanged(ComponentChangeEvent e) {
setTitle();
}
});
document.addDocumentChangeListener(new DocumentChangeListener() {
@Override
public void documentChanged(DocumentChangeEvent e) {
setTitle();
}
});
setTitle();
this.pack();
// Set initial window size
Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
size.width = size.width * 9 / 10;
size.height = size.height * 9 / 10;
this.setSize(size);
// Remember changed size
GUIUtil.rememberWindowSize(this);
this.setLocationByPlatform(true);
GUIUtil.rememberWindowPosition(this);
GUIUtil.setWindowIcons(this);
this.validate();
vertical.setDividerLocation(0.4);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
closeAction();
}
});
frames.add(this);
if( componentSelectionModel.isSelectionEmpty() ){
final Rocket rocket = document.getRocket();
if( rocket != null ) {
final RocketComponent topStage = rocket.getChild(0);
if (topStage != null) {
final TreePath selectionPath = new TreePath(topStage);
componentSelectionModel.setSelectionPath(selectionPath);
tree.setSelectionRow(1);
// Don't select children components at startup (so override the default behavior with this new selection)
rocketpanel.getFigure().setSelection(new RocketComponent[] { topStage });
rocketpanel.getFigure3d().setSelection(new RocketComponent[] { topStage });
log.debug("... Setting Initial Selection: " + tree.getSelectionPath() );
}
}
}
log.debug("BasicFrame instantiation complete");
}
/**
* Return the currently selected rocket component, or <code>null</code> if none selected.
*/
private RocketComponent getSelectedComponent() {
TreePath path = componentSelectionModel.getSelectionPath();
if (path == null)
return null;
tree.scrollPathToVisible(path);
return (RocketComponent) path.getLastPathComponent();
}
/**
* Return the currently selected rocket component, or <code>null</code> if none selected.
*/
private List<RocketComponent> getSelectedComponents() {
TreePath[] paths = componentSelectionModel.getSelectionPaths();
if (paths == null || paths.length == 0)
return null;
List<RocketComponent> result = new LinkedList<>();
for (TreePath path : paths) {
tree.scrollPathToVisible(path);
RocketComponent component = (RocketComponent) path.getLastPathComponent();
result.add(component);
}
return result;
}
public RocketPanel getRocketPanel() {
return rocketpanel;
}
/**
* Creates the menu for the window.
*/
private void createMenu() {
JMenuBar menubar = new JMenuBar();
JMenu fileMenu;
JMenuItem item;
// File
fileMenu = new JMenu(trans.get("main.menu.file"));
fileMenu.setMnemonic(KeyEvent.VK_F);
fileMenu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.desc"));
menubar.add(fileMenu);
//// New etc.
addFileCreateAndOpenMenuItems(fileMenu, this);
// ------------------------------------------------------------------------------------------
fileMenu.addSeparator();
//// Save
item = new JMenuItem(trans.get("main.menu.file.save"), KeyEvent.VK_S);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, SHORTCUT_KEY));
//// Save the current rocket design
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.save.desc"));
item.setIcon(Icons.FILE_SAVE);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Save selected");
saveAction();
}
});
fileMenu.add(item);
//// Save as...
item = new JMenuItem(trans.get("main.menu.file.saveAs"), KeyEvent.VK_A);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
SHORTCUT_KEY | ActionEvent.SHIFT_MASK));
//// Save the current rocket design to a new file
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.saveAs.desc"));
item.setIcon(Icons.FILE_SAVE_AS);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Save as... selected");
saveAsAction();
}
});
fileMenu.add(item);
//// Export as
JMenu exportSubMenu = new JMenu(trans.get("main.menu.file.exportAs"));
exportSubMenu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.exportAs.desc"));
exportSubMenu.setIcon(Icons.FILE_EXPORT);
////// Export RASAero
JMenuItem exportRASAero = new JMenuItem(trans.get("main.menu.file.exportAs.RASAero"));
exportRASAero.setIcon(Icons.RASAERO);
exportRASAero.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.exportAs.RASAero.desc"));
exportRASAero.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
exportRASAeroAction();}
});
exportSubMenu.add(exportRASAero);
////// Export RockSim
JMenuItem exportRockSim = new JMenuItem(trans.get("main.menu.file.exportAs.RockSim"));
exportRockSim.setIcon(Icons.ROCKSIM);
exportRockSim.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.exportAs.RockSim.desc"));
exportRockSim.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
exportRockSimAction();}
});
exportSubMenu.add(exportRockSim);
exportSubMenu.addSeparator();
////// Export Wavefront OBJ
JMenuItem exportOBJ = new JMenuItem(trans.get("main.menu.file.exportAs.WavefrontOBJ"));
exportOBJ.setIcon(Icons.EXPORT_3D);
exportOBJ.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.exportAs.WavefrontOBJ.desc"));
exportOBJ.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
exportWavefrontOBJAction();}
});
exportSubMenu.add(exportOBJ);
fileMenu.add(exportSubMenu);
fileMenu.addSeparator();
// ------------------------------------------------------------------------------------------
//// Save decal image...
item = new JMenuItem(trans.get("main.menu.file.exportDecal"));
item.setIcon(Icons.SAVE_DECAL);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.exportDecal.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
exportDecalAction();
}
});
item.setEnabled(document.getDecalList().size() > 0);
// TODO
/* document.getRocket().addChangeListener(new StateChangeListener() {
@Override
public void stateChanged(EventObject e) {
exportMenuItem.setEnabled(document.getDecalList().size() > 0);
}
}); */
fileMenu.add(item);
//// Print design info...
item = new JMenuItem(trans.get("main.menu.file.print"), KeyEvent.VK_P);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, SHORTCUT_KEY));
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.info(Markers.USER_MARKER, "Print action selected");
printAction();
}
});
fileMenu.add(item);
// export sim table...
AbstractAction simTableExportAction = simulationPanel.getExportSimulationTableAsCSVAction();
JMenuItem exportSimTableToCSVMenuItem = new JMenuItem(simTableExportAction);
fileMenu.add(exportSimTableToCSVMenuItem);
fileMenu.addSeparator();
// ------------------------------------------------------------------------------------------
//// Close
item = new JMenuItem(trans.get("main.menu.file.close"), KeyEvent.VK_C);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, SHORTCUT_KEY));
//// Close the current rocket design
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.close.desc"));
item.setIcon(Icons.FILE_CLOSE);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Close selected");
closeAction();
}
});
fileMenu.add(item);
fileMenu.addSeparator();
//// Quit
item = new JMenuItem(trans.get("main.menu.file.quit"), KeyEvent.VK_Q);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, SHORTCUT_KEY));
//// Quit the program
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.quit.desc"));
item.setIcon(Icons.FILE_QUIT);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Quit selected");
quitAction();
}
});
fileMenu.add(item);
//// Edit
JMenu editMenu = new JMenu(trans.get("main.menu.edit"));
editMenu.setMnemonic(KeyEvent.VK_E);
//// Rocket editing
editMenu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.desc"));
menubar.add(editMenu);
Action action = UndoRedoAction.newUndoAction(document);
item = new JMenuItem(action);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, SHORTCUT_KEY));
item.setMnemonic(KeyEvent.VK_U);
//// Undo the previous operation
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.undo.desc"));
editMenu.add(item);
action = UndoRedoAction.newRedoAction(document);
item = new JMenuItem(action);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, SHORTCUT_KEY));
item.setMnemonic(KeyEvent.VK_R);
//// Redo the previously undone operation
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.redo.desc"));
editMenu.add(item);
editMenu.addSeparator();
item = new JMenuItem(actions.getEditAction());
editMenu.add(item);
item = new JMenuItem(actions.getCutAction());
editMenu.add(item);
item = new JMenuItem(actions.getCopyAction());
editMenu.add(item);
item = new JMenuItem(actions.getPasteAction());
editMenu.add(item);
item = new JMenuItem(actions.getDuplicateAction());
editMenu.add(item);
item = new JMenuItem(actions.getDeleteAction());
editMenu.add(item);
editMenu.addSeparator();
JMenu subMenu = new JMenu(trans.get("RocketActions.Select"));
editMenu.add(subMenu);
item = new JMenuItem(actions.getSelectSameColorAction());
subMenu.add(item);
item = new JMenuItem(actions.getDeselectAllAction());
subMenu.add(item);
editMenu.addSeparator();
item = new JMenuItem(actions.getScaleAction());
editMenu.add(item);
//// Preferences
item = new JMenuItem(trans.get("main.menu.edit.preferences"));
item.setIcon(Icons.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) {
log.info(Markers.USER_MARKER, "Preferences selected");
PreferencesDialog.showPreferences(BasicFrame.this);
}
});
editMenu.add(item);
//// Edit Component Preset File
if (System.getProperty("openrocket.preseteditor.fileMenu") != null) {
item = new JMenuItem(trans.get("main.menu.edit.editpreset"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JFrame dialog = new JFrame();
dialog.getContentPane().add(new ComponentPresetEditor(dialog));
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.pack();
dialog.setVisible(true);
}
});
editMenu.add(item);
}
// Tools
JMenu toolsMenu = new JMenu(trans.get("main.menu.tools"));
menubar.add(toolsMenu);
//// Component analysis
item = new JMenuItem(trans.get("main.menu.tools.componentAnalysis"), KeyEvent.VK_C);
//// Analyze the rocket components separately
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.tools.componentAnalysis.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Component analysis selected");
ComponentAnalysisDialog.showDialog(rocketpanel);
}
});
toolsMenu.add(item);
//// Optimize
item = new JMenuItem(trans.get("main.menu.tools.optimization"), KeyEvent.VK_O);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.tools.optimization.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Rocket optimization selected");
try {
new GeneralOptimizationDialog(document, BasicFrame.this).setVisible(true);
} catch (InterruptedException ex) {
log.warn(ex.getMessage());
}
}
});
toolsMenu.add(item);
//// Custom expressions
item = new JMenuItem(trans.get("main.menu.tools.customExpressions"), KeyEvent.VK_E);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.tools.customExpressions.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.debug("Custom expressions selected");
new CustomExpressionDialog(document, BasicFrame.this).setVisible(true);
}
});
toolsMenu.add(item);
item = new JMenuItem(trans.get("PhotoFrame.title"), KeyEvent.VK_P);
item.getAccessibleContext().setAccessibleDescription(trans.get("PhotoFrame.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Photo... selected");
PhotoFrame pa = new PhotoFrame(document, BasicFrame.this);
pa.setVisible(true);
}
});
toolsMenu.add(item);
//// Debug
// // (shown if openrocket.debug.fileMenu is defined)
if (System.getProperty("openrocket.debug.fileMenu") != null) {
menubar.add(makeDebugMenu());
}
//// Help
generateHelpMenu(menubar, this);
this.setJMenuBar(menubar);
}
public static void generateHelpMenu(JMenuBar menubar, JFrame parent) {
JMenu menu;
JMenuItem item;
menu = new JMenu(trans.get("main.menu.help"));
menu.setMnemonic(KeyEvent.VK_H);
menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.desc"));
menubar.add(menu);
//// Guided tours
item = new JMenuItem(trans.get("main.menu.help.tours"), KeyEvent.VK_L);
item.setIcon(Icons.HELP_TOURS);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.tours.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Guided tours selected");
GuidedTourSelectionDialog.showDialog(parent);
}
});
menu.add(item);
//// Wiki (Online Help)
item = new JMenuItem(trans.get("main.menu.help.wiki"));
item.setIcon(Icons.WIKI);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.wiki.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Wiki selected");
URLUtil.openWebpage(URLUtil.WIKI_URL);
}
});
menu.add(item);
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) {
log.info(Markers.USER_MARKER, "Bug report selected");
BugReportDialog.showBugReportDialog(parent);
}
});
menu.add(item);
//// Debug log
item = new JMenuItem(trans.get("main.menu.help.debugLog"), KeyEvent.VK_D);
item.setIcon(Icons.HELP_DEBUG_LOG);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, SHIFT_SHORTCUT_KEY));
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.debugLog.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Debug log selected");
new DebugLogDialog(parent).setVisible(true);
}
});
menu.add(item);
menu.addSeparator();
//// 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.info(Markers.USER_MARKER, "License selected");
new LicenseDialog(parent).setVisible(true);
}
});
menu.add(item);
//// 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.info(Markers.USER_MARKER, "About selected");
new AboutDialog(parent).setVisible(true);
}
});
menu.add(item);
}
public static void addFileCreateAndOpenMenuItems(JMenu fileMenu, Window parent) {
JMenuItem item;
//// New
item = new JMenuItem(trans.get("main.menu.file.new"), KeyEvent.VK_N);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, SHORTCUT_KEY));
item.setMnemonic(KeyEvent.VK_N);
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.info(Markers.USER_MARKER, "New... selected");
newAction();
if (parent instanceof BasicFrame) {
((BasicFrame) parent).closeIfReplaceable();
}
}
});
fileMenu.add(item);
//// Open...
item = new JMenuItem(trans.get("main.menu.file.open"), KeyEvent.VK_O);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORTCUT_KEY));
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.open.desc"));
item.setIcon(Icons.FILE_OPEN);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Open... selected");
openAction(parent);
}
});
fileMenu.add(item);
//// Open Recent
item = new MRUDesignFileAction(trans.get("main.menu.file.openRecent"), parent);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.openRecent.desc"));
item.setIcon(Icons.FILE_OPEN);
fileMenu.add(item);
//// Open example
BasicFrame basicFrame = parent instanceof BasicFrame ? (BasicFrame) parent : null;
item = new ExampleDesignFileAction(trans.get("main.menu.file.openExample"), basicFrame);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.openExample.desc"));
item.setIcon(Icons.FILE_OPEN_EXAMPLE);
fileMenu.add(item);
//// Import
JMenu importSubMenu = new JMenu(trans.get("main.menu.file.import"));
importSubMenu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.import.desc"));
importSubMenu.setIcon(Icons.FILE_IMPORT);
fileMenu.add(importSubMenu);
////// Import RASAero
JMenuItem importRASAero = new JMenuItem(trans.get("main.menu.file.import.RASAero"));
importRASAero.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.import.RASAero.desc"));
importRASAero.setIcon(Icons.RASAERO);
importRASAero.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
importRASAeroAction(parent);
}
});
importSubMenu.add(importRASAero);
////// Import RockSim
JMenuItem importRockSim = new JMenuItem(trans.get("main.menu.file.import.RockSim"));
importRockSim.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.import.RockSim.desc"));
importRockSim.setIcon(Icons.ROCKSIM);
importRockSim.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
importRockSimAction(parent);
}
});
importSubMenu.add(importRockSim);
}
public RocketActions getRocketActions() {
return actions;
}
public void doComponentTreePopup(MouseEvent e) {
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
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.info(Markers.USER_MARKER, "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." },
"Debug menu", JOptionPane.INFORMATION_MESSAGE);
}
});
menu.add(item);
menu.addSeparator();
//// Create test rocket
item = new JMenuItem("Create test rocket");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "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"
}, "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 = OpenRocketDocumentFactory.createDocumentFromRocket(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) {
log.info(Markers.USER_MARKER, "Create Iso-Haisu selected");
Rocket r = TestRockets.makeIsoHaisu();
OpenRocketDocument doc = OpenRocketDocumentFactory.createDocumentFromRocket(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.info(Markers.USER_MARKER, "Create Big Blue selected");
Rocket r = TestRockets.makeBigBlue();
OpenRocketDocument doc = OpenRocketDocumentFactory.createDocumentFromRocket(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.info(Markers.USER_MARKER, "Memory statistics selected");
// // Get discarded but remaining objects (this also runs System.gc multiple times)
List<MemoryData> 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.info(Markers.USER_MARKER, "Exhaust memory selected");
LinkedList<byte[]> data = new LinkedList<byte[]>();
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.info(Markers.USER_MARKER, "Exception here selected");
throw new RuntimeException("Testing exception from menu action listener");
}
});
menu.add(item);
item = new JMenuItem("Exception from EDT");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Exception from EDT selected");
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() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Exception from other thread selected");
new Thread() {
@Override
public void run() {
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.info(Markers.USER_MARKER, "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.info(Markers.USER_MARKER, "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;
}
/**
* Return the frame that was created at the application's startup.
*/
public static BasicFrame getStartupFrame() {
return startupFrame;
}
/**
* Set the frame that is created at the application's startup.
*/
public static void setStartupFrame(BasicFrame startupFrame) {
BasicFrame.startupFrame = startupFrame;
}
/**
* Select the tab on the main pane.
*
* @param tab one of {@link #DESIGN_TAB}, {@link #FLIGHT_CONFIGURATION_TAB} or {@link #SIMULATION_TAB}.
*/
public void selectTab(int tab) {
tabbedPane.setSelectedIndex(tab);
}
public int getSelectedTab() {
return tabbedPane.getSelectedIndex();
}
/**
* Open a custom design file, specified by the file filter.
* @param parent parent window to open the file chooser on
* @param filter the file filter to use, or null for no filter. E.g. use "RockSim" for RockSim files.
*/
public static void openAction(Window parent, FileFilter filter) {
JFileChooser chooser = new JFileChooser();
chooser.addChoosableFileFilter(FileHelper.ALL_DESIGNS_FILTER);
chooser.addChoosableFileFilter(filter);
chooser.setFileFilter(filter);
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setMultiSelectionEnabled(true);
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
int option = chooser.showOpenDialog(parent);
if (option != JFileChooser.APPROVE_OPTION) {
log.info(Markers.USER_MARKER, "Decided not to open files, option=" + option);
return;
}
((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
File[] files = chooser.getSelectedFiles();
log.info(Markers.USER_MARKER, "Opening files " + Arrays.toString(files));
for (File file : files) {
log.info("Opening file: " + file);
if (open(file, parent) != null) {
MRUDesignFile opts = MRUDesignFile.getInstance();
opts.addFile(file.getAbsolutePath());
}
}
}
/**
* Open an OpenRocket file.
*/
public static void openAction(Window parent) {
openAction(parent, FileHelper.OPENROCKET_DESIGN_FILTER);
}
/**
* Import a RockSim file.
* @param parent parent window to open the file chooser on
*/
public static void importRockSimAction(Window parent) {
log.info(Markers.USER_MARKER, "Import RockSim selected");
openAction(parent, FileHelper.ROCKSIM_DESIGN_FILTER);
}
/**
* Import a RASAero file.
* @param parent parent window to open the file chooser on
*/
public static void importRASAeroAction(Window parent) {
log.info(Markers.USER_MARKER, "Import RASAero selected");
openAction(parent, FileHelper.RASAERO_DESIGN_FILTER);
}
private void closeIfReplaceable() {
// 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();
}
}
/**
* Open a file based on a URL.
* @param url the file to open.
* @param parent the parent window for dialogs.
* @return <code>true</code> if opened successfully.
*/
public static void open(URL url, BasicFrame parent) {
String displayName = null;
// First figure out the file name from the URL
// Try using URI.getPath();
try {
URI uri = url.toURI();
displayName = uri.getPath();
} catch (URISyntaxException ignore) {
}
// Try URL-decoding the URL
if (displayName == null) {
try {
displayName = URLDecoder.decode(url.toString(), "UTF-8");
} catch (UnsupportedEncodingException ignore) {
}
}
if (displayName == null) {
displayName = "";
}
// Remove path from filename
if (displayName.lastIndexOf('/') >= 0) {
displayName = displayName.substring(displayName.lastIndexOf('/') + 1);
}
//// Open the file
log.info("Opening file from url=" + url + " filename=" + displayName);
OpenFileWorker worker = new OpenFileWorker(url);
open(worker, displayName, parent, true);
}
/**
* Open the specified file in a new design frame. If an error occurs, an error
* dialog is shown and <code>false</code> is returned.
*
* @param file the file to open.
* @param parent the parent component for which a progress dialog is opened.
* @return the BasicFrame that was created, or null if not created successfully.
*/
public static BasicFrame open(File file, Window parent) {
OpenFileWorker worker = new OpenFileWorker(file);
BasicFrame frame = open(worker, file.getName(), parent, false);
if (frame != null) {
MRUDesignFile.getInstance().addFile(file.getAbsolutePath());
}
return frame;
}
/**
* Open the specified file using the provided worker.
*
* @param worker the OpenFileWorker that loads the file.
* @param displayName the file name to display in dialogs.
* @param parent
* @param openRocketConfigDialog if true, will open the configuration dialog of the rocket. This is useful for examples.
* @return the BasicFrame that was created, or null if not created successfully.
*/
private static BasicFrame open(OpenFileWorker worker, String displayName, Window parent, boolean openRocketConfigDialog) {
//// Open the file in a Swing worker thread
log.info("Starting OpenFileWorker");
if (!SwingWorkerDialog.runWorker(parent, "Opening file", "Reading " + displayName + "...", worker)) {
// // User cancelled the operation
log.info("User cancelled the OpenFileWorker");
return null;
}
//// Handle the document
OpenRocketDocument doc = null;
try {
doc = worker.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException) {
log.warn("File not found", cause);
JOptionPane.showMessageDialog(parent,
"File not found: " + displayName,
"Error opening file", JOptionPane.ERROR_MESSAGE);
return null;
} else if (cause instanceof RocketLoadException) {
log.warn("Error loading the file", cause);
JOptionPane.showMessageDialog(parent,
"Unable to open file '" + displayName + "': "
+ cause.getMessage(),
"Error opening file", JOptionPane.ERROR_MESSAGE);
return null;
} 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("Document loader returned null");
}
//// 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
trans.get("BasicFrame.WarningDialog.txt1") + " " + displayName + ".",
// // Some design features may not have been loaded correctly.
trans.get("BasicFrame.WarningDialog.txt2")
},
// // Warnings while opening file
trans.get("BasicFrame.WarningDialog.title"), warnings);
}
//// Open the frame
log.debug("Opening new frame with the document");
BasicFrame frame = new BasicFrame(doc);
frame.setVisible(true);
if (parent != null && parent instanceof BasicFrame) {
((BasicFrame) parent).closeIfReplaceable();
}
if (openRocketConfigDialog) {
ComponentConfigDialog.showDialog(frame, doc, doc.getRocket());
}
return frame;
}
/**
* "Save" action. If the design is new, then this is identical to "Save As", with a default file filter for .ork.
* If the rocket being edited previously was opened from a .ork file, then it will be saved immediately to the same
* file. But clicking on 'Save' for an existing design file with a RockSim or RASAero file will bring up a confirmation
* dialog because it's potentially a destructive write (loss of some fidelity if it's truly an original RockSim/RASAero
* generated file).
*
* @return true if the file was saved, false otherwise
*/
private boolean saveAction() {
File file = document.getFile();
if (file == null || document.getDefaultStorageOptions().getFileType().equals(FileType.ROCKSIM)
|| document.getDefaultStorageOptions().getFileType().equals(FileType.RASAERO)) {
log.info("Document does not contain file, opening save as dialog instead");
return saveAsAction();
}
log.info("Saving document to " + file);
return saveAsOpenRocket(file);
}
/**
* Opens a file chooser dialog for saving a new file, and returns the selected file.
* @param fileType file type to use (e.g. RASAero)
* @return the file selected from the dialog, or null if no file was selected.
*/
private File openFileSaveAsDialog(FileType fileType) {
final DesignFileSaveAsFileChooser chooser = DesignFileSaveAsFileChooser.build(document, fileType);
int option = chooser.showSaveDialog(BasicFrame.this);
if (option != JFileChooser.APPROVE_OPTION) {
log.info(Markers.USER_MARKER, "User decided not to save, option=" + option);
return null;
}
File file = chooser.getSelectedFile();
if (file == null) {
log.info(Markers.USER_MARKER, "User did not select a file");
return null;
}
((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
return file;
}
//// BEGIN RASAero Save/Export Action
/**
* MODEL "Export as" RASAero file format
*
* @return true if the file was saved, false otherwise
*/
public boolean exportRASAeroAction() {
File file = openFileSaveAsDialog(FileType.RASAERO);
if (file == null) {
return false;
}
file = FileHelper.forceExtension(file, RASAeroCommonConstants.FILE_EXTENSION);
if (FileHelper.confirmWrite(file, BasicFrame.this)) {
boolean result = saveAsRASAero(file);
if (!result) {
file.delete();
}
return result;
}
return false;
}
/**
* Perform the writing of the design to the given file in RASAero format.
* @param file the chosen file
* @return true if the file was written
*/
private boolean saveAsRASAero(File file) {
if (prefs.getShowRASAeroFormatWarning()) {
// Show RASAero format warning
JPanel panel = new JPanel(new MigLayout());
panel.add(new StyledLabel(trans.get("SaveRASAeroWarningDialog.txt1")), "wrap");
final JCheckBox check = new JCheckBox(trans.get("SaveRASAeroWarningDialog.donotshow"));
check.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
prefs.setShowRASAeroFormatWarning(!check.isSelected());
}
});
panel.add(check);
int sel = JOptionPane.showOptionDialog(BasicFrame.this,
panel,
"", // title
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE,
null, // icon
null, // options
null // default option
);
if (sel == 1) {
return false;
}
}
StorageOptions options = new StorageOptions();
options.setFileType(FileType.RASAERO);
return saveRASAeroFile(file, options);
}
/**
* Perform the actual saving of the RASAero file
* @param file file to be stored
* @param options storage options to use
* @return true if the file was written
*/
private boolean saveRASAeroFile(File file, StorageOptions options) {
try {
ROCKET_SAVER.save(file, document, options);
WarningSet warnings = ROCKET_SAVER.getWarnings();
ErrorSet errors = ROCKET_SAVER.getErrors();
if (!warnings.isEmpty() && errors.isEmpty()) {
WarningDialog.showWarnings(BasicFrame.this,
new Object[]{
// // The following problems were encountered while saving
trans.get("BasicFrame.WarningDialog.saving.txt1") + " '" + file.getName() + "'.",
// // Some design features may not have been exported correctly.
trans.get("BasicFrame.WarningDialog.saving.txt2")
},
// // Warnings while opening file
trans.get("BasicFrame.WarningDialog.saving.title"), warnings);
} else if (!errors.isEmpty()) {
ErrorWarningDialog.showErrorsAndWarnings(BasicFrame.this,
new Object[]{
// // The following problems were encountered while saving
trans.get("BasicFrame.WarningDialog.saving.txt1") + " '" + file.getName() + "'.",
// // Please correct the errors.
trans.get("BasicFrame.ErrorWarningDialog.txt1")
},
// // Errors/Warnings while saving file
trans.get("BasicFrame.ErrorWarningDialog.saving.title"), errors, warnings);
}
// Do not update the save state of the document.
return errors.isEmpty();
} catch (IOException e) {
return false;
} catch (DecalNotFoundException decex) {
DecalImage decal = decex.getDecal();
// Check if the user replaced the source file, if not, just ignore the faulty decal on the next save
if (!DecalNotFoundDialog.showDialog(null, decex) && decal != null) {
decal.setIgnored(true);
}
return saveRASAeroFile(file, options); // Re-save
}
}
//// END RASAero Save/Export Action
//// BEGIN ROCKSIM Save/Export Action
/**
* MODEL "Export as" RASAero file format
*
* @return true if the file was saved, false otherwise
*/
public boolean exportRockSimAction() {
File file = openFileSaveAsDialog(FileType.ROCKSIM);
if (file == null) {
return false;
}
file = FileHelper.forceExtension(file, "rkt");
if (FileHelper.confirmWrite(file, BasicFrame.this) ) {
return saveAsRockSim(file);
}
return false;
}
/**
* Perform the writing of the design to the given file in RockSim format.
*
* @param file the chosen file
*
* @return true if the file was written
*/
private boolean saveAsRockSim(File file) {
if ( prefs.getShowRockSimFormatWarning() ) {
// Show RockSim format warning
JPanel panel = new JPanel(new MigLayout());
panel.add(new StyledLabel(trans.get("SaveRktWarningDialog.txt1")), "wrap");
final JCheckBox check = new JCheckBox(trans.get("SaveRktWarningDialog.donotshow"));
check.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
prefs.setShowRockSimFormatWarning(!check.isSelected());
}
});
panel.add(check);
int sel = JOptionPane.showOptionDialog(BasicFrame.this,
panel,
"", // title
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE,
null, // icon
null, // options
null // default option
);
if ( sel == 1 ) {
return false;
}
}
StorageOptions options = new StorageOptions();
options.setFileType(StorageOptions.FileType.ROCKSIM);
return saveRockSimFile(file, options);
}
/**
* Perform the actual saving of the RockSim file
* @param file file to be stored
* @param options storage options to use
* @return true if the file was written
*/
private boolean saveRockSimFile(File file, StorageOptions options) {
try {
ROCKET_SAVER.save(file, document, options);
// Do not update the save state of the document.
return true;
} catch (IOException e) {
return false;
} catch (DecalNotFoundException decex) {
DecalImage decal = decex.getDecal();
// Check if the user replaced the source file, if not, just ignore the faulty decal on the next save
if (!DecalNotFoundDialog.showDialog(null, decex) && decal != null) {
decal.setIgnored(true);
}
return saveRockSimFile(file, options); // Re-save
}
}
//// END ROCKSIM Save/Export Action
//// BEGIN WAVEFRONT OBJ Save/Export Action
private void exportWavefrontOBJAction() {
// TODO: popup dialog for extra options (quality, whether to triangulate, whether to export materials, whether to save all subcomponents of the selected ones, whether to offset the object position to zero or to the location in the rocket, whether to scale the rocket etc.)
String filePath = "/Users/SiboVanGool/Downloads/test.obj";
OBJExporterFactory exporter = new OBJExporterFactory(getSelectedComponents(), false, false, true, filePath);
exporter.doExport();
}
/**
* "Save As" action.
*
* @return true if the file was saved, false otherwise
*/
private boolean saveAsAction() {
// Open dialog for saving rocket info
showSaveRocketInfoDialog();
File file = openFileSaveAsDialog(FileType.OPENROCKET);
if (file == null) {
return false;
}
file = FileHelper.forceExtension(file, "ork");
boolean result = FileHelper.confirmWrite(file, BasicFrame.this) && saveAsOpenRocket(file);
if (result) {
MRUDesignFile opts = MRUDesignFile.getInstance();
opts.addFile(file.getAbsolutePath());
}
return result;
}
private void showSaveRocketInfoDialog() {
if (!prefs.isShowSaveRocketInfo()) {
return;
}
// Select the rocket in the component tree to indicate to users that they can edit the rocket info by editing the rocket
setSelectedComponent(rocket);
// Open the save rocket info
JDialog dialog = new JDialog();
SaveDesignInfoPanel panel = new SaveDesignInfoPanel(document, rocket, dialog);
dialog.setContentPane(panel);
dialog.pack();
dialog.setTitle(trans.get("BasicFrame.lbl.SaveRocketInfo"));
dialog.setModal(true);
dialog.setLocationRelativeTo(null);
dialog.setVisible(true);
}
/**
* Perform the writing of the design to the given file in OpenRocket format.
*
* @param file the chosen file
*
* @return true if the file was written
*/
private boolean saveAsOpenRocket(File file) {
file = FileHelper.forceExtension(file, "ork");
log.info("Saving document as " + file);
if (!StorageOptionChooser.verifyStorageOptions(document, BasicFrame.this)) {
// User cancelled the dialog
log.info(Markers.USER_MARKER, "User cancelled saving in storage options dialog");
return false;
}
document.getDefaultStorageOptions().setFileType(FileType.OPENROCKET);
SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER);
if (!SwingWorkerDialog.runWorker(BasicFrame.this, "Saving file",
"Writing " + file.getName() + "...", worker)) {
// User cancelled the save
log.info(Markers.USER_MARKER, "User cancelled the save, deleting the file");
file.delete();
return false;
}
try {
worker.get();
document.setFile(file);
document.setSaved(true);
setTitle();
return true;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof IOException) {
log.warn("An I/O error occurred while saving " + file, cause);
JOptionPane.showMessageDialog(BasicFrame.this, new String[] {
"An I/O error occurred while saving:",
e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
return false;
}
else if (cause instanceof DecalNotFoundException) {
DecalNotFoundException decex = (DecalNotFoundException) cause;
DecalImage decal = decex.getDecal();
// Check if the user replaced the source file, if not, just ignore the faulty decal on the next save
if (!DecalNotFoundDialog.showDialog(null, decex) && decal != null) {
decal.setIgnored(true);
}
return saveAsOpenRocket(file); // Re-save
} else {
Reflection.handleWrappedException(e);
}
} catch (InterruptedException e) {
throw new BugException("EDT was interrupted", e);
}
return false;
}
private boolean closeAction() {
if (!document.isSaved()) {
log.info("Confirming whether to save the design");
ComponentConfigDialog.disposeDialog();
int result = JOptionPane.showConfirmDialog(BasicFrame.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
log.info(Markers.USER_MARKER, "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.info(Markers.USER_MARKER, "User requested to discard design");
} else {
// Cancel or close
log.info(Markers.USER_MARKER, "User cancelled closing, result=" + result);
return false;
}
}
// Rocket has been saved or discarded
log.debug("Disposing window");
BasicFrame.this.dispose();
ComponentConfigDialog.disposeDialog();
ComponentAnalysisDialog.hideDialog();
frames.remove(BasicFrame.this);
if (frames.isEmpty()) {
// Don't quit the application on macOS, but keep the application open
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
DummyFrameMenuOSX.createDummyDialog();
} else {
log.info("Last frame closed, exiting");
System.exit(0);
}
}
return true;
}
public void exportDecalAction() {
new ExportDecalDialog(BasicFrame.this, document).setVisible(true);
}
public void printAction() {
double rotation = rocketpanel.getFigure().getRotation();
new PrintDialog(BasicFrame.this, document, rotation).setVisible(true);
}
/**
* Opens a new design file or the last design file, if set in the preferences.
* Can be used for reopening the application or opening it the first time.
* @return the BasicFrame that was created
*/
public static BasicFrame reopen() {
if (!Application.getPreferences().isAutoOpenLastDesignOnStartupEnabled()) {
return BasicFrame.newAction();
} else {
String lastFile = MRUDesignFile.getInstance().getLastEditedDesignFile();
if (lastFile != null) {
log.info("Opening last design file: " + lastFile);
BasicFrame frame = BasicFrame.open(new File(lastFile), null);
if (frame == null) {
MRUDesignFile.getInstance().removeFile(lastFile);
return BasicFrame.newAction();
}
else {
MRUDesignFile.getInstance().addFile(lastFile);
return frame;
}
}
else {
return BasicFrame.newAction();
}
}
}
/**
* Open a new design window with a basic rocket+stage.
* @return the BasicFrame that was created
*/
public static BasicFrame newAction() {
log.info("New action initiated");
OpenRocketDocument doc = OpenRocketDocumentFactory.createNewRocket();
BasicFrame frame = new BasicFrame(doc);
frame.replaceable = true;
frame.setVisible(true);
return frame;
}
/**
* Quit the application. Confirms saving unsaved designs. The action of File->Quit.
*/
public static void quitAction() {
if (quitCalled) return;
quitCalled = true;
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");
quitCalled = false;
return;
}
}
// Should not be reached, but just in case
log.error("Should already have exited application");
System.exit(0);
}
/**
* Set the title of the frame, taking into account the name of the rocket, file it
* has been saved to (if any) and saved status.
*/
private void setTitle() {
File file = document.getFile();
boolean saved = document.isSaved();
String title;
title = rocket.getName();
if (file != null) {
title = title + " (" + file.getName() + ")";
}
if (!saved)
title = "*" + title;
setTitle(title);
}
/**
* 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 <code>null</code> if none found.
*/
public static BasicFrame findFrame(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;
}
/**
* Return all BasicFrame instances
*/
public static List<BasicFrame> getAllFrames() {
return frames;
}
/**
* Checks whether all the BasicFrames are closed.
* @return true if all the BasicFrames are closed, false if not
*/
public static boolean isFramesEmpty() {
return frames.isEmpty();
}
/**
* 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 <code>null</code> if not found.
*/
public static OpenRocketDocument findDocument(Rocket rocket) {
BasicFrame frame = findFrame(rocket);
if (frame != null) {
return frame.document;
} else {
return null;
}
}
public void setSelectedComponent(RocketComponent component) {
this.selectionModel.setSelectedComponent(component);
}
public void setSelectedComponents(List<RocketComponent> components) {
this.selectionModel.setSelectedComponents(components);
}
public void stateChanged(ChangeEvent e) {
JTabbedPane tabSource = (JTabbedPane) e.getSource();
int tab = tabSource.getSelectedIndex();
if (previousTab == SIMULATION_TAB) {
simulationPanel.updatePreviousSelection();
}
previousTab = tab;
switch (tab) {
case DESIGN_TAB:
designPanel.takeTheSpotlight();
break;
case FLIGHT_CONFIGURATION_TAB:
flightConfigurationPanel.takeTheSpotlight();
break;
case SIMULATION_TAB:
simulationPanel.takeTheSpotlight();
simulationPanel.activating();
break;
}
}
public void open() {
}
}
class BasicFrame_changeAdapter implements javax.swing.event.ChangeListener {
BasicFrame adaptee;
BasicFrame_changeAdapter(BasicFrame adaptee) {
this.adaptee = adaptee;
}
public void stateChanged(ChangeEvent e) {
adaptee.stateChanged(e);
}
}