This series discusses techniques to package Java applications for Mac OS X. Thanks to an excellent Java VM, pure Java applications run immediately on the Mac. Still it’s a very polished machine and users have high expectations. The series discusses simple steps you should take to make your Java application more Mac friendly. The series will be useful to Java developers who want to write more portable applications.
Menus and the Mac
In the previous article, you learned about differences between the Mac user interface (Aqua) and other GUIs such as Windows/KDE/Gnome. You also learned how to package JAR files as application bundles. Application bundles replace the Start menu of other platforms, as well as the installer for most applications.
The next issue to consider when fine-tuning a Java application for Aqua is the menu bar. Aqua has a single menu bar that always appears at the top of the screen. It is shared between the applications and the system. On most other operating systems, individual windows have their own menus. You see the difference when you compare figure 1, a Windows screenshot, with figure 2, the Mac OS equivalent.
Figure 1: Under Windows, individual windows have their own menus.
Figure 2: Under Mac OS, the menu is shared between the applications and the system.
Finally look at the application menu. It appears on the right of the Apple menu and is labelled with the application name. Every application has one. The system controls half of the entries and your application the other half. On figure 3, entries for the application are highlighted.
Figure 3: The application is managed by the system and the application.
Support the Menu Bar
Porting to Mac with AWT is easy. Since AWT uses native controls, it automatically supports the Aqua bar. Although, the setMenuBar() method is defined on the Frame object, the menus are regular Mac menus.
Swing is less convenient. Since Swing draws the controls in Java, it attaches menus to windows in a non-portable way. To use the Aqua menu with Swing applications, you need to set the com.apple.macos.useScreenMenuBar system property to true. The following code excerpt demonstrates it:
System.setProperty("com.apple.macos.useScreenMenuBar","true"); int keyModifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); JMenuItem fileNew = new JMenuItem("New"); fileNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,keyModifier)); JMenu fileMenu = new JMenu("File"); fileMenu.add(fileNew); JMenuBar menuBar = new JMenuBar(); frame.setJMenuBar(menuBar); menuBar.add(file);
Although it sets an Apple-specific property, this code remains portable. The property is silently ignored on non-Apple platforms. Alternatively, you can set the property from the command-line with -D option:
java -Dcom.apple.macos.useScreenMenuBar=true -jar menu.jar
or using the MRJAppBuilder introduced in the previous article.
This example also demonstrates the use of getMenuShortcutKeyMask(). To activate menu shortcuts, Macs use the Command key, other platform use the Alt or the Control key. getMenuShortcutKeyMask(), which is part of the JDK, returns the proper key for the current platform. It’s good practice to use en Java.
Support the Application Menu
As you have seen, it’s easy to use the Aqua menu bar even with Swing. Alas supporting the application menu is more work. The application menu is very specific to Aqua and there is zero support for it in the JDK. We will have to turn to extensions that Apple ships with its JDK.
The application menu has three items that may interested your application: About, Preferences (also known as Options on some platforms) and Quit. Care must be taken not to duplicate those in the regular menus. Figure 4 illustrates what you should avoid: this application has Quit and Preferences in its regular menus whereas the user will look for them under the application menu. In practice your application should test whether it runs on a Mac and registers with the application menu accordingly.
Figure 4: Avoid duplicating menus.
The following code demonstrates how to register for the Quit menu:
import com.apple.mrj.*; // ... some code deleted if(System.getProperty("mrj.version") == null) { fileMenu.addSeparator(); fileQuit = new JMenuItem("Quit"); fileQuit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,keyModifier)); fileQuit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(promptTheUser()) System.exit(0); } }); fileMenu.add(fileQuit); } else { MRJApplicationUtils.registerQuitHandler(new MRJQuitHandler() { public void handleQuit() { SwingUtilities.invokeLater(new Runnable() { public void run() { if(promptTheUser()) System.exit(0); } }); throw new IllegalStateException("Stop Pending User Confirmation"); } }); }
Let’s review the code step by step:
Aqua will call handleQuit() when the users selects the Quit menu. An additional annoyance is that it is not safe to call AWT or Swing methods from the handler itself, you have to run user interface code in a different thread. The easiest workaround is to use SwingUtilities.invokeLater() (or EventQueue.invokeLater()) to return to the the user interface thread.
Finally handleQuit throws an IllegalStateException exception. There is a technical note from Apple on this topic. In a nutshell, Quit attempts to terminate the application unless the handler throws an IllegalStateException exception.
In the com.apple.mrj package, you will find two additional interface for the Preferences menu (MRJPrefsHandler) and the About menu (MRJAboutHandler). They do not differ much from MRJQuitHandler and should be self-explanatory.
Towards More Portable Code
Unfortunately to support the application menu, you have to turn to Apple extensions. Which means that, when deploying on other platforms, the VM needs access to the Apple class definitions to load your application. Since it’s unlikely Sun will include Apple extensions in the standard JDK, you will want to add a copy of MRJToolkitStubs.zip to the classpath. You don’t need this file on the Mac, of course.
An alternative is to organize your code so that only Mac-specific classes use the com.apple.mrj package. If you are careful not to load these classes on other platforms, you don’t need to ship MRJToolkitStubs.zip with your application.
I have found it convenient to write a small library that abstract the application menu in a more portable way and takes care of loading the MRJ extensions. The library is available at ananas.org/mac under an open source license. You may want to grab a copy.
Take Another Byte
Although pure Java applications run on the Mac without changes, you can make a few adjustements so that they run more smoothly. In the previous article, you learned how to package a Java application. In this article, you learned how to use the Aqua menus.
Benoît Marchal is a Belgian developer. He is the author of XML by Example and other XML books. He was originally attracted to Mac OS X for digital photography but he has learned to enjoy the robust Java implementation. Benoît is available to help you with your projects.