net.goui.awt
Class GoLayout

java.lang.Object
  extended bynet.goui.awt.GoLayout
All Implemented Interfaces:
java.awt.LayoutManager

public final class GoLayout
extends java.lang.Object
implements java.awt.LayoutManager

Version:
1.0
Author:
David Beaumont, Copyright 2005

This class provides an intuitive and easy to specify way of controlling the layout of a fixed number of Components within a Container. Using the underlying GraphLayout class to control the layout procedure and with a simple and intuitive syntax for specifying the layout structure with a single String instance, this class is both easier to use and more powerful than the traditional AWT layout managers such as GridBagLayout.

Note however that this class does not address the problem of laying out an unknown number of Components in a regular formation and in these cases you should still use LayoutManagers such as GridLayout or FlowLayout.

The information required to define the layout of the Components is provided in a single String instance using the layout syntax described below. A simple BNF like form of the syntax will be given first and then each part will be examined in detail, along with several simple examples. Whitespace is ignored between elements but must not occur inside numeric values.

 Layout := ( Row )+
 
 Row  := ( "<" Weight Cell ( Edge? Cell )* ">" ) |
                ( "[" Cell ( Edge? Cell )* "]" ) |
                ( "*" )
 
 Cell := ( "<" Alignment? Weight? ( ( ":" CompIndex ) | "*" )? ">" ) |
                ( "[" Alignment? ( CompIndex | "*" )? "]" ) |
                ( "(" ")" ) |
                ( "*" )
 
 Edge := ( "|" ) |
                ( EdgeOffset? ( "v" | "^" ) RowOffset? ) |
                ( EdgeOffset? ":" RowIndex )
 
 Alignment := [-^v|]
 Weight, CompIndex, EdgeOffset, RowIndex := [0-9]+
 RowOffset := "0"* [1-9] [0-9]*
 

Layout

A layout is specified by a sequence of one of more Rows, each of which has a weight value associated with it. A weight value is a non-negative integer which can be specified directly or omitted to allow the default value to be used. Note that using the "[", "]" form to specify a Row results in a weight of zero being used, whereas the default value for the weight when using the "<", ">" form will be strictly positive.

Rows

A Row contains a sequence of Cells separated by Edges. If an Edge declaration is omitted between two Cells, then an Edge will be generated internally by the layout class.

The use of "*" to specify a Row is equivalent to "<*>" and results in an empty Row with a weight of one. This is useful as a space filling Row to facilitate the vertical alignment of other Rows.

Cells

A Cell, like a Row, as a weight value associated with it. The use of the "[", "]" form for a Cell is similar to that for Rows and gives the Cell a weight of zero. Unlike a Row however, a Cell can have a vertical alignment character specified before the weight value. This is used during the vertical layout phase and is discussed below.

As well as having a weight, a Cell also has a Component index. This is either "*" or a numeric value specified after a ":" in the Cell declaration. If the index is omitted completely then the index is implicitly defined as the next available index, starting from zero. If the index is specified explicitly then it must be a non-negative integer and correspond to an index of a Component to be laid out. It is not permitted to mix implicit and explicit indices within a Layout. If the index is just given as "*" then the Cell will not be associated with a Component and remain empty.

The use of "(", ")" to specify a Cell indicates that the matching Cell from the Row above should be used at this point in the Row. This 'cloning' operation is used to allow a single Cell to span several Rows. However when a Cell is cloned, it is important that the Edges on either side of the original Cell are the same as the Edges on either side of the clone. For both the original and the clone Cell the Edges either side must be explicitly declared.

The use of "*" to specify a Cell is equivalent to "<*>" and results in an empty Cell with a weight of one. This is useful as a space filling Cell to facilitate the horizontal alignment of other Cells.

Edges

While Edges can span multiple Rows within a layout, in order to ensure a well defined result from the layout procedure it is necessary to specify upon which Row an Edge is actually defined. An Edge specified via "|" defines an anchor point within the Row in which it is declared and this can be referenced from another Row using the "^", "v" or ":" notation.

In order to be able to determine which Edge a given Edge reference refers to, a target Row must be specified. This is defined as follows:

A Row offset must be strictly positive and correspond to a Row in the layout. If the Row offset is not specified then it default to 1. When specifying a Row index using ":" a non-negative value must be given which corresponds to a Row other than this one.

Once a target Row has been determined, a particular Edge can be found. Where no Edge offset is given, the Edge used will be the next available one in the target Row. If an Edge offset is supplied then that number of Edges (or Edge references) will be skipped first. For example, if we format the layout string so that the Rows are above each other:

        A     B     C
 "[ [ ] | [ ] | [ ] | [ ] ]"
 "[ [ ] ^ [ ] ^ [ ] ^ [ ] ]"
        A'    B'    C'
In this case we have three Edge anchors in the first Row which are referenced from the second Row. If we now introduce a third Row, we can reference Edges within it also:
        A     B'    C
 "[ [ ] | [ ]   [ ] | [ ] ]"
 "[ [ ] ^ [ ] v [ ] ^ [ ] ]"
 "[ [       ] | [     [ ] ]"
        A'    B     C'
Note that this is correct because C' references the next explicitly defined Edge in the first Row (C) whereas B' references the first explicitly defined Edge in the 3rd Row (B). However the following example, while superficially similar, does not behave as expected.
        A     X     C
 "[ [ ] | [ ] | [ ] | [ ] ]"
 "[ [ ] ^ [ ] v [ ] ^ [ ] ]"
 "[ [       ] | [       ] ]"
        A'    B     C'
Here C' will actually refer to the Edge reference X, because X is the next explicitly defined Edge in the first Row. To make this example work while retaining the existence of X, we can use an explicit Edge offset "1^":
        A     X      C
 "[ [ ] | [ ] | [ ]  | [ ] ]"
 "[ [ ] ^ [ ] v [ ] 1^ [ ] ]"
 "[ [       ] | [        ] ]"
        A'    B     C'
This also highlights the difference between specifying an Edge anchor "|" which has no references to it and not specifying an Edge at all. During a layout they will behave the same, but an explicitly defined Edge will change the behaviour of any Edge references which reference that Row.

The Layout Process

Okay, so we now know what the syntax for specifying a layout string is, but what does it actually do?

Essentially the layout process is divided into the horizontal layout phase and the vertical layout phase. In the horizontal layout, each Row is laid out according to the constraints placed upon it by the Cells within it and the placement of of Edges from earlier Row layout. The order in which the Rows are laid out is determined by looking at the dependencies between the Rows.

In order to avoid circular dependencies within the layout, there must be at least one Row which contains no Edge references, only Edge anchors or implicit Edge declarations; all such Rows are laid out first. The remaining Rows are laid out such that any Edge references they contain may only refer to Edges whose anchor was declared in Rows which have already been laid out. If the layout declaration string does not allow for the well ordering of the Rows in this way then an IllegalArgumentException will be throw during initialisation of the GoLayout. For example:

 "[ [ ] |  [ ] v [ ] ]"            Row 0: References Row 1
 "[ <        > | [ ] ]"            Row 1: Does not reference any other Row
 "[ [ ] ^2 <       > ]"            Row 2: References Row 0
would result in the Rows being laid out in the order (1, 0, 2); whereas:
 "[ [ ] |  [ ] v <       > ]"      Row 0: References Row 1
 "[ <        > | [ ] v [ ] ]"      Row 1: References Row 2
 "[ [ ] ^2 <       > | [ ] ]"      Row 2: References Row 0
causes an IllegalArgumentException to be thrown.

Row Layout

When a Row is laid out there may be Edge references whose position has already been defined by the layout of a previous Row. In this case, the Edges which are already fixed will not be moved by any subsequent layout. Effectively the layout of a single Row can be thought of in terms of the layout of groups of Cells which lie between the fixed Edge references.

When laying out such a group, an attempt is made to give each Cell a fraction of the available space between the fixed Edges which is in proportion to their weight values. However the layout process will never resize a Cell below its minimum or above its maximum width (these values are calculated based upon the minimum, preferred and maximum sizes of the Components associated with each Cell).

Because the layout process will initially always attempt to give a Cell with zero weight a zero width, it will always end up being given its minimum width. Thus you can think of a zero weight Cell as a fixed width Cell, whose width is always the minimum width of the Cell. Any Cell whose weight is greater than zero can be thought of as a stretchy Cell.

If a group of Cells all have zero weight then it is possible that the layout process will not allocate all of the available space to the Cells. If this occurs then the Cells will be left-justified and any remaining space will go to the far end of the group.

If there is enough space within the Container to allow every Cell to be resized to the preferred width of its Component, then the minimum width of the Cell will simply be the preferred width of the associated Component. When there is no longer enough room to give each Cell the preferred width of its Component, then the minimum width of a Cell is calculated as a value between the minimum and preferred widths of its Component in proportion to the overall minimum and preferred width of the layout.

In other words, if the layout has a width which is half way between its minimum and preferred sizes, then each Cell will have a minimum width which is half way between the minimum and preferred sizes of its associated Component. Note however that this is not the same as simply taking a layout at the preferred size and shrinking it by some factor.

Vertical Layout

The height of the Rows is determined in a very similar way to that in which a single Row is laid out. Note that it does not quite follow the same rules as regards the minimum and maximum width of Cells however.

The minimum height of a Row is defined as the maximum of the set of minimum heights of all Cells which are defined only in this Row (remember that it is possible for a Cell to cover several Rows due to cloning). The maximum height of a Row is unbounded.

Furthermore because of the more limited control on the position of Cells in the vertical layout, the optional Cell alignment is used give more control regarding the position of the Cell's Component. The valid alignment characters are:

Examples

The following examples have been chosen because they mimic the examples used by Sun for the GridBagLayout class in The Java Class Libraries: Second Edition, Volume 2 by Patrick Chan and Rosanna Lee. For comparison, the code examples from this book can be downloaded at and the code can be found in java/awt/GridBagLayout/Main.java.

Border Layout

This layout String defines a five Component layout which mimics exactly the standard BorderLayout class. In this example there is no need for any explicitly defined edges and all the weights are the same.
 "[ <         > ]"
 "< [ ] < > [ ] >"
 "[ <         > ]"
 

Table Layout

This layout String defines a vertically centred three row layout with fixed width cells on the left and stretchy cells on right, all sharing a common vertical edge.
 "      *      "
 "[ [*] | <*> ]" <-- Zero height row
 "[ < > ^ < > ]"
 "[ < > ^ < > ]"
 "[ < > ^ < > ]"
 "      *      "
 

This is an interesting example because it introduces the idea of using a zero height Row to facilitate layout control. The use of "[*]" to define an empty, zero weight, Cell results in the Edge anchor "|" being positioned as far to the left as possible while complying with the minimum size constraints of all the Cells below it. "<*>" is used here (rather than just "*") as a stylistic point to clarify that this empty cell has a vital positional role in this layout rather than just being empty space.

Crazy Layout

This layout is included for completeness and demonstrates a complex layout example.
 "[  [ ] | <                       > ]"
 "[  ( ) ^ <        > 1v <         > ]"
 "<2 <        >  v [ ] v (         ) >"
 "<1 (        ) 1v ( ) v  *  v <   > >"
 "[  *  :0 <  > 1v ( ) v [ ] |   *   ]"
 "[  *  :0 <1*>  | ( ) | <2    *   > ]" <-- Zero height row
 

This layout mimics the "crazy" layout used in the Sun examples and the best way to seek to understand how it works is to use the example code in the GoLayoutTest class. Things worth noting in this example are:

In this case we use the zero height row to specify that Cell 'D' should be twice as wide as Cell 'F' (despite the fact they do not share any rows in the layout).

While clearly quite non-trivial in its complexity, this is still a lot simpler and easier to control than the equivalent implementation using GridBagLayout.


Nested Class Summary
 class GoLayout.Cell
           
 class GoLayout.Row
           
 
Field Summary
static int VALIGN_BOTTOM
          Vertical alignment constant for Cells.
static int VALIGN_CENTER
          Vertical alignment constant for Cells.
static int VALIGN_FILLED
          Vertical alignment constant for Cells.
static int VALIGN_TOP
          Vertical alignment constant for Cells.
static java.lang.String VERSION
          Simple version identifier which can be used to ensure compatibility between versions.
 
Constructor Summary
GoLayout(java.lang.String layout)
          This constructs a GoLayout instance using the given layout string and with a default weight of 1 and a default vertical alignment of VALIGN_FILLED.
GoLayout(java.lang.String layout, int weight, int align)
          This constructs a GoLayout instance given a layout string, default weight and default vertical alignment.
 
Method Summary
 void addLayoutComponent(java.lang.String constraint, java.awt.Component component)
          This method is implemented as part of the LayoutManager interface but does nothing in GoLayout.
 java.awt.Dimension getEdgeSize()
          This method returns the current Edge width and height.
 GoLayout.Row getRow(int index)
          This method returns the Row instance corresponding to the specified index within the layout.
 int getRowCount()
          This method returns the number of Rows in this layout.
 void layoutContainer(java.awt.Container parent)
          This method lays out the child Components of the given Container instance in accordance with the layout constraints place upon it by the layout string and the minimum, preferred and maximum sizes of the Components being laid out.
 java.awt.Dimension minimumLayoutSize(java.awt.Container parent)
          This method returns the minimum required size for a Container to ensure that all the laid out Components can receive their minimum size.
 java.awt.Dimension preferredLayoutSize(java.awt.Container parent)
          This method returns the minimum required size for a Container to ensure that all the laid out Components can receive their preferred size.
 void removeLayoutComponent(java.awt.Component component)
          This method is implemented as part of the LayoutManager interface but does nothing in GoLayout.
 void setEdgeSize(int width, int height)
          This method sets the Edge width and height for this layout.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

VERSION

public static final java.lang.String VERSION
Simple version identifier which can be used to ensure compatibility between versions. All versions of GoLayout with the same version String will produce the same layout behaviour.

See Also:
Constant Field Values

VALIGN_CENTER

public static final int VALIGN_CENTER
Vertical alignment constant for Cells. If this is set then providing there is enough space within the Cell, the Cell's Component will be given its preferred height and aligned centrally in the available space.

See Also:
Constant Field Values

VALIGN_TOP

public static final int VALIGN_TOP
Vertical alignment constant for Cells. If this is set then providing there is enough space within the Cell, the Cell's Component will be given its preferred height and aligned to the top edge of the Cell.

See Also:
Constant Field Values

VALIGN_BOTTOM

public static final int VALIGN_BOTTOM
Vertical alignment constant for Cells. If this is set then providing there is enough space within the Cell, the Cell's Component will be given its preferred height and aligned to the bottom edge of the Cell.

See Also:
Constant Field Values

VALIGN_FILLED

public static final int VALIGN_FILLED
Vertical alignment constant for Cells. If this is set then the Cell's Component will be resized to the full height of the Cell.

See Also:
Constant Field Values
Constructor Detail

GoLayout

public GoLayout(java.lang.String layout,
                int weight,
                int align)
This constructs a GoLayout instance given a layout string, default weight and default vertical alignment. The layout string must be a non-null string defining the Rows and Cells which will comprise the layout. The supplied weight will be used to set the weight of any Rows and Cells which do not have an explicit weight set and the alignment constant will define the default vertical alignment of Cells.

Parameters:
layout - A valid layout string as described above (non-null).
weight - A non-negative default weight for Rows and Cells.
align - The default vertical alignment constant for Cells.
Throws:
java.lang.IllegalArgumentException - if any of the parameters are invalid.

GoLayout

public GoLayout(java.lang.String layout)
This constructs a GoLayout instance using the given layout string and with a default weight of 1 and a default vertical alignment of VALIGN_FILLED.

Parameters:
layout - A valid layout string as described above (non-null).
Throws:
java.lang.IllegalArgumentException - if any of the parameters are invalid.
Method Detail

setEdgeSize

public void setEdgeSize(int width,
                        int height)
This method sets the Edge width and height for this layout. Cells will be laid out with a horizontal gap of the specified Edge width and a vertical gap of the specified height between them and around the outside of the layout. Note that the effect of changing these values is not quite the same as talking about a padding around every Cell because that would not result in the same effect at the edges of the layout and would make it impossible to obtain an odd number of pixels between Cells.

Note that if a Cell is empty, inactive or has zero a width then the Edge sizes will not be applied. This permits the use of multiple successive empty Cells without issue and ensures that inactive Cells behaviour in the expected manner.

Parameters:
width - The non-negative Edge width in the layout.
height - The non-negative Edge height in the layout.
Throws:
java.lang.IllegalArgumentException - if either of the arguments are negative.

getEdgeSize

public java.awt.Dimension getEdgeSize()
This method returns the current Edge width and height.

Returns:
A new Dimension instance containing the current Edge width and height values.

getRowCount

public int getRowCount()
This method returns the number of Rows in this layout. This can be used in conjunction with getRow() to enumerate the available Rows.

Returns:
The number of Rows in this layout

getRow

public GoLayout.Row getRow(int index)
This method returns the Row instance corresponding to the specified index within the layout.

Parameters:
index - The index of the Row to be returned.
Returns:
The Row corresponding to the given index.
Throws:
java.lang.IndexOutOfBoundsException - if no such Row exists.

layoutContainer

public void layoutContainer(java.awt.Container parent)
This method lays out the child Components of the given Container instance in accordance with the layout constraints place upon it by the layout string and the minimum, preferred and maximum sizes of the Components being laid out.

Specified by:
layoutContainer in interface java.awt.LayoutManager
Parameters:
parent - The parent Container in which the Components which are to be laid out reside.

minimumLayoutSize

public java.awt.Dimension minimumLayoutSize(java.awt.Container parent)
This method returns the minimum required size for a Container to ensure that all the laid out Components can receive their minimum size. This will have already taken into account the insets of the parent Container.

In fact GoLayout will never size a Component below its minimum size, so if the parent Container is reduced below this size, some Components may be positioned outside the bounds of the parent and rendered partially of completely invisible.

Specified by:
minimumLayoutSize in interface java.awt.LayoutManager
Parameters:
parent - The parent Container in which the child Components reside.

preferredLayoutSize

public java.awt.Dimension preferredLayoutSize(java.awt.Container parent)
This method returns the minimum required size for a Container to ensure that all the laid out Components can receive their preferred size. This will have already taken into account the insets of the parent Container.

Specified by:
preferredLayoutSize in interface java.awt.LayoutManager
Parameters:
parent - The parent Container in which the child Components reside.
Returns:
A new Dimension instance holding the preferred size.

addLayoutComponent

public void addLayoutComponent(java.lang.String constraint,
                               java.awt.Component component)
This method is implemented as part of the LayoutManager interface but does nothing in GoLayout.

Specified by:
addLayoutComponent in interface java.awt.LayoutManager

removeLayoutComponent

public void removeLayoutComponent(java.awt.Component component)
This method is implemented as part of the LayoutManager interface but does nothing in GoLayout.

Specified by:
removeLayoutComponent in interface java.awt.LayoutManager