Snippet: java_osx Index

Developing an OSX Application in Java

This article is incomplete

I haven't found any good development guides for Java apps on OSX so I thought I'd write one myself.

I want this article to be both a guide and a reference. I'll try to add/edit info as I learn it through experimentation. If you have any info you think belongs in this article feel free to contact me.

Someone suggested I write a demo app that is fully OS X capable. When I feel like I've aggregated enough information here I will definitely do that.

All of the Apple related classes are located in the com.apple.eawt package which should come with your Mac JDK.

There quite a few classes in the com.apple.eawt package but the most important one is Application. This class lets you get an Application instance through the static getApplication() method which represents your app on OSX. Through this instance you'll be able to add all kinds of cool functionality which will make your app feel a lot more native to OSX.

Throughout this article I'll be referencing macApplication which is simply one such Application instance:

Application macApplication = Application.getApplication();

Events

So the whole com.apple.eawt system abstractifies user interaction with OSX specific app components (like menubar items, I'll get to that) as well operations originating from the OS by throwing events. This abstractification is a bit convoluted and took me quite a bit to wrap my head around.

The com.apple.eawt.AppEvent class contains all the different OSX specific events that might get thrown (in the form of abstract subclasses).

I've separated these events into different categories:

Menu Bar Item Events

App State Events

Computer State Events

The names of these events should pretty clearly indicate their purpose.

Miscellaneous Events (aka I'm not exactly sure what these do)

Event Handlers

Here's where things get confusing.

Allow me to compare the com.apple.eawt event system to Swing's event system:

com.apple.eawt provides handlers for some events.

Here are the handlers available:

Implementing these handlers is pretty straightforward (demonstrated with com.apple.eawt.PreferencesHandler and com.apple.eawt.AboutHandler in the next section).

The events that don't have handlers instead have listeners, I'm not sure why... Maybe "handlers" are for user input whereas "listeners" are for OS induced operations? Seems more confusing and redundant than anything else.

Here are the listeners available:

All these listeners extend com.apple.eawt.AppEventListener.

Register these listeners through macApplication.addAppEventListener(AppEventListener paramAppEventListener).

You can write your own listener that extends com.apple.eawt.AppEventListener but attempting to register it will result in nothing happening. Only the above 6 listeners can be registered. This should be obvious since the only events you're catching with this system are being thrown by the OS, which means you can't create your own events and therefore have no reason to create your own listeners.

"Catch All" Adapter

After understanding how the com.apple.eawt event system works I felt it was only appropriate to quickly write up and share an event adapter that catches everything. I realize it's not very elegant but it "just makes sense".

I'm including it here as well as in the com.moomoohk.Mootilities.OSUtils.MacOSX package of Mootilities (a pretty handy project of mine that you should totally check out):

import com.apple.eawt.AboutHandler;
import com.apple.eawt.AppEvent.AboutEvent;
import com.apple.eawt.AppEvent.AppForegroundEvent;
import com.apple.eawt.AppEvent.AppHiddenEvent;
import com.apple.eawt.AppEvent.AppReOpenedEvent;
import com.apple.eawt.AppEvent.OpenFilesEvent;
import com.apple.eawt.AppEvent.OpenURIEvent;
import com.apple.eawt.AppEvent.PreferencesEvent;
import com.apple.eawt.AppEvent.PrintFilesEvent;
import com.apple.eawt.AppEvent.QuitEvent;
import com.apple.eawt.AppEvent.ScreenSleepEvent;
import com.apple.eawt.AppEvent.SystemSleepEvent;
import com.apple.eawt.AppEvent.UserSessionEvent;
import com.apple.eawt.AppForegroundListener;
import com.apple.eawt.AppHiddenListener;
import com.apple.eawt.AppReOpenedListener;
import com.apple.eawt.Application;
import com.apple.eawt.OpenFilesHandler;
import com.apple.eawt.OpenURIHandler;
import com.apple.eawt.PreferencesHandler;
import com.apple.eawt.PrintFilesHandler;
import com.apple.eawt.QuitHandler;
import com.apple.eawt.QuitResponse;
import com.apple.eawt.ScreenSleepListener;
import com.apple.eawt.SystemSleepListener;
import com.apple.eawt.UserSessionListener;

/**
 * An event adapter meant to catch all the com.apple.eawt events.
 * 
 * @author Meshulam Silk (moomoohk@ymail.com)
 * @since Sep 27, 2014
 */
public abstract class MacOSXAppEventAdapter implements AboutHandler, PreferencesHandler, PrintFilesHandler, OpenFilesHandler, OpenURIHandler, QuitHandler, AppReOpenedListener, AppForegroundListener, AppHiddenListener, UserSessionListener, ScreenSleepListener, SystemSleepListener
{
    /**
     * Constructor
     * 
     * Registers this instance with the com.apple.eawt system
     */
    public MacOSXAppEventAdapter()
    {
        Application.getApplication().addAppEventListener(this);
        Application.getApplication().setAboutHandler(this);
        Application.getApplication().setPreferencesHandler(this);
        Application.getApplication().setPrintFileHandler(this);
        Application.getApplication().setOpenFileHandler(this);
        Application.getApplication().setOpenURIHandler(this);
        Application.getApplication().setQuitHandler(this);
    }

    @Override
    public abstract void systemAboutToSleep(SystemSleepEvent paramSystemSleepEvent);

    @Override
    public abstract void systemAwoke(SystemSleepEvent paramSystemSleepEvent);

    @Override
    public abstract void screenAboutToSleep(ScreenSleepEvent paramScreenSleepEvent);

    @Override
    public abstract void screenAwoke(ScreenSleepEvent paramScreenSleepEvent);

    @Override
    public abstract void userSessionDeactivated(UserSessionEvent paramUserSessionEvent);

    @Override
    public abstract void userSessionActivated(UserSessionEvent paramUserSessionEvent);

    @Override
    public abstract void appHidden(AppHiddenEvent paramAppHiddenEvent);

    @Override
    public abstract void appUnhidden(AppHiddenEvent paramAppHiddenEvent);

    @Override
    public abstract void appRaisedToForeground(AppForegroundEvent paramAppForegroundEvent);

    @Override
    public abstract void appMovedToBackground(AppForegroundEvent paramAppForegroundEvent);

    @Override
    public abstract void appReOpened(AppReOpenedEvent paramAppReOpenedEvent);

    @Override
    public abstract void handleQuitRequestWith(QuitEvent arg0, QuitResponse arg1);

    @Override
    public abstract void openURI(OpenURIEvent paramOpenURIEvent);

    @Override
    public abstract void openFiles(OpenFilesEvent paramOpenFilesEvent);

    @Override
    public abstract void handlePreferences(PreferencesEvent paramPreferencesEvent);

    @Override
    public abstract void printFiles(PrintFilesEvent paramPrintFilesEvent);

    @Override
    public abstract void handleAbout(AboutEvent paramAboutEvent);
}

Menu Items

The first thing you'll want to do is add and customize the default OSX menu items (About, Preferences, etc.).

It was super confusing to figure out due to all the outdated tutorials and docs I've found online coupled with the fact that a lot of classes are deprecated.

You'll notice that by default your app bundle doesn't have a "Preferences" item in the main app menu. I assume this is because most people won't implement their own preferences implementation. Most (if not all) of the guides I came across online state that you should call macApplication.setEnabledPreferencesMenu(true); or macApplication.addPreferencesMenuItem() to get the item to show up. However, these methods are deprecated and I therefore recommend you don't use them.

To get the "Preferences" item to show up all you seemingly need to do is implement a handler for that item, like so:

macApplication.setPreferencesHandler(new PreferencesHandler()
{
    @Override
    public void handlePreferences(PreferencesEvent paramPreferencesEvent)
    {
        // Make preferences window appear here
    }
});

This appears to be applicable to the "About" menu item as well:

macApplication.setAboutHandler(new AboutHandler()
{
    @Override
    public void handleAbout(AboutEvent paramAboutEvent)
    {
        // Make about window appear here
    }
});

This code should replace macApplication.setEnabledAboutMenuItem(true); and macApplication.addAboutMenuItem();.

Fullscreen

In Mac OS X Lion (10.7) Apple introduced full screen apps.

Calling FullScreenUtilities.setWindowCanFullScreen(frame, true);, where frame is your window object (as in, extends java.awt.Window. This includes javax.swing.JFrame), will enable this functionality.

Before enabling full screen:

After enabling full screen:

To programmatically toggle full screen mode call macApplication.requestToggleFullScreen(frame);. There's no way to set the fullscreen state manually, only a toggle.

Note

I'm not sure how com.apple.eawt handles these calls on systems older than 10.7. Here's a more complete example with a (possibly redundant) check to make sure the system is at least Mac OS X Lion:

if (System.getProperty("os.name").startsWith("Mac OS X"))
{
    String osVer = System.getProperty("os.version");
    int major = Integer.parseInt(osVer.substring(0, osVer.indexOf(".")));
    osVer = osVer.substring(osVer.indexOf(".") + 1);
    int minor = Integer.parseInt(osVer.substring(0, osVer.indexOf(".")));
    if (major >= 10 && minor >= 7)
    {
        FullScreenUtilities.setWindowCanFullScreen(frame, true);
        macApplication.requestToggleFullScreen(frame);
    }
}

Notification Center

com.apple.eawt does not provide any way to send NC notifications.

Apple implemented NC in Mountain Lion (10.8), one version after Lion (10.7). Since com.apple.eawt includes full screen capabilities I can only assume that the package hasn't been updated since Lion was a thing, which would be 2011. This might explain why there's no NC capabilities in com.apple.eawt. Here's to hoping that gets patched in before long.

Workarounds/Alternatives

Unfortunately I haven't tried most of these solutions so I can't really provide much more than information I've found online (for now).

Ideally we shouldn't have to go through all this trouble.

Setting The Menu Bar Title (App Name)

This following approach doesn't seem to work with later combinations of Java and OS X. I'm not sure whether it's the versions of Java I tried with (1.6/1.7) or my OS (Mavericks). I can't find a way to set the menu bar title on my setup.

All the guides that I came across stated that you have to set the com.apple.mrj.application.apple.menu.about.name property to the name of your app and it should work.

Now that will indeed work right off the bat if you set the property through command line, but if you're like me you'll be wanting to do it programmatically:

System.setProperty("com.apple.mrj.application.apple.menu.about.name", "This is my app name!");

Here's where it gets quirky: Apparently this won't work if you make the call after you either interact with java.awt.Toolkit or set your app's look and feel.

So just make the call first thing in the main right? Not quite. Interacting with java.awt.Toolkit includes loading a class that extends javax.swing.JFrame. This means that if your main method is in a subclass of javax.swing.JFrame it won't work (since the class is loaded right away)!

To remedy this simply extract your main method to a separate class and you're good to go:

public class Launcher
{
    public static void main(String[] args)
    {
        System.setProperty("com.apple.mrj.application.apple.menu.about.name", "This is my app");
        MyJFrame frame = new MyJFrame();
        frame.setVisible(true);
    }
}

I also found mention of setting the -Xdock:name nonstandard JVM flag to your title:

-Xdock:name="This is my app"

You can set this through the "VM Options" section when exporting your Mac Application Bundle in Eclipse.

This approach doesn't appear to work either as a result of a bug with Java. It has been reported here and here

Effectively, there currently appears to be no way to set the menu bar title in later versions of OS X coupled with later versions of Java.

Multitouch Gesture Handling

Multitouch gestures are what I've been missing in my Java apps all these years.

Luckily the com.apple.eawt.event package provides a system to handle gestures.

Unluckily it doesn't appear to work. As mentioned above, I'm on 10.9.5 and I've tested these features on 10.7.5.

Events to listen for:

Listeners to use:

All 4 listeners implement the com.apple.eawt.event.GestureListener interface.

Listeners are attached to javax.swing.JComponents through the com.apple.eawt.event.GestureUtilities.addGestureListenerTo(javax.swing.JComponent component, com.apple.eawt.event.GestureListener listener) method

Here's an example implementation of all these event listeners:

import javax.swing.JPanel;

import com.apple.eawt.event.GesturePhaseEvent;
import com.apple.eawt.event.GesturePhaseListener;
import com.apple.eawt.event.GestureUtilities;
import com.apple.eawt.event.MagnificationEvent;
import com.apple.eawt.event.MagnificationListener;
import com.apple.eawt.event.RotationEvent;
import com.apple.eawt.event.RotationListener;
import com.apple.eawt.event.SwipeEvent;
import com.apple.eawt.event.SwipeListener;

public class GestureListenerTest
{
    JPanel p = new JPanel();

    public static void main(String[] args)
    {    
        GestureUtilities.addGestureListenerTo(p, new SwipeListener()
        {
            @Override
            public void swipedDown(SwipeEvent e)
            {
                System.out.println("Down");
            }

                        @Override
            public void swipedLeft(SwipeEvent e)
            {
                System.out.println("Left");
            }

            @Override
            public void swipedRight(SwipeEvent e)
            {
                System.out.println("Right");
            }

            @Override
            public void swipedUp(SwipeEvent e)
            {
                System.out.println("Up");
            }
        });

        GestureUtilities.addGestureListenerTo(p, new MagnificationListener()
        {
            @Override
            public void magnify(MagnificationEvent e)
            {
                System.out.println(e.getMagnification());
            }
        });

        GestureUtilities.addGestureListenerTo(p, new RotationListener()
        {
            @Override
            public void rotate(RotationEvent e)
            {
                System.out.println(e.getRotation());
            }
        });

          GestureUtilities.addGestureListenerTo(p, new GesturePhaseListener()
        {
            @Override
            public void gestureBegan(GesturePhaseEvent e)
            {
                System.out.println("Begin");
            }

            @Override
            public void gestureEnded(GesturePhaseEvent e)
            {
                System.out.println("End");
            }
        });
    }
}

The com.apple.eawt.event.GestureAdapter class is an adapter for all the possible gesture events.

Again, I haven't been able to get any of these events to fire on my setup.

Auto Hiding Scrollbars

In Mac OS X Lion (10.7) Apple introduced auto hiding scroll bars.

Unfortunately this scroll bar style isn't available out of the box (even with the OS X Look and Feel set).

I did come across this workaround a while ago but I haven't tested it extensively.

Documentation and Source Code

The only generated JavaDocs I've found online can be found here.

Source code (with inline JavaDocs) can be found here.

When I get the chance I'll see if I can regenerate JavaDocs from the source and place them on my site in case CodeRanch decides to stop hosting it.

My Beef With com.apple.eawt

Throughout the article I've tried to keep my personal opinions on the com.apple.eawt package bottled up but I feel like some aspects of it that I find too problematic to ignore.

Document Changelog

Share: circlefacebook circletwitterbird circlegoogleplus circlereddit