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() .empty() .add(field3);
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() .spanRow() .add(field3);
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);
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.

No comments:

Post a Comment