There's nothing wrong with not taking the trouble to make your application look good on a Mac, but by going the extra mile, your work will truly look as though it belonged on a mac, while not having to maintain a separate version.
The answer can be found at Apple's developer site in Technical Note 2042. In particular, we're interested in the section that talks about the Application Menu.
The Application Menu is a menu that exists on the mac by default, and has some actions associated with it. The application should register handlers for those menu items and not put coresponding menu items elsewhere in the JMainMenuBar.
The code to register these events looks something like this:
import com.apple.mrj.*; class SpecialMacHandler implements MRJQuitHandler, MRJPrefsHandler, MRJAboutHandler { Program us; public SpecialMacHandler(Program theProgram) { us = theProgram; System.setProperty("com.apple.macos.useScreenMenubar", "true"); System.setProperty("com.apple.mrj.application.apple.menu.about.name", "MyProgram"); MRJApplicationUtils.registerAboutHandler(this); MRJApplicationUtils.registerPrefsHandler(this); MRJApplicationUtils.registerQuitHandler(this); } void HandleAbout() { us.About(); } void HandlePrefs() { us.Prefs(); } void HandleQuit() { SwingUtilities.invokeLater(new Runnable() { public void run() { us.Quit(); } }); throw new IllegalStateException("Let the quit handler do it"); } }In that snippet,
Program
represents the class of the main program. It
must have public methods for About
, Prefs
and
Quit
somewhere in it that
do whatever you require for those menu items.
Incidently, the need to wrap the HandleQuit innards with
SwingUtilities.invokeLater()
is a workaround for a known
bug in the MacOS X JRE. It was fixed in Jaguar, but the workaround is
harmless and avoids problems on older JREs.
Of course, this code will not even compile properly on non-Mac
platforms. To get it to compile, you need to supply a dummy
public class MRJApplicationUtils
implementation. But you
can't package the dummy implementation with your code, because it might
be prefered on a mac in place of the system implementation. But if you
don't include an implementation of those classes, then this class will
fail to link at run-time on non-macs. Oh my! What a quandry!
Update! I am told that the classloader indeed
will guarantee to prefer the system implementation over a shim
MRJApplicationUtils
implementation in the jar, so I guess
another way to do this is to simply provide an implementation of
MRJApplicationUtils that has empty methods. It turns out, though, that
you'll also have to provide dummy interface classes for all of the
arguments to the register*Handler()
methods if you do this.
This is the key. So long as no other class in our program directly references our "contaminated" class,
we won't wind up depending on the com.apple.mrj
classes
(which don't exist on non-mac platforms). But we need an instance of
the contaminated class on the acceptable platform. That is, we really
want to do this:
if (isMac()) { new SpecialMacHandler(this); }but we will land in hot water because the class loader doesn't know what
isMac()
will return. It
will only see that SpecialMacHandler
wants
com.apple.mrj.MRJApplicationUtils
and fail to start the
program on non-Macs. We must achieve the same end without
referencing the class directly in the code. Reflection offers us the
way. The reflected version of the same code looks like this:
if (isMac()) { try { Object[] args = { this }; Class[] arglist = { Program.class }; Class mac_class = class.forName("SpecialMacHandler"); Constructor new_one = mac_class.getConstructor(arglist); new_one.newInstance(args); } catch(Exception e) { System.out.println(e); } }(The use of
this
in args
implies that we are
either in the constructor for or a method of class Program
)This will give us a new instance of the special mac handler when we're on a mac, but not anywhere else.
Oh, incidently, here's how you tell if you're on a mac (again, stright from TN2042):
public boolean isMac() { return System.getProperty("mrj.version") != null; }This will let you do things like remove the About, Prefs and Quit menu items from your
JMenuBar
.