ToolbarBuilder – Use the builder pattern to create toolbars

Recently I discovered  Jeremys uber-ButtonUI’s. He provides UI classes to mimic Mac OS Button looks (and more) including segmented Buttons. The UI’s are nice, but while playing around with, I thought a builder approach would fit to easily create toolbars of buttons and components. Basically the problem is that segmentation configuration depends on Button order ( ;) ), so changing the order of elements within a toolbar now also involves updating the segmentation configuration. Jeremy provides a nice installUI method that helps adding the appropriate button UIs, but I thought this could be improved further and I wanted to play with the builder pattern and find a way to make it extensible without copy/pasting the whole builder.

The basic idea is straight forward. Create a builder, add components, get the toolbar from the builder. This is how it looks:

simple-toolbar-1

And this is how it feels:

JComponent toolbar = new ToolbarBuilder()
        .add(new JButton("A"))
        .add(new JButton[]{new JButton("B"), new JButton("C"), new JButton("D")})
        .addSeperator()
        .add(new JButton("E"))
        .getToolbar();

Nothing special so far. Except for the method concatenation and the Button[] support, this could be done easily with a default toolbar. The builder supports some helper methods to make life easier when adding Button[], a ButtonGroup or Actions.

Now lets see what happens when we use one of Jeremys ButtonUI classes and activate segmentation.

segmented-toolbar-1

You see the nice mac like segmentation. To achieve this, we had to change the builder code slightly:

JComponent toolbar = new ToolbarBuilder()
        .withUI(TexturedButtonUI.class)
        .withSegmentation(true)
        .add(new JButton("A"))
        .add(new JButton[]{new JButton("B"), new JButton("C"), new JButton("D")})
        .addSeperator()
        .createGroup()
           .add(new JButton("E"))
           .add(new JButton("F"))
        .close()
        .getToolbar();

The UI class is switched to TexturedButtonUI and segmentation is turned on. To get an idea of group creation, we added an “F” button to create another group.

Notice that the builders add() method that takes Button[], ButtonGroup, or Action[] automatically create segmented groups. The second way is createGroup(), which creates and returns a builder that groups buttons and components. The segmentation is completely controlled by the  builder and the order of components just depends on order you add your buttons and components.

Another point to notice here is the ComponentFactory supported by the builder. Its not shown in the example, but you are not stuck with JToolBar – you can pass in any Component in the ToolbarBuilder constructor and, using a custom ComponentFactory, you can control how Buttons and group containers are created.

Now, up to this point I like the builder. I can pass in by own container, use custom UIs for my buttons and also get buttons created from Actions automatically. I can created groups of segmented buttons and other components. But, I soon realized that I use two other types of toolbars regularly. Jeremys Customizable toolbar and Ken Orrs UnifiedToolbar from the Mac Widgets Project. And that’s where the trouble began. One could write two completely separated builders for the two, but, I yell at my students if they don’t follow the DRY principles so I should try to stick with my own rules and find a way to extend the existing builder. Before I explain the problem, here are the results.

customized-toolbar

This is the customized toolbar created with the following code:

JButton customize = new JButton("Customize...");
final CustomizedToolbar toolbar = new CustomizableToolbarBuilder("toolbar demo")
        .withSegmentation(true)
        .withUI(TexturedButtonUI.class)
        .add(customize)
        .add(new JCheckBox("Check box"))
        .add(new JLabel("Label"))
        .add(new JButton("Button"))
        .add(new JButton("Hidden"))
        .hide()
        .createGroupContainer()
           .add(new JButton("g1"))
           .add(new JButton("g2"))
        .close()
        .getToolbar();

Fairly straight forward. Only one new method in use ( .hide() ) which removes the last added component from the default layout. In this case the “Hidden” button is not part of the default layout of the toolbar. This time the builder was a success as it really simplifies customizable toolbar creation.

For the UnifiedToolbar – please don’t take it too serious – Mac Widget comes with a good UnifiedToolbarBuilder and this was more of a test to see how one can extend the existing builder without too much hassle. So, lets take a look at the result.

unified-toolbar-buttonui

This almost looks like Kens original example, except that I used Jeremys ButtonUI. Here is the code to create the UnifiedToolbar above. Notice that I left out the component creation, but in contrast to Kens example, the component creation is straight forward. I don’t set any properties or apply any UI changes.

JComponent toolBar = new UnifiedToolbarBuilder()
        .createGroup("View")
           .withUI(TexturedButtonUI.class)
           .add(leftButton)
           .add(rightButton)
        .close()
        .center()
            .add(new JButton("MobileMe", blueGlobeIcon))
            .add(greyGlobeButton)
            .add(new JButton("Advanced", gear))
        .right()
            .createGroup("Search", textField)
        .getToolbar();

A few things to note here. The UnifiedToobarBuilder adds components to the left by default. Component placement can be switched using center() or right() ( there is also a left() ;) ). The default UI class applied to buttons is UnifiedToolbarButtonUI that comes with MacWidgets. The builder comes with two new createGroup() methods that can be used to create “labeled” groups (i.e. the View group). As you can see, we used the TexturedButtonUI implementation again, but this time inside a group. Such configurations are always restricted to the group. This enables changing group specific parameters, without any changes to the rest of the toolbar and works for all .withXXX() methods.

To recreate the original example, switch to a null UI within the view group ( .withUI(null) ). This prevents the default UnifiedToolbarButtonUI to be applied and you will end up with (mac only) segmented buttons again. The appropriate properties are applied to all buttons automatically because the constructor of UnifiedToolbarBuilder contains a line:

withProperty("JButton.buttonType", "segmentedTextured");

This applies the property to all added components. Use the withProperty() method to change the default properties.

Builder hierarchies

Ok. So far so good, I am happy with the result :) But I didn’t expect so much trouble on the way. The basic idea is straight forward, but the tricky part is extending the default ToolbarBuilder.  It is not enough to change the existing implementation by overriding some methods. That would be easy and works as expected. Problems arise with new methods in the extending builder.  Well, that should not be a problem. And it isn’t. At least not when we live with the drawback of loosing the ability to concatenate method calls and change the group creation to some other implementation. Ok, ok, example:

class A{
    public A methodA(){return this;}
}
class B extends A{
    public B methodB(){return this;};
}

Remember – we want to get the builder back from the method calls. This is how we can concatenate calls. In the simple scenario above, we already have a problem. new B().methodA() returns an instance of type A. From that point on, we have lost access to B and all methods in B. I know that the returned instance is actually of type B and could cast. But that doesn’t feel right because I have to assume things about the implementation that might change over time. The other thing is to give up the concatenation B b = new B(); b.methodA(); b.methodB() ... will work. Well, as long as the returned instance is of no special interest and we can be sure that it is always our ‘b’. In case of the toolbar builders, that not the case. For example, if you create groups using .createGroup(), the method returns a NEW builder instance. Call close() on that new instance and you are back in the original builder. But, as I said, this is nothing I want to know and it should be hidden from the user.
What I was looking for was a way to include the actual type of the returned builder instance and the only way I found was using generics. The generic information passed on to the actual implementations looks really really ugly (I will show you the base ToolbarBuilder in a second), but enables encoding the actual return type in all methods.

class A<T extends A>{
    public T methodA(){return (T) this;}
}
class B extends A<B>{
    public B methodB(){return this;};
}

This time, B extends A and tells it that the “global” return type should be “B”. We have to do a cast now, but thats internal and when you use the class you won’t notice (I also think the cast is fairly safe). With this structure in hand, we can do new B().methodA().methodB();. Notice that methodA() now returns an instance of type B, so we do not loose any functionality while calling methods in the builder hierarchy.
There is a major drawback tough. What if I now want to extend B ? With the example above I would be stuck in the original problem, because B is not generified and will only return instances of type B instead of, say C. We could put generics on B too and create another sub class:

class A<T extends A>{
    public T methodA(){return (T) this;}
}
class B<T extends B> extends A<T>{
    public T methodB(){return (T) this;};
}
class C<T extends C> extends B<T>{
    public T methodC(){return (T) this;};
}

This time we already generified C, but the whole thing contains another problem. Now we have to provide the generic parameter to the constructor even if it looks silly : new C<C>().methodB() returns C. Without the generic information, you will only get back a B  ( new C().methodB() returns B) due to automatic type inference.

Anyways, its ugly and I don’t want to force a new UnifiedToolbarBuilder<UnifiedToolbarBuilder>() so the final Builder classes are not generified. (They are not final though, you can still extend and override methods). There are abstract classes that carry the generic information and can be used to create new builders.

If you find the time, just try it :) And of course, feedback, comments and suggestions are welcome !!

Download

The ToolbarBuilder is released under the BSD License.

You can get the ToolbarBuilder.jar file here.

The sources are provided here.

API Documentaiton can be found here.

Remember to put MacWidgets , the ButtonUI and/or the Customizable Toolbar in your ClassPath. The ToolbarBuilder was tested with Java 1.5 and 1.6.

Tags: , ,

Monday, September 28th, 2009 Swing

No comments yet.

Leave a comment