diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 206b75452..61e78c872 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -61,6 +61,7 @@ import net.sf.openrocket.gui.help.tours.GuidedTourSelectionDialog; import net.sf.openrocket.gui.main.componenttree.ComponentTree; import net.sf.openrocket.gui.main.flightconfigpanel.FlightConfigurationPanel; 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; @@ -100,9 +101,9 @@ public class BasicFrame extends JFrame { private static final Translator trans = Application.getTranslator(); private static final Preferences prefs = Application.getPreferences(); - private static final int SHORTCUT_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); + public static final int SHORTCUT_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); - private static final int SHIFT_SHORTCUT_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() | + public static final int SHIFT_SHORTCUT_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() | SHIFT_DOWN_MASK; public static final int COMPONENT_TAB = 0; @@ -1189,6 +1190,10 @@ public class BasicFrame extends JFrame { private void openAction() { + openAction(this); + } + + public static void openAction(Window parent) { JFileChooser chooser = new JFileChooser(); chooser.addChoosableFileFilter(FileHelper.ALL_DESIGNS_FILTER); @@ -1198,7 +1203,7 @@ public class BasicFrame extends JFrame { chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); chooser.setMultiSelectionEnabled(true); chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); - int option = chooser.showOpenDialog(this); + int option = chooser.showOpenDialog(parent); if (option != JFileChooser.APPROVE_OPTION) { log.info(Markers.USER_MARKER, "Decided not to open files, option=" + option); return; @@ -1211,7 +1216,7 @@ public class BasicFrame extends JFrame { for (File file : files) { log.info("Opening file: " + file); - if (open(file, this)) { + if (open(file, parent)) { MRUDesignFile opts = MRUDesignFile.getInstance(); opts.addFile(file.getAbsolutePath()); } @@ -1688,10 +1693,14 @@ public class BasicFrame extends JFrame { ComponentAnalysisDialog.hideDialog(); frames.remove(this); - // Don't quit the application on macOS - if (frames.isEmpty() && (SystemInfo.getPlatform() != SystemInfo.Platform.MAC_OS)) { - log.info("Last frame closed, exiting"); - System.exit(0); + 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; } @@ -1803,6 +1812,14 @@ public class BasicFrame extends JFrame { return null; } + /** + * 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. diff --git a/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java new file mode 100644 index 000000000..1d0bf444e --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java @@ -0,0 +1,185 @@ +package net.sf.openrocket.gui.util; + +import net.sf.openrocket.gui.dialogs.AboutDialog; +import net.sf.openrocket.gui.dialogs.LicenseDialog; +import net.sf.openrocket.gui.help.tours.GuidedTourSelectionDialog; +import net.sf.openrocket.gui.main.BasicFrame; +import net.sf.openrocket.gui.main.ExampleDesignFileAction; +import net.sf.openrocket.gui.main.MRUDesignFileAction; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.Markers; +import net.sf.openrocket.startup.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; + +/** + * This dummy frame will be generated when all design windows are close on macOS. + * It serves to maintain and customize the application menu bar when all the BasicFrame windows are closed. + * + * @author Sibo Van Gool + */ +public class DummyFrameMenuOSX extends JFrame { + private static final Translator trans = Application.getTranslator(); + private static final Logger log = LoggerFactory.getLogger(BasicFrame.class); + + private static DummyFrameMenuOSX dialog; + + private DummyFrameMenuOSX() { + createMenu(); + setUndecorated(true); + setBackground(new Color(0, 0, 0, 0)); + setVisible(true); // this is needed to show the menu bar + } + + public static DummyFrameMenuOSX createDummyDialog() { + dialog = new DummyFrameMenuOSX(); + return dialog; + } + + public static DummyFrameMenuOSX getDummyDialog() { + return dialog; + } + + public static void removeDummyDialog() { + if (dialog != null) { + dialog.dispose(); + dialog = null; + } + } + + private void createMenu() { + JMenuBar menubar = new JMenuBar(); + JMenu menu; + JMenuItem item; + + //// File + menu = new JMenu(trans.get("main.menu.file")); + menu.setMnemonic(KeyEvent.VK_F); + //// File-handling related tasks + menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.desc")); + menubar.add(menu); + + //// New + item = new JMenuItem(trans.get("main.menu.file.new"), KeyEvent.VK_N); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, BasicFrame.SHORTCUT_KEY)); + item.setMnemonic(KeyEvent.VK_N); + //// 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) { + DummyFrameMenuOSX.removeDummyDialog(); + log.info(Markers.USER_MARKER, "New... selected"); + BasicFrame.newAction(); + } + }); + menu.add(item); + + //// Open... + item = new JMenuItem(trans.get("main.menu.file.open"), KeyEvent.VK_O); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, BasicFrame.SHORTCUT_KEY)); + //// Open a rocket design + 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) { + DummyFrameMenuOSX.removeDummyDialog(); + log.info(Markers.USER_MARKER, "Open... selected"); + BasicFrame.openAction(DummyFrameMenuOSX.this); + } + }); + menu.add(item); + + //// Open Recent... + item = new MRUDesignFileAction(trans.get("main.menu.file.openRecent"), this); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.openRecent.desc")); + item.setIcon(Icons.FILE_OPEN); + menu.add(item); + + //// Open example... + item = new ExampleDesignFileAction(trans.get("main.menu.file.openExample"), null); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.file.openExample.desc")); + item.setIcon(Icons.FILE_OPEN_EXAMPLE); + menu.add(item); + + menu.addSeparator(); + + //// Quit + item = new JMenuItem(trans.get("main.menu.file.quit"), KeyEvent.VK_Q); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, BasicFrame.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"); + BasicFrame.quitAction(); + } + }); + menu.add(item); + + + //// Help + 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(DummyFrameMenuOSX.this); + } + }); + 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(DummyFrameMenuOSX.this).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(DummyFrameMenuOSX.this).setVisible(true); + } + }); + menu.add(item); + + + this.setJMenuBar(menubar); + } +} diff --git a/swing/src/net/sf/openrocket/startup/OSXSetup.java b/swing/src/net/sf/openrocket/startup/OSXSetup.java index a9f589c17..4f88aa90c 100644 --- a/swing/src/net/sf/openrocket/startup/OSXSetup.java +++ b/swing/src/net/sf/openrocket/startup/OSXSetup.java @@ -7,6 +7,7 @@ import java.awt.desktop.PreferencesHandler; import java.awt.desktop.QuitHandler; import java.awt.desktop.AppReopenedListener; +import net.sf.openrocket.gui.util.DummyFrameMenuOSX; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,8 +53,10 @@ final class OSXSetup { }; private static final AppReopenedListener APP_REOPENED_HANDLER = (e) -> { - log.info("App re-opened"); - BasicFrame.reopen(); + if (BasicFrame.isFramesEmpty()) { + log.info("App re-opened"); + BasicFrame.reopen(); + } }; /**