Mac Fidelity on Menus using GUI Commands

One of the things to Java/Swing developers face when trying to migrate a application to the Mac is handling the differences in how the Mac displays menus. This can particularly be the case if you’re a microISV and can’t justify the time and effort so support a platform you may rarely use. Still, the number of Mac users are growing and it would be nice to include them if you can.

So if you’re going to deploy to the Mac there are four main things that need to be addressed.

  1. Mac menu items don’t contain icons
  2. Mac menu items don’t have mnemonics.
  3. The default menu accelerator is the Command key (KeyEvent.VK_META) and not Control.
  4. Mac menus often have a different structure. For example, “Quit” is never in the “File” menu, it’s on the Mac provided application menu along with your “Preferences” (think “Tools/Options” on Windows).

These differences can be a huge pain to implement and you definitely want to avoid putting a whole stack of if (Env.isMacOS()) statements in your code. Of the four points above, GUI Commands handles the first three automatically, the fourth is handled using Mac specific configuration files that provide just the Mac specific differences.

Fixing Icons, Mnemonics and the Default Accelerator

GUI Commands automatically provides the correct rendering on the Mac without any developer intervention. The following images show how the “Edit” menu from Mr Schedule appears on both windows and the Mac with the exact same configuration.

windows-menu Menu on a Mac

GUI commands stores the details of any particular menu item in standard resource bundles and uses a relatively simple key syntax to define each value. The following is a shows the configuration for the “Cut” menu item. They basic syntax is <command-id>@<dotted-property-name>=.... The “face” prefix is used for all visual properties such as the text or icon etc.

editor-cut@face.text=Cu_t@default X
editor-cut@face.icon=classpath:images/cut.gif
editor-cut@face.description=Cuts the current selection to the clipboard (${accelerator})

GUI commands then automatically removes the mnemonic and icon and uses the correct accelerator “⌘X” when running in a Mac environment. The use of ${accelerator} in the description is also important since GUI Commands will inject the correct value for the platform, thus you’ll get “Ctrl-X” on Windows, and “⌘X” on the Mac.

Changing Menus and Other Things

So far, we haven’t had to provide any Mac specific configuration. But to remove the “Quit” item from the file menu we’ll have take a different approach. In this case we need to provide alternate configuration when running on Macs. Importantly, by using GUI Commands you only need to supply the data that changes, i.e. you won’t have any replicated configuration (the DRY principle).

In this example we’ll create a file menu that works correctly for both Windows and the Mac. First off we’ll create the default configuration file, in it we’ll define the “File” menu and an “Open” and “Quit” command. Menus are defined using the prefix group! and we’ll create our settings in a file called Commands.properties.

# The file menu
group!file-menu@face.text=_File
group!file-menu@members=open-command, separator, quit-command

# The commands
open-command@face.text=_Open@default O
quit-command@face.text=E_xit@alt F4

Now we create MacCommandVariants.properties and put anything that is different for the Mac platform. In our case the file menu has different members and the quit command has different text and a different accelerator.

# The Mac file menu (we only need to include the properties that change).
group!file-menu@members=open-command

# The quit-command has different text and accelerator on the Mac.
quit-command@face.text=Quit@default Q

Now in our code we can do the following.

// load all the commands as normal.
GuiCommands.load("Commands");
if (Env.isMacOS())
{
   // Load the Mac specific differences.  It's important to
   // do this after loading the main file so it can override
   // existing settings.
   GuiCommands.load("MacCommandVariants");
}

Using Locale Variants

Because GUI Commands configuration files are standard ResouceBundles, you can also use Locale Variants to acheive this same behaviour. In this case you’d configure you locale variant to something like “MAC” and rename your Mac settings to something like Commands_en_AU_MAC.properties. Because of the way the Locale class works, this can be an issue of you want to use the default bundle or don’t want to specify country codes. So unless you’re fully localising your app, the previous method is simpler and easier to understand.

Putting Things in the Application Menu

While GUI Commands doesn’t support this directly, the following shows how to add our Quit command to the applicaiton menu using MRJAdapter.

QuitCommand quitCommand = ...;
MRJAdapter.addQuitApplicationListener(quitCommand.getActionAdapter());

And that’s about it. When running on the Mac, our menus will behave as expected, and our “Quit” will appear in all the right places.

You can read more about GUI Commands on the website. Personal licenses cost $15 dollars US.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *