Sunday, November 16, 2008

My presentation on JSR-296 at Jazoon 08

I have just seen that Jazoon has removed all presentations files of Jazoon 08 talks.
That's the occasion for me to post the content of my talk on "JSR-296 Swing AppFramework from the trenches".

The full content (in PPT format) is here (I have just removed the Jazoon template stuff). If you don't have PowerPoint, here is a PDF version (unfortunately without the original animations). Unfortunately I could find no way to convert it to ODP format (for OpenOffice users) without big losses in animations but also in diagrams (most of them got terribly ugly after conversion). I am afraid I'll have to give up on that one (if anyone can convert the original PPT to ODP please email me the result and I'll post it here).

If you want to have an idea of the content before download, here is the agenda:
  • Introduction
    • why do we need a Swing framework
    • what is JSR-296? what does it bring (in a nutshell)?
    • about the presented applications
  • Application Lifecycle
    • standard application lifecycle
    • adding dependency injection (Spring, Hivemind, Guice)
    • adding docking capability (MyDoggy)
    • tips & pitfalls
  • Resources & I18N
    • resource injection principles
    • what about JTable?
    • tips & pitfalls
  • Actions & Tasks
    • a better, more responsive, javax.swing.Action
    • where should I put my @Action?
    • when should I use a Task?
    • what about Exception handling?
    • tips & pitfalls
  • Persistent Session State
    • state restoration (or users' happiness)
    • what is stored? what is not? where?
    • tips & pitfalls
  • What is missing for a complete GUI framework?
    • GUI framework architecture
    • Example: EL4J Swing stack
  • Future
    • JSR-296 standpoint
    • what's likely to change?
Please note that, considering the recent events about Sun in general, and Swing in particular, the "Future" section of the presentation is probably largely obsolete:-(
Still, I believe that Swing is a better platform for GUI development than many others (not just talking about Java here), and developers will still use it for a couple of years more (maybe not in pure Java, though, other options exist: Groovy, Scala). Wait and see...

Enjoy!

Wednesday, November 05, 2008

Announce: DesignGridLayout 1.0-rc1 released!

I am particulaly glad to announce the first release candidate of DesignGridLayout V1.0.

DesignGridLayout is a Swing LayoutManager, revolutionary by its API, simple but powerful. Its main advantages are:
  • Good looking forms (alignment, spacing, sizing, visual balance): this is entirely taken over by DesignGridLayout itself without any special hint from the developer
  • Reduced learning curve for developers, thanks to its fluent API which is simple, effective, compile-safe and IDE code-completion friendly
  • No graphics designer needed: the API is the graphical designer
  • Readability and maintainability: you can literally "visualize" the layout by browsing the code that sets it up; inserting a new row of components is done by simply inserting a new line of code in the layout setup code...
  • Free: the project is open source and released under Apache License 2.0
Version 1.0-rc1 is available here or through the java net maven 2 repository (for more info, you can check my previous post and replace "0.9" with "1.0-rc1").
This version brings the following improvements:
  • #13: support for multiple groups of fields, each with its own label column
  • #5: smart vertical resize: DesignGridLayout automatically determines which rows should grow vertically and also make sure that components height is suitable to display an entire line of information (useful for JList, JTable, JTextArea)
  • #18: smarter horizontal resize behavior: now DesignGridLayout won't resize components under their minimum size
  • #9: automatic support of right-to-left text orientation based on Locale
  • #16: smarter gaps for empty rows
  • #15: resolution independence
  • #20: fixed ugly layout problem when container has a border
  • #12: now setLayout() is automatically called by DesignGridLayout constructor
Please note that V1.0 required API changes that I could unfortunately not keep compatible with previous 0.9 release. This should be the last time that happens (V1.1 should only extend the current API).

I consider the current version ready for production as the current test suite of DesignGridLayout is quite comprehensive and covers all its features.
However, I have decided to prepare a release candidate to give myself a chance to fix any problems that users may find but that I could not discover by myself (in particular, problems related to platforms that I don't have: MacOS-X, Linux, Solaris).

If needed, I will create further release candidates. I will wait about one month after a rc until I cut a final release.

What's next?
  • first of all, I'll get some rest;-)
  • then I'll spend some weeks on my other open source project, guice-gui
  • finally I'll start working on DesignGridLayout V1.1, which should include just one improvement (issue #10: support for components spanning several rows) which should be released as a Christmas present;-)
Enjoy it and don't hesitate to report any problems or enhancements!

Monday, November 03, 2008

In search of the perfect API (final part)

This is the last post of my series on DesignGridLayout API evolutions. As mentioned before, although I use DesignGridLayout to illustrate it, this series might be useful to any developer of libraries.

In the first, second, third and fourth posts, I have described the evolution of DesignGridLayout API from its original release to the latest -soon released now- V1.0.

This last post is more a reflection on the next probable evolution of this API. This means that anything shown in this post has not been implemented (and might be difficult to implement), but thinking in terms of API first rather than implementation should be your mantra when writing a library with a large users base (I'm dreaming aloud;-))

Immediately after releasing V1.0 (in a few days), I'll start working on V1.1, which main enhancement will be issue #10 (add support for components spanning several rows).

This is not an easy problem, API-wise; indeed, you have to keep in mind the following principles that DesignGridLayout project adheres to:
  • the feature (API) must be easy to understand and use
  • the code visually represents the actual layout
  • developer mistakes must be avoided or can be found (and eradicated) at compile-time
I would also add an important point here:
  • don't break the current API (and the existing code that uses DesignGridLayout library V1.0)!
With this in mind, let's try to sketch what we would like the user to write (in terms of code), starting from a layout that is possible in V1.0:
layout.row().grid(lbl1).add(table1).add(field2);
layout.row().grid() .empty() .add(field3);
layout.row().grid(lbl4).add(field4);
Note that table1 represents a JList in a JScrollPane with a preferred size of a few rows (let's say 2 for this example).

Here is a snapshot of the displayed layout for this snippet:


Here we can see that with DesignGridLayout V1.0, table1 is displayed at its preferred height but this fixes the height of its whole row. Hence a lot of empty space is lost on the second column of the layout. Instead, wee would like to have table1 span on the second row as well (that's why we have already put an empty() column in the same column in row 2).

Now how can we let the user indicate that table1 should span on 2nd row?

One option I have in mind is something like this:
layout.row().grid(lbl1).add(table1).add(field2);
layout.row().grid() .spanRow() .add(field3);
layout.row().grid(lbl4).add(field4);
Using "spanRow()" on the second row would have the following meaning:
  1. the component on the previous row at the same column position in the same sub-grid will span this row
  2. the horizontal span of this component will be the same as the column span defined for that same component on the previous row
  3. the sub-grid in which spanRow() appears will have its gridspan forced to the same value as the sub-grid in the same position of the previous row
  4. if the matching component in the previous row is empty() or empty(n), then spanRow() is also empty() or empty(n)
  5. if this is the first row in the layout, then spanRow() is equivalent to empty()
This definition solves some problematic cases when using column spans in a grid:
layout.row().grid(lbl1).add(table1, 2).add(field2);
layout.row().grid() .spanRow() .add(field3);
layout.row().grid(lbl4).add(field4);
Or even when using multiple sub-grids:
layout.row().grid(lbl1, 2).add(table1).add(field2);
layout.row().grid(1) .spanRow().add(field3);
layout.row().grid(lbl4) .add(field4) .grid(lbl5).add(field5);
However, there is a problem in this latter example: in second row, we specify a gridspan of 1, but the actual gridspan will be 2 (the same as in the first row)! This looks counter-intuitive.

One approach to solving this problem would be to prevent the possible call to grid(int gridspan) or grid(JLabel label, int gridspan).

But the only way to prevent it would be to explicitly indicate that, when we create a new sub-grid in a row, this row will include row-spanning components, and in this case we can restrict the interface to prevent setting explicitly gridspans.

For this we could simply change the signature of grid methods in ISubGridStarter interface:
public interface ISubGridStarter {
public ISpannableGridRow grid(JLabel);
public ISpannableGridRow grid();
public IGridRow grid(JLabel, int gridspan);
public IGridRow grid(int gridspan);
}
Here we introduce a new interface ISpannableGridRow which is defined as follows:
public interface ISpannableGridRow extends IGridRow {
public ISpannableGridRow spanRow();
}
This is just an extension of the normal IGridRow with one extra method that allows spanning of components.

Now there is another problem (unsolved yet, else this issue would be fixed in V1.0 already;-)), related to vertical resize feature.
DesignGridLayout has the notion of rows that can vary in height or not (based on the components they hold); in addition, it allows you to define a vertical grow weight factor for those rows with variable height (see DesignGridLayout.row(double weight) method).
Now that components can possibly span several rows, how should we address the way individual rows will be shared vertical space?
Or is this really a problem? Can't the current algorithm work as well? Only a prototype for issue #10 will tell us...

For the definitive API of DesignGridLayout V1.1, well, I guess we'll have to wait until official release;-) maybe as a Christmas present, or sooner if I can manage!

This post closes my long discussion on searching the best possible API for a library. Maybe, in a later post, I'll show implementation details for this API (in particular, how return co-variance (Java 5+) has helped me much for that).

Hope you found this series interesting. Don't hesitate to comment! Enjoy.

Sunday, November 02, 2008

In search of the perfect API (part 4)

This is the fourth post of my series about how the API of DesignGridLayout library has been improved. This series can be useful for anyone developing libraries (DesignGridLayout just illustrates improvements).

The first post discussed the original V0.1 API and its related issues.

The second post has shown how to solve most V0.1 issues.

The third post has dealt with API improvements driven by new features. In particular, one interesting change to the API has been to introduce a new interface ISubGridStarter to reduce the list of available methods in a context, these methods then returning the "full" interface IGridRow.

In this installment, I will talk about the latest changes made to the API before V1.0 is released (which should happen very soon now).

Latest 1.0-SNAPSHOT: back to 0.1?

First of all, I was not very happy with the new methods I introduced in DesignGridLayout class in one of both previous snapshots:
public class DesignGridLayout {
public INonGridRow leftRow();
public INonGridRow leftRow(double weight);
public INonGridRow rightRow();
public INonGridRow rightRow(double weight);
public INonGridRow centerRow();
public INonGridRow centerRow(double weight);
public IGridRow row();
public IGridRow row(double weight);
}
There is a lot of repeat here. In addition, it is not clear why "row()" is not "gridRow()" (lack of consistency).
In addition, the ISubGridStarter interface uses methods all named "label()" to actually do 2 things:
  1. Start a new canonical sub-grid
  2. Set a label to this new sub-grid (or no label at all)
So the name is not representative of the method behavior. In particular, it is extremely weird to call label() with no argument to indicate we start a new sub-grid with no label!

Actually, I prefer something like that (in terms of usage):
layout.row().left().add(...);
layout.row(1.0).grid(lbl1).add(...).grid(lbl2).add(...);
layout.row(0.5).grid() .add(...).grid(lbl3).add(...);
Several changes have occurred to make this kind of code possible.

First I introduced (once again) a new interface:
public interface IRowCreator extends ISubGridStarter {
public INonGridRow left();
public INonGridRow right();
public INonGridRow center();
}
I also modified ISubGridStarter (only the method names):
public interface ISubGridStarter {
public IGridRow gridJLabel);
public IGridRow grid();
}
You have to remember that IGridRow extends ISubGridStarter (which allows to later start new sub-grids in a row).

Finally, I simplified DesignGridLayout API:
public class DesignGridLayout implements LayoutManager {
...
public IRowCreator row();
public IRowCreator row(double weight);
public void emptyRow();
}
Now we have only 2 general methods to create a new row (whatever its type), one allows you to specify the vertical growth weight factor (the no-arg row() will automatically determine if the actual row should have variable height and, if so, assign it a default weight of 1.0).

Also note that we still keep emptyRow(), but it has lost its "int height" argument, this is due to an improvement of the behavior (issue #16: make emptyRow smarter).

Now we can code our layouts like that:
layout.row().grid(lbl1).add(field1)             .grid(lbl2) .add(field2).add(field3);
layout.row().grid() .add(field4).add(field5) .grid(lbl3) .add(field6);
layout.row().grid() .grid(lbl4) .add(field7).add(field8).add(field9);
layout.emptyRow();
layout.row().center().add(ok).add(cancel);
The snippet above will exhibit the following layout:


The source code reflects the way the container will be laid out.
Code completion is helpful in that it does not authorize useless, or erroneous, method calls.

In fact, we have just built a simple "DSL" for laying out a Swing container!

To summarize, here is what we have gained in consistency (compared with previous 1.0-SNAPSHOT):
  • you add a new row to your form with one of two methods, named the same (row())
  • once a new row has been created, you can only choose its type in a clear way (left(), right(), center() or grid())
  • on a grid row, you can start a new sub-grid at any time with grid(), this is consistent with the way you started the grid row (because a grid row starts with a sub-grid)
In addition to previous improvements:
  • it is never possible for users to call a meaningless API (e.g. add(JComponent child, int span) in a non-grid row)
  • thus the number of options (callable methods) to the user in a context is restricted to its minimum (easing the search of methods through code completion in your IDE)
Please note that throughout this series, the API was simplified a little bit (there are a few more methods to some interfaces, but not many) in order to make it easier to follow.

This API should now be considered stabilized and there should be no change until the first official release candidate 1.0-rc1 of DesignGridLayout (which should be in a few days now).

In the next (and last) installment, I will talk about the future extensions of DesignGridLayout API (due for V1.1), necessary to implement the issue #10 (Support for components spanning several rows).

Saturday, November 01, 2008

In search of the perfect API (part 3)

This is the third installment of the short series discussing how to enhance a library API to make it better for its users. The series is based on DesignGridLayout as a practical example.

The first post discussed the original API of DesignGridLayout V0.1, focusing on its weak points for the end user.

The second post introduced the API of V0.9, showing how most of V0.1 pain points were solved (in particular by using interfaces and making public only what has to be public).

This third post will describe two evolutions (performed in two distinct steps) made in the current development trunk (which I call 1.0-SNAPSHOT), originated in new features requested by some users.

First evolution to 1.0-SNAPSHOT

When implementing issue #5 (vertical resize behavior), it had to be possible to add a "vertical grow weight" factor to any kind of row (except empty rows), in the same manner as you can assign "weighty" in Swing GridBagLayout.

For that, I only modified DesignGridLayout class:
public class DesignGridLayout {
...
public INonGridRow leftRow();
public INonGridRow leftRow(double weight);
public INonGridRow rightRow();
public INonGridRow rightRow(double weight);
public INonGridRow centerRow();
public INonGridRow centerRow(double weight);
public IGridRow row();
public IGridRow row(double weight);
public void emptyRow(int height);
}
That was really straightforward and it worked well for the library user. Note that we still did not solve the API issue (existing from V0.1) allowing multiple calls to label() on grid rows, but where only one call is effective.

Second evolution of 1.0-SNAPSHOT


Issue #13 (grids with multiple labels) was an important request from DesignGridLayout users so I had first to find the right API before implementing this feature.

The first idea that came to my mind was an API that would allow the following snippet:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row() .add(field3).label(lbl4).add(field4);
So I need multiple calls to label(JLabel) (which was easy because the current API already enabled that, that was even its main remaining flaw).

Please note the absence of a label at the beginning of the second row, which means that there will be no label at this position in the displayed form (however empty space will be added, so that field1 and field3 are vertically aligned with each other). But this possibility required to have the same possibility for the second (and next) label in a row, I needed something like that:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row() .add(field3).label() .add(field4);
Note the new no-arg label() method. Here is the new IGridRow interface (excerpt):
public interface IGridRow {
...
IGridRow label(JLabel);
IGridRow label();
}
Adding this method solves the problem but leads to inconsistency in the API, that we see in the snippet above, you now have two ways to obtain the same result:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row() .add(field3).label() .add(field4);
or
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row().label() .add(field3).label() .add(field4);
The second way should actually be the only way to get this result! There are two reasons why I want that:
  1. API Consistency (as mentioned before)
  2. It allows unclear code when creating specific layouts where one would want to completely skip the first label AND the first field (and define only the second label and field)
For example, what layout would be produced by the following snippet:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row().label(lbl4).add(field4);
Should lbl4 belong to the first label column or the second one? If we want to avoid such inconsistencies we have to make it mandatory to call one of both label() methods when starting a grid row.

Concretely this requires adding a new interface returned by DesignGridLayout#row():
public class DesignGridLayout {
...
public ISubGridStarter row();
public ISubGridStarter row(double weight);
}

public interface ISubGridStarter {
public IGridRow label(JLabel label);
public IGridRow label();
}
I also refactored IGridRow interface to extend ISubGridStarter:
public interface IGridRow extends ISubGridStarter {
...
}
This way, we have one unique, consistent way to define our layout.

In next installment, I will explain some last minute changes in the 1.0-SNAPSHOT API, in order to make it definitely better (and easier to use).