Mac Fidelity and the JOptionPane

The slogan of write once run anywhere definitely has it’s limits. In slowing migrating Mr Schedule toward the Apple Human Interface Guidelines the JOptionPane is one of those limits. There are a couple of Mac specific requirements that it just doesn’t support. Amongst other things they include:

  1. The Mac supports document model dialogs that are displayed as a sheet.
  2. The Mac supports the notion of a “destructive option”, things like “Discard Changes”. These options are displayed physically distant from non other options like “Save” or “Cancel”. This is a perfect application of the proximity principle in visual design.
  3. Mac dialogs don’t have a title.
  4. Mac dialogs should display only the application’s icon and not the regular question or exclamation icons.

The following example shows a document model dialog with a destructive option.

Mac Dialog Example

Mac Dialog Example

Now the excellent Quaqua library already does much of the work for us including providing a sheet style dialog implementation, but there are a few things that are still missing for easy use in a production environment.

  1. Quaqua does’t provide a unified API for all platforms, it’s still up to the developer to write Mac specific code
  2. The document model sheet implementation isn’t a blocking call. This adds to the level of Mac specific code you need to write as well as adding an additional threading concern.

Now to overcome this we need some kind of framework that delivers following:

  1. An encapsulation of all the data required to show a message to the user including the default and destructive options.
  2. A standard mechanism to display the message to the user with well defined semantics for the developer (i.e. does this call block or not?)
  3. A platform specific implementation the does the “right thing” for the platform.
  4. A factory that automatically gives us the correct implementation based on the current platform

Creating Your Message
Most of this isn’t too hard, and pays off enormously once you use it in a second application. In my code I’ve created a Message object that holds the message title, the main and information messages along with the options available (including if they’re the default or destructive option).

Message myMessage = new Message("This is the title"); 
myMessage.setMainMessage("Do you want to save the changes to 
                          this message before closing?");
myMessage.setInfoMessage("If you don't save, your changes will 
                          be lost");
// now add the options..
myMessage.addDefaultOption(Option.SAVE);
myMessage.addOption(Option.CANEL);
myMessage.addDestructiveOption(Option.DISCARD_CHANGES);

This creates everything we need to know about the message. I’ve also created Option object instead of the JOptionPane integer approach since objects are nice and play better when using resource bundles.

Displaying the Message
Now we need a mechanism for displaying the message. To do this I’ve created a MessagePane class that acts as both an abstract base class and provides static factory methods for accessing the correct implementation for the platform. You could of course separate these roles, but I chose not to. The concrete instances of MessagePane are created by a service provider interface called MessagePaneProvider. In my world this is configured using Google Guice, but you could just as easily do something like MessagePane.setMessagePaneProvider(...) if you’re not doing the injection thing.

Once our MessagePaneProvider has been configured we can now simply go:

// get a document message pane instance
MessagePane pane = MessagePane.getDocumentInstance(myFrame);

// now call to show to display it.  This call will block on
//  all platforms
Result result = pane.show(myMessage);

// now check the result
if (result.userChose(Option.SAVE))
{
   // save our document...
}
else if (result.userChose(Option.CANCEL))
{
   // cancel...
}

For the Mac this will use a MessagePane instance that displays a JSheet and uses some Foxtrot magic to make it a blocking call.

The above example also highlights the shows of a Result object instead of JOptionPanes integer approach. I find result.userChose(Option.SAVE) much more readable for a start, but more importantly result.userChose(…) throws an exception if passed an option that was never included in the message.

JSheet and Foxtrot Magic
The only really tricky aspect of the design was getting the JSheet call to block. The approach I’ve used uses Foxtrot to block the “show” method until until the JSheet callback has been invoked.

public Result
show(Message message)
{
   // we use a JOptionPane to do the work.  This method creates
   // and configures the JOptionPane appropriately for the platform.
   JOptionPane pane = createOptionPane(message);
      
   // Quaqua's JSheet is non blocking so we create a blocking
   // queque that foxtrot can block on until the selected option
   // is placed in queue by the JSheet callback.
   final ArrayBlockingQueue

Summary
So just a little bit of thought and effort you can create an API that is consistent and simple for the developer and pushes as many of the platform specific details as possible into the framework implementation.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

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