Mac Fidelity and the JOptionPane

January 3rd, 2009

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.

Mr Schedule 1.0.97 Released

October 28th, 2008

This release adds keyboard shortcuts for switching between the outline and notes editors along with a few bug fixes. Please read the release notes for more details.

You can get the new version from the download page.

Cheers
Andrew

Simplifying

October 19th, 2008

I always find it healthy to come back and use code you wrote a while ago. There’s nothing like a bit of space to help see things in a new light. Often too you can be so focused on solving one problem that you don’t get a chance to step back and see the bigger picture.

And so it was when using my binding framework the other day. Don’t get me wrong, I really like it. But the first pass gave me flexibililty I needed, but not the simplicity I needed for day to day regular use. Who really wants to create presentation models using the following?

// somewhere at startup
Pectin.registerMixin(ComponentMixin.instance());

// then when you need a presentation model
PresentationModel<ComponentValueModel> pm =
      new PresentationModel<ComponentValueModel>();

// and use the "firstName" value model..
pm.get("firstName").setEnabled(false);

What’s a ComponentValueModel anyway and why do I need one? As it turns out I almost always want one (since it gives my value models the common enabled, visible, and editable properties) so why do I have to specify it every time? Then there’s validation, you almost always need that too.

So, short story long, I’ve layered the API. The original classes have been moved to a lower level (i.e. GenericPresentationModel) and I’ve created new implementations that extend them and that come pre-configured with the default interfaces. I’ve also installed the associated Mixins automatically so for everyday use it’s now just:

// This is much better
PresentationModel pm = new PresentationModel();
pm.get("firstName").setEnabled(false);

Much nicer.

Mr Schedule 1.0 Released!

October 15th, 2008

The wait is over, Mr Schedule 1.0 is out.

For those who don’t know, Mr Schedule is a task planner, todo list and note editor, based Joel Spolsky’s Painless Software Schedules ideas.

Enjoy.

Cheers
Andrew

Farewell Tapestry and Hello GWT

June 26th, 2008

With the upcoming release of Tapestry 5 I’ve decided to take the plunge and try out GWT. I’ve long been a Tapestry fan (I can’t stand the goto development style of Struts), but this latest version has left me a little wanting. In short:

  • It’s changing dramatically again.
  • The framework now magically invokes methods if they follow particular conventions.
  • I hate method conventions. Intellij IDEA is really good at telling me all kinds of useful things. Method conventions bugger that up completely.
  • Tapestry documentation has always been lacking, this magnifies the previous gripe by a factor of 100.

Once I realised the depth of my grumpiness I decided to re-evaluate the webapp landscape. Since I’m not interested in ruby/rails I decided to give GWT a whirl.

The GWT approach is different. You don’t develop a web application with lots of pages etc, you write Javascript application which you then embed in a given web page (I.e. a bit like embedding an Applet in a page). While it’s not a solution for generating websites, it’s great for developing web applications.

What’s more, you write your Javascript in Java. Sweet. I get everything Java gives me (well a fair bit anyway) like packages, interfaces and type safe refactoring etc.

Some of the good points are:

  • The documentation is great. Well thought out and well written.
  • The shell environment is wonderful. The best out of the box web development experience I’ve had yet.
  • The client aspects of the application are written using standard client techniques. Observable models, reusable widgets, similar development approaches.
  • My server is stateless.

Things that could be better:

  • There’s no binding API. Life without binding sucks. There are apparently plans for one, but in the mean time it took me a week to migrate my Swing binding framework over to GWT.
  • It would be nice if there was an easier way to add servlet context listeners and context parameters to the shell environment.
  • Better documentation on writing compiler plugins would be nice.

Other than that, the combination of GWT in the browser and simple servlets using Guice on the backend has been a delight.

Playing with Google Guice: Look and Feel

May 12th, 2008

In this example I’m showing how I use the LookAndFeelService in conjection with the EnvironmentService to configure the LAF defaults based on the runtime environment. The LookAndFeelModule allows me to configure various look and feel aspects based on the runtime environment.

The LookAndFeelModule is the starting point. You configure it and it configures the LookAndFeelService appropriately.

// create the module
LookAndFeelModule lafModule = new LookAndFeelModule();

// set the default look and feel.
lafModule.setDefaultLookAndFeel(UIManager.getSystemLookAndFeelClassName());

// now configure environment specific lafs
lafModule.registerLookAndFeel(MacOSX.class,
                              QuaquaLookAndFeel.class.getName());

I also use a replacement for JOptionPane that supports application wide and document specific option panes as required by the Mac. This too needs to be configured based on the environment we’re running on.

// Configure the MessagePaneProvider to use by default
lafModule.setDefaultMessagePaneProvider(new GenericMessagePaneProvider());

// now configure environment specific MessagePane providers
lafModule.registerMessagePaneProvider(MacOSX.class,
                                      new MacMessagePaneProvider());

Then I simply pass the module to my application launcher. The application retrieves the LookAndFeelService and invokes its configure method.

Launcher.launch(MyApplication.class, args, lafModule);

Of course instead of repeating this in every application I’ve created a default module implementation that’s preconfigured. So all I need do is:

Launcher.launch(MyApplication.class, args, new DefaultLookAndFeelModule());

And everything just works. Nice.

Playing with Google Guice: Splash Screens

May 7th, 2008

The previous entry showed the creation of a (very) simple single frame application. Splash screens are common, so lets add one. Since there’s no easy way to do it for all occasions, we’ll use a Guice module to provide it for runtimes prior to Java 6.

public static void main(String[] args)
{
   URL splashImage = Main.class.getResource("/images/splash.png");

   // create the splash module for pre Java 6 runtimes.
   Module splashModule = Java5SplashScreenModule(splashImage);
   Launcher.launch(MyApplication.class, args, splashModule);
}

And that’s it. The framework will display the splash screen and all will work fine. Of course if you’re running on Java 6 you’d used the Java 6 version (that uses the new SplashScreen functionality). Of course if we don’t define the service in one of our modules, the framework uses the default `NullSplashScreenService`.

If MyApplicationFrame (or any other class for that matter) wanted to use the splash screen service, it just needs to inject it into it’s constructor.

public class MyApplicationFrame extends JFrame
{
   @Inject
   public MyApplicationFrame(SplashScreenService splashService)
   {
      // do stuff....
      splashService.setImageURL(nextImageURL);
   }
}

Playing with Google Guice

May 7th, 2008

It’s been a long time coming but I’ve finally started dabbling in dependency injection for my swing frameworks. It’s taken me a quite a while to get a feel for where and how it can/should be used. Given the complexity of even an average Swing application I’d been loathed to convert all my UI construction and wiring over to a container, but I’d also love to create a framework environment where I can install all my standard behaviours in one line.

I’d also been wanting to consolidate my various frameworks and utilities into a single application framework (along the lines of JSR-296) but wanted to try out the dependency injection route.

Anyway, so far I’ve been very pleased with the results. I’ve been able to create services for controlling the splash screen, determining the runtime environment and providing runtime environment services, configuring Look and Feel settings and so on. With Guice you can easily create default implementations of your services so you can have the basics going with zero configuration.

So the following useless example shows what’s necessary to get a frame up and create a file in the default application data directory.

public class MyApplication
extends SingleFrameApplication
{
   public MyApplication()
   {
      super("MyApplication", MyApplicationFrame.class);
   }

   public void initialise(String[] args)
   {
      // The EnvironemntService ensures it's the correct location
      // based on the runtime platform.. i.e. `C:\AppData\MyApplication`
      // on windows and `~/Library/Application Support/MyApplication`
      // on the Mac.

      // AbstractApplication provides getters for standard services so
      // you don't have to magically discern their existence.
      EnvironmentService env = getEnvironmentServices();
      File logFile = env.getFileInAppDataDirectory("logs/my-app.log");

      // do stuff with the file...
      ....
   }
}

Then the application can be simply started using the following.

public static void main(String[] args)
{
   Launcher.launch(MyApplication.class, args);
}

Why I generally avoid GUI Builders

February 5th, 2008

How’s that for a non committal title (c:

Most of my own thoughts have been better explained by others.  Beware the GUI Builder is an excellent article on Hacknot, and a response by Karsten Lentzsch to this post sums up the design issues well (search for “Karsten Lentzsch” and you’ll find his comment).

The meta design issues described by Karsten are a major sticking point for me. The implication is that if you use GUI builders and your UI design requires that the label/component gap be 4 DLUs, or that section titles should have 12 DLUs of top padding, then every developer must know every rule and ensure they implement it correctly on every form. You will have problems.

Custom layout builders on the other hand don’t generally suffer the same fate. They’re able to capture the design rules in one place (i.e. the DRY principle) and your developers can live blissfully unaware of the nitty gritty requirements or implementation issues.

For larger projects you can also specialise the builders and integrate them with other infrastructure such as a binding or command framework.

//  a trivial example that doesn't really make sense.
SimpleFormBuilder b = new SimpleFormBuilder();
b.setFormTitle("My Cool Form");
b.addTextField("First _Name", firstNameValueModel);

Of course the real problems with layout builders is that you have to design and write them, and there will always be cases that break the rules. You can minimize this pain however if you start with a decent builder and layer your architecture so developers can drop down to “manual” for the edge cases.

This then is the real issue for me: if you only have a few static screens, then a GUI builder is probably an acceptable option, but once your project grows you’re heading for trouble.

Private Mixin Interfaces

September 25th, 2007

One additional feature of my binding library is that a Mixin can register any number of interfaces to a presentation model. This allows the Mixin to define implementation properties that aren’t visible to the user. Thus code completion on the ValueModels returned from the PresentationModel won’t show the private properties.

I use this feature in the ValidationMixin. The mixin registers its own interfaces with the PresentationModel being validated. This means you don’t even need to define that your PresentationModel vends ValidatableValueModels. The following is an example.

// Create a model that vends ComponentValueModels defined by
// the ComponentMixin.
public class PersonPM
extends BeanPresentationModel
{
   public PersonPM()
   {
      // we have to pass the classes in because of erasure..
      super(Person.class, ComponentValueModel.class);   

     // create a validation engine, this will install the required
     // mixin interfaces to the presentation model.
     engine = new ValidationEngine(this);  

     // now we can add some values..
     installValueModel("firstName");

     // now configure the validators, these will use the private
     // interfaces defined by the validation mixin.
     engine.getValidationManager("firstName")
           .setMandatory("Please enter your first name")
           .addValidator(new NameValidator());
  }
}