FileDocCategorySizeDatePackage
Form.javaAPI DocJ2ME MIDP 2.090956Thu Nov 07 12:02:26 GMT 2002javax.microedition.lcdui

Form

public class Form extends Screen
A Form is a Screen that contains an arbitrary mixture of items: images, read-only text fields, editable text fields, editable date fields, gauges, choice groups, and custom items. In general, any subclass of the {@link Item Item} class may be contained within a form. The implementation handles layout, traversal, and scrolling. The entire contents of the Form scrolls together.

Item Management

The items contained within a Form may be edited using append, delete, insert, and set methods. Items within a Form are referred to by their indexes, which are consecutive integers in the range from zero to size()-1, with zero referring to the first item and size()-1 to the last item.

An item may be placed within at most one Form. If the application attempts to place an item into a Form, and the item is already owned by this or another Form, an IllegalStateException is thrown. The application must remove the item from its currently containing Form before inserting it into the new Form.

If the Form is visible on the display when changes to its contents are requested by the application, updates to the display take place as soon as it is feasible for the implementation to do so. Applications need not take any special action to refresh a Form's display after its contents have been modified.

Layout

Layout policy in Form is organized around rows. Rows are typically related to the width of the screen, respective of margins, scroll bars, and such. All rows in a particular Form will have the same width. Rows do not vary in width based on the Items contained within the Form, although they may all change width in certain circumstances, such as when a scroll bar needs to be added or removed. Forms generally do not scroll horizontally.

Forms grow vertically and scroll vertically as necessary. The height of a Form varies depending upon the number of rows and the height of each row. The height of each row is determined by the items that are positioned on that row. Rows need not all have the same height. Implementations may also vary row heights to provide proper padding or vertical alignment of Item labels.

An implementation may choose to lay out Items in a left-to-right or right-to-left direction depending upon the language conventions in use. The same choice of layout direction must apply to all rows within a particular Form.

Prior to the start of the layout algorithm, the Form is considered to have one empty row at the top. The layout algorithm considers each Item in turn, starting at Item zero and proceeding in order through each Item until the last Item in the Form has been processed. If the layout direction (as described above) is left-to-right, the beginning of the row is the left edge of the Form. If the layout direction is right-to-left, the beginning of the row is the right edge of the Form. Items are laid out at the beginning of each row, proceeding across each row in the chosen layout direction, packing as many Items onto each row as will fit, unless a condition occurs that causes the packing of a row to be terminated early. A new row is then added, and Items are packed onto it as described above. Items are packed onto rows, and new rows are added below existing rows as necessary until all Items have been processed by the layout algorithm.

The layout algorithm has a concept of a current alignment. It can have the value LAYOUT_LEFT, LAYOUT_CENTER, or LAYOUT_RIGHT. The value of the current alignment at the start of the layout algorithm depends upon the layout direction in effect for this Form. If the layout direction is left-to-right, the initial alignment value must be LAYOUT_LEFT. If the layout direction is right-to-left, the initial alignment value must be LAYOUT_RIGHT. The current alignment changes when the layout algorithm encounters an Item that has one of the layout directives LAYOUT_LEFT, LAYOUT_CENTER, or LAYOUT_RIGHT. If none of these directives is present on an Item, the current layout directive does not change. This rule has the effect of grouping the contents of the Form into sequences of consecutive Items sharing an alignment value. The alignment value of each Item is maintained internally to the Form and does not affect the Items' layout value as reported by the {@link Item#getLayout Item.getLayout} method.

The layout algorithm generally attempts to place an item on the same row as the previous item, unless certain conditions occur that cause a "row break." When there is a row break, the current item will be placed at the beginning of a new row instead of being placed after the previous item, even if there is room.

A row break occurs before an item if any of the following conditions occurs:

  • the previous item has a row break after it;
  • it has the LAYOUT_NEWLINE_BEFORE directive; or
  • it is a StringItem whose contents starts with "\n";
  • it is a ChoiceGroup, DateField, Gauge, or a TextField, and the LAYOUT_2 directive is not set; or
  • this Item has a LAYOUT_LEFT, LAYOUT_CENTER, or LAYOUT_RIGHT directive that differs from the Form's current alignment.

A row break occurs after an item if any of the following conditions occurs:

  • it is a StringItem whose contents ends with "\n"; or
  • it has the LAYOUT_NEWLINE_AFTER directive; or
  • it is a ChoiceGroup, DateField, Gauge, or a TextField, and the LAYOUT_2 directive is not set.

The presence of the LAYOUT_NEWLINE_BEFORE or LAYOUT_NEWLINE_AFTER directive does not cause an additional row break if there is one already present. For example, if a LAYOUT_NEWLINE_BEFORE directive appears on a StringItem whose contents starts with "\n", there is only a single row break. A similar rule applies with a trailing "\n" and LAYOUT_NEWLINE_AFTER. Also, there is only a single row break if an item has the LAYOUT_NEWLINE_AFTER directive and the next item has the LAYOUT_NEWLINE_BEFORE directive. However, the presence of consecutive "\n" characters, either within a single StringItem or in adjacent StringItems, will cause as many row breaks as there are "\n" characters. This will cause empty rows to be present. The height of an empty row is determined by the prevailing font height of the StringItem within which the "\n" that ends the row occurs.

Implementations may provide additional conditions under which a row break occurs. For example, an implementation's layout policy may lay out labels specially, implicitly causing a break before every Item that has a label. Or, as another example, a particular implementation's user interface style may dictate that a DateField item always appears on a row by itself. In this case, this implementation may cause row breaks to occur both before and after every DateField item.

Given two items with adjacent Form indexes, if none of the specified or implementation-specific conditions for a row break between them occurs, and if space permits, these items should be placed on the same row.

When packing Items onto a row, the width of the item is compared with the remaining space on the row. For this purpose, the width used is the Item's preferred width, unless the Item has the LAYOUT_SHRINK directive, in which case the Item's minimum width is used. If the Item is too wide to fit in the space remaining on the row, the row is considered to be full, a new row is added beneath this one, and the Item is laid out on this new row.

Once the contents of a row have been determined, the space available on the row is distributed by expanding items and by adding space between items. If any items on this row have the LAYOUT_SHRINK directive (that is, they are shrinkable), space is first distributed to these items. Space is distributed to each of these items proportionally to the difference between the each Item's preferred size and its minimum size. At this stage, no shrinkable item is expanded beyond its preferred width.

For example, consider a row that has 30 pixels of space available and that has two shrinkable items A and B. Item A's preferred size is 15 and its minimum size is 10. Item B's preferred size is 30 and its minimum size is 20. The difference between A's preferred and minimum size is 5, and B's difference is 10. The 30 pixels are distributed to these items proportionally to these differences. Therefore, 10 pixels are distributed to item A and 20 pixels to item B.

If after expanding all the shrinkable items to their preferred widths, there is still space left on the row, this remaining space is distributed equally among the Items that have the LAYOUT_EXPAND directive (the stretchable Items). The presence of any stretchable items on a row will cause the Items on this row to occupy the full width of the row.

If there are no stretchable items on this row, and there is still space available on this row, the Items are packed as tightly as possible and are placed on the row according to the alignment value shared by the Items on this row. (Since changing the current alignment causes a row break, all Items on the same row must share the same alignment value.) If the alignment value is LAYOUT_LEFT, the Items are positioned at the left end of the row and the remaining space is placed at the right end of the row. If the alignment value is LAYOUT_RIGHT, the Items are positioned at the right end of the row and the remaining space is placed at the left end of the row. If the alignment value is LAYOUT_CENTER, the Items are positioned in the middle of the row such that the remaining space on the row is divided evenly between the left and right ends of the row.

Given the set of items on a particular row, the heights of these Items are inspected. For each Item, the height that is used is the preferred height, unless the Item has the LAYOUT_VSHRINK directive, in which case the Item's minimum height is used. The height of the tallest Item determines the height of the row. Items that have the LAYOUT_VSHRINK directive are expanded to their preferred height or to the height of the row, whichever is smaller. Items that are still shorter than the row height and that have the LAYOUT_VEXPAND directive will expand to the height of the row. The LAYOUT_VEXPAND directive on an item will never increase the height of a row.

Remaining Items shorter than the row height will be positioned vertically within the row using the LAYOUT_TOP, LAYOUT_BOTTOM, and LAYOUT_VCENTER directives. If no vertical layout directive is specified, the item must be aligned along the bottom of the row.

StringItems are treated specially in the above algorithm. If the contents of a StringItem (its string value, exclusive of its label) contain a newline character ("\n"), the string should be split at that point and the remainder laid out starting on the next row.

If one or both dimensions of the preferred size of a StringItem have been locked, the StringItem is wrapped to fit that width and height and is treated as a rectangle whose minimum and preferred width and height are the width and height of this rectangle. In this case, the LAYOUT_SHRINK, LAYOUT_EXPAND, and LAYOUT_VEXPAND directives are ignored.

If both dimensions of the preferred size of a StringItem are unlocked, the text from the StringItem may be wrapped across multiple rows. At the point in the layout algorithm where the width of the Item is compared to the remaining space on the row, as much text is taken from the beginning of the StringItem as will fit onto the current row. The contents of this row are then positioned according to the current alignment value. The remainder of the text in the StringItem is line-wrapped to the full width of as many new rows as are necessary to accommodate the text. Each full row is positioned according to the current alignment value. The last line of the text might leave space available on its row. If there is no row break following this StringItem, subsequent Items are packed into the remaining space and the contents of the row are positioned according to the current alignment value. This rule has the effect of displaying the contents of a StringItem as a paragraph of text set flush-left, flush-right, or centered, depending upon whether the current alignment value is LAYOUT_LEFT, LAYOUT_RIGHT, or LAYOUT_CENTER, respectively. The preferred width and height of a StringItem wrapped across multiple rows, as reported by the {@link Item#getPreferredWidth Item.getPreferredWidth} and {@link Item#getPreferredHeight Item.getPreferredHeight} methods, describe the width and height of the bounding rectangle of the wrapped text.

ImageItems are also treated specially by the above algorithm. The foregoing rules concerning the horizontal alignment value and the LAYOUT_LEFT, LAYOUT_RIGHT, and LAYOUT_CENTER directives, apply to ImageItems only when the LAYOUT_2 directive is also present on that item. If the LAYOUT_2 directive is not present on an ImageItem, the behavior of the LAYOUT_LEFT, LAYOUT_RIGHT, and LAYOUT_CENTER directives is implementation-specific.

A Form's layout is recomputed automatically as necessary. This may occur because of a change in an Item's size caused by a change in its contents or because of a request by the application to change the Item's preferred size. It may also occur if an Item's layout directives are changed by the application. The application does not need to perform any specific action to cause the Form's layout to be updated.

Line Breaks and Wrapping

For all cases where text is wrapped, line breaks must occur at each newline character ('\n' = Unicode 'U+000A'). If space does not permit the full text to be displayed it is truncated at line breaks. If there are no suitable line breaks, it is recommended that implementations break text at word boundaries. If there are no word boundaries, it is recommended that implementations break text at character boundaries.

Labels that contain line breaks may be truncated at the line break and cause the rest of the label not to be shown.

User Interaction

When a Form is present on the display the user can interact with it and its Items indefinitely (for instance, traversing from Item to Item and possibly scrolling). These traversing and scrolling operations do not cause application-visible events. The system notifies the application when the user modifies the state of an interactive Item contained within the Form. This notification is accomplished by calling the {@link ItemStateListener#itemStateChanged itemStateChanged()} method of the listener declared to the Form with the {@link #setItemStateListener setItemStateListener()} method.

As with other Displayable objects, a Form can declare {@link Command commands} and declare a command listener with the {@link Displayable#setCommandListener setCommandListener()} method. {@link CommandListener CommandListener} objects are distinct from {@link ItemStateListener ItemStateListener} objects, and they are declared and invoked separately.

Notes for Application Developers

  • Although this class allows creation of arbitrary combination of components the application developers should keep the small screen size in mind. Form is designed to contain a small number of closely related UI elements.
  • If the number of items does not fit on the screen, the implementation may choose to make it scrollable or to fold some components so that a separate screen appears when the element is edited.

see
Item
since
MIDP 1.0

Fields Summary
final boolean
TRAVERSE_HORIZONTAL
A boolean declaring whether the contents of the viewport can be traversed using the horizontal traversal keys, ie, left and right
final boolean
TRAVERSE_VERTICAL
A boolean declaring whether the contents of the viewport can be traversed using the vertical traversal keys, ie, up and down
static final int
CELL_SPACING
The spacing, in pixels, between cells
static final int
ONE_PIXEL_BOX
A static identifying a one pixel box for a traversal indicator
static final int
TRIANGLE_CORNERS
A static identifying a traversal indicator using triangles
static final int
TRAVERSE_INDICATOR
A static holding the type of traversal indicator to draw
static final int
TRAVERSE_INDICATOR_COLOR
A static holding the color of the traversal indicator
static final int
LAYOUT_HMASK
A bit mask to capture the horizontal layout directive of an item
static final int
LAYOUT_VMASK
A bit mask to capture the vertical layout directive of an item
private int
formMode
A Form is always in one of 2 modes: 1. traversing the Form 2. traversing within an Item These 2 modes map to the values: FORM_TRAVERSE ITEM_TRAVERSE
private static final int
FORM_TRAVERSE
A value indicating this Form is in "form traverse" mode
private static final int
ITEM_TRAVERSE
A value indicating this Form is in "item traverse" mode
private int
traverseIndex
The item index which has the traversal focus
private boolean
indicateTraverse
The traversal indicator should only be shown when the current traversal item is actually interactive. indicateTraverse is true when the traversal indicator should be drawn around the current traversal item.
private boolean
validateVisibility
Items must have their show/hide notify methods called when they come into and go out of view. This essentially only happens on scrolling, however their method is called in paint(). This flag tells us wether we should be calling the method in the paint routine, and is set in the scroll routine
private int[]
viewable
'viewable' contains the dimensions and location of the child viewable object within the viewport
private int[]
visRect
When a Form calls an Item's traverse() method, it passes in an in-out int[] that represents the Item's internal traversal bounds. This gets cached in the visRect variable
private static final int
GROW_SIZE
This is the rate at wich the internal array of Items grows if it gets filled up
private Item[]
items
Array of Items that were added to this form.
private int
numOfItems
The number of actual Items added is numOfItems.
private ItemStateListener
itemStateListener
itemStateListener that has to be notified of any state changes
private boolean
pointerPressed
true if a callPointerPressed event has occured without a corresponding callPointerReleased. false otherwise
Constructors Summary
public Form(String title)
Creates a new, empty Form.

param
title the Form's title, or null for no title


// ************************************************************
//  Static initializer, constructor
// ************************************************************

                        
       
        this(title, null);
    
public Form(String title, Item[] items)
Creates a new Form with the specified contents. This is identical to creating an empty Form and then using a set of append methods. The items array may be null, in which case the Form is created empty. If the items array is non-null, each element must be a valid Item not already contained within another Form.

param
title the Form's title string
param
items the array of items to be placed in the Form, or null if there are no items
throws
IllegalStateException if one of the items is already owned by another container
throws
NullPointerException if an element of the items array is null

        super(title);

        synchronized (Display.LCDUILock) {

            // Initialize the in-out rect for Item traversal
            visRect = new int[4];

            if (items == null) {
                this.items = new Item[GROW_SIZE];
                // numOfItems was initialized to 0
                // so there is no need to update it
                return;

            } else {
                this.items = new Item[items.length > GROW_SIZE ?
                                      items.length : GROW_SIZE];
            }

            // We have to check all items first so that some
            // items would not be added to a form that was not
            // instanciated
            for (int i = 0; i < items.length; i++) {
                // NullPointerException will be thrown by
                // items[i].getOwner() if items[i] == null;
                if (items[i].getOwner() != null) {
                    throw new IllegalStateException();
                }
            }

            numOfItems = items.length;

            for (int i = 0; i < numOfItems; i++) {
                items[i].setOwner(this);
                this.items[i] = items[i];
            }

        } // synchronized
    
Methods Summary
public intappend(Item item)
Adds an Item into the Form. The newly added Item becomes the last Item in the Form, and the size of the Form grows by one.

param
item the {@link Item Item} to be added.
return
the assigned index of the Item
throws
IllegalStateException if the item is already owned by a container
throws
NullPointerException if item is null

        synchronized (Display.LCDUILock) {
            // NullPointerException will be thrown
            // by item.getOwner() if item == null
            if (item.getOwner() != null) {
                throw new IllegalStateException();
            }

            return insertImpl(numOfItems, item);
        }
    
public intappend(java.lang.String str)
Adds an item consisting of one String to the Form. The effect of this method is identical to

append(new StringItem(null, str))

param
str the String to be added
return
the assigned index of the Item
throws
NullPointerException if str is null

        if (str == null) {
            throw new NullPointerException();
        }

        synchronized (Display.LCDUILock) {
            return insertImpl(numOfItems, new StringItem(null, str));
        }
    
public intappend(Image img)
Adds an item consisting of one Image to the Form. The effect of this method is identical to

append(new ImageItem(null, img, ImageItem.LAYOUT_DEFAULT, null))

param
img the image to be added
return
the assigned index of the Item
throws
NullPointerException if img is null

        if (img == null) {
            throw new NullPointerException();
        }

        synchronized (Display.LCDUILock) {
            return insertImpl(numOfItems,
                new ImageItem(null, img, ImageItem.LAYOUT_DEFAULT, null));
        }
    
voidcallHideNotify(Display d)
notify this Form it is being hidden on the given Display

param
d the Display hiding this Form

        super.callHideNotify(d);

        // SYNC NOTE: Rather than make a complete copy of the set
        // of items on this form, we'll simply catch any exception
        // that occurs and move on. The only problem that could occur
        // would be items being deleted from the Form, which would
        // mean the application was removing items from the Form
        // while it was technically still visible.
        if (traverseIndex != -1) {
            try {
                items[traverseIndex].callTraverseOut();
            } catch (Throwable t) {
                // Simply swallow the error and move on
            }
        }

        // We need to loop through our Items and call hideNotify
        // on those that were visible
        for (int x = 0; x < numOfItems; x++) {
            try {
                if (items[x].visible) {
                    items[x].callHideNotify();
                }
            } catch (Throwable t) {
                // Simply swallow the error and move on
            }
        }
    
voidcallInvalidate(Item item)
Re-validate the contents of this Form, possibly due to an individual item

param
item the Item causing the invalidation (may be null)

        if (!paintDelegate.isShown()) {
            return;
        }

        synchronized (Display.LCDUILock) {
            // If an Item is now invalid, we layout the Form, repaint it,
            // and do a traverse to the current item with the "NONE"
            // direction.
            layout();
        }

        repaintContents();
        traverse(CustomItem.NONE);
    
voidcallItemStateChanged(Item item)
Used by the event handler to notify the ItemStateListener of a change in the given item

param
item the Item which state was changed

        // get a copy of the object reference to ItemStateListener
        ItemStateListener isl = itemStateListener;
        if (isl == null || item == null) {
            return;
        }

        // Protect from any unexpected application exceptions
        try {
            // SYNC NOTE: We lock on calloutLock around any calls
            // into application code
            synchronized (Display.calloutLock) {
                isl.itemStateChanged(item);
            }
        } catch (Throwable thr) {
            Display.handleThrowable(thr);
        }
    
voidcallKeyPressed(int keyCode)
Handle a key press

param
keyCode the key code of the key which was pressed

        Item i = null;

        synchronized (Display.LCDUILock) {
            if (numOfItems == 0 || traverseIndex < 0) {
                return;
            }

            i = items[traverseIndex];
        } // synchronized


        // SYNC NOTE: formMode can only change as a result of a
        // traversal, which can only occur serially on the event
        // thread, so its safe to use it outside of the lock

        if (keyCode == Display.KEYCODE_UP
            || keyCode == Display.KEYCODE_DOWN
            || keyCode == Display.KEYCODE_LEFT
            || keyCode == Display.KEYCODE_RIGHT) {
            traverse(Display.getGameAction(keyCode));
            return;
        }

        // SYNC NOTE: callKeyPressed may result in a call to the
        // application, so we make sure we do this outside of the
        // LCDUILock
        if (i != null) {
            // pass the keypress onto the current item
            i.callKeyPressed(keyCode);
        }
    
voidcallKeyReleased(int keyCode)
Handle a key release event

param
keyCode the key which was released

        Item i = null;

        synchronized (Display.LCDUILock) {
            if (numOfItems == 0 || traverseIndex < 0) {
                return;
            }

            i = items[traverseIndex];
        }

        // SYNC NOTE: this call may result in a call to the
        // application, so we make sure we do this outside of the
        // LCDUILock
        i.callKeyReleased(keyCode);
    
voidcallKeyRepeated(int keyCode)
Handle a key repeat

param
keyCode the key code of the key which was repeated

        if (keyCode == Display.KEYCODE_UP
            || keyCode == Display.KEYCODE_DOWN
            || keyCode == Display.KEYCODE_LEFT
            || keyCode == Display.KEYCODE_RIGHT) {
            traverse(Display.getGameAction(keyCode));
        }
    
voidcallKeyTyped(char c)
Handle a typed key from a keyboard

param
c The char typed from the keyboard

        Item i = null;

        synchronized (Display.LCDUILock) {
            if (numOfItems == 0 || traverseIndex < 0) {
                return;
            }

            i = items[traverseIndex];
        }

        // SYNC NOTE: this call may result in a call to the
        // application, so we make sure we do this outside of the
        // LCDUILock
        i.callKeyTyped(c);
    
voidcallPaint(Graphics g, java.lang.Object target)
Paint the contents of this Form

param
g the Graphics object to paint on
param
target the target Object of this repaint

        super.callPaint(g, target);

        /*
        System.err.println("Form:Clip: " +
            g.getClipX() + "," + g.getClipY() + "," +
            g.getClipWidth() + "," + g.getClipHeight());

        System.err.println("numOfItems: " + numOfItems);
        */

        // SYNC NOTE: We cannot hold any lock around a call into
        // the application's paint() routine. Rather than copy
        // the dataset and expend heap space, we simply protect
        // this operation with try/catch. The only error condition
        // would be if an insert/append/delete occurred in the middle
        // of painting. This error condition would be quickly remedied
        // by the pending validation of that change which causes a
        // repaint automatically

        try {
            if (numOfItems == 0) {
                return;
            }

            int clip[] = new int[4];
            clip[X] = g.getClipX();
            clip[Y] = g.getClipY();
            clip[WIDTH] = g.getClipWidth();
            clip[HEIGHT] = g.getClipHeight();

            // If the clip is an area above our viewport, just return
            if (clip[Y] + clip[HEIGHT] <= viewport[Y]) {
                return;
            }

            if (SCROLLS_VERTICAL) {
                setVerticalScroll();
            }

            if (SCROLLS_HORIZONTAL) {
                // setHorizontalScroll();
            }

            if (target instanceof Item) {
                if (((Item)target).owner == this) {
                    paintItem((Item)target, g, clip);
                }
            } else {
                for (int i = 0; i < numOfItems; i++) {
                    paintItem(items[i], g, clip);
                }
            }
        } catch (Throwable t) {
            // Swallow the error and continue
        }
    
voidcallPointerDragged(int x, int y)
Handle a pointer dragged event

param
x The x coordinate of the drag
param
y The y coordinate of the drag

        Item i = null;

        synchronized (Display.LCDUILock) {
            if (numOfItems == 0 || traverseIndex < 0 || !pointerPressed) {
                return;
            }

            i = items[traverseIndex];

            x = (x - viewport[X] + view[X]) - i.bounds[X];
            y = (y - viewport[Y] + view[Y]) - i.bounds[Y];

        }

        // SYNC NOTE: this call may result in a call to the
        // application, so we make sure we do this outside of the
        // LCDUILock
        i.callPointerDragged(x, y);
    
voidcallPointerPressed(int x, int y)
Handle a pointer pressed event

param
x The x coordinate of the press
param
y The y coordinate of the press

        Item i = null;

        synchronized (Display.LCDUILock) {
            if (numOfItems == 0 || traverseIndex < 0) {
                return;
            }

            i = items[traverseIndex];

            x = (x - viewport[X] + view[X]) - i.bounds[X];
            y = (y - viewport[Y] + view[Y]) - i.bounds[Y];

            if (x < 0 
                || x > i.bounds[WIDTH]
                || y < 0
                || y > i.bounds[HEIGHT]) {
                return;
            }

            pointerPressed = true;
        }

        // SYNC NOTE: this call may result in a call to the
        // application, so we make sure we do this outside of the
        // LCDUILock
        i.callPointerPressed(x, y);
    
voidcallPointerReleased(int x, int y)
Handle a pointer released event

param
x The x coordinate of the release
param
y The y coordinate of the release

        Item i = null;

        synchronized (Display.LCDUILock) {
            if (numOfItems == 0 || traverseIndex < 0 || !pointerPressed) {
                return;
            }

            i = items[traverseIndex];

            x = (x - viewport[X] + view[X]) - i.bounds[X];
            y = (y - viewport[Y] + view[Y]) - i.bounds[Y];

            pointerPressed = false;

        }

        // SYNC NOTE: this call may result in a call to the
        // application, so we make sure we do this outside of the
        // LCDUILock
        i.callPointerReleased(x, y);
    
voidcallShowNotify(Display d)
notify this Form it is being shown on the given Display

param
d the Display showing this Form

        super.callShowNotify(d);

        synchronized (Display.LCDUILock) {
            // Whenever this Form is shown, update the layout
            layout();

            // In most cases, we reset the form to the top
            // The cast is safe because paintDelegate is always
            // either a List, TextBox, Alert or this Form itself.
            if (((Screen)paintDelegate).resetToTop) {
                traverseIndex = -1;
                view[Y] = 0;
                view[X] = 0;
            } else {
                ((Screen)paintDelegate).resetToTop = true;
            }
        }

        // We issue a default traverse when a Form is initially
        // shown to traverse to the first item in the Form
        traverse(CustomItem.NONE);
    
voidcommitPendingInteraction()
Called to commit any pending user interaction for the current item. Override the no-op in Displayable.

        Item curItem = getCurrentItem();
        if (curItem != null) {
            curItem.commitPendingInteraction();
        }
    
public voiddelete(int itemNum)
Deletes the Item referenced by itemNum. The size of the Form shrinks by one. It is legal to delete all items from a Form. The itemNum parameter must be within the range [0..size()-1], inclusive.

param
itemNum the index of the item to be deleted
throws
IndexOutOfBoundsException if itemNum is invalid

        synchronized (Display.LCDUILock) {
            if (itemNum < 0 || itemNum >= numOfItems) {
                throw new IndexOutOfBoundsException();
            }

            Item deletedItem = items[itemNum];

            deletedItem.setOwner(null);

            numOfItems--;

            if (traverseIndex == itemNum) {
                formMode = FORM_TRAVERSE;
            }

            if (traverseIndex > itemNum || traverseIndex == numOfItems) {
                traverseIndex--;
            }

            if (itemNum < numOfItems) {
                System.arraycopy(items, itemNum + 1, items, itemNum,
                                 numOfItems - itemNum);
            }

            // Delete reference to the last item 
            // that was left after array copy
            items[numOfItems] = null;

            // The Form is clear; reset its state
            if (numOfItems == 0 && items.length > GROW_SIZE) {
                items = new Item[GROW_SIZE];                 // start fresh
            }

            invalidate(null);
        } // synchronized
    
public voiddeleteAll()
Deletes all the items from this Form, leaving it with zero items. This method does nothing if the Form is already empty.

since
MIDP 2.0

        synchronized (Display.LCDUILock) {
            if (numOfItems == 0) {
                return;
            }

            for (int x = 0; x < numOfItems; x++) {
                items[x].setOwner(null);
                items[x] = null;
            }
            if (items.length > GROW_SIZE) {
                items = new Item[GROW_SIZE];                     // start fresh
            }

            // Reset form state
            numOfItems = 0;
            formMode = FORM_TRAVERSE;
            traverseIndex = -1;

            invalidate(null);
        }
    
intfindNearestNeighborDown()
Find the nearest neighbor to the current traversed-to Item moving downward

return
the index of the nearest neighbor down

        // SYNC NOTE: see traverse()

        if (traverseIndex == -1) {
            return 0;
        }

        int a1 = items[traverseIndex].bounds[X];
        int b1 = items[traverseIndex].bounds[Y];
        int a2 = a1 + items[traverseIndex].bounds[WIDTH];
        int b2 = b1 + items[traverseIndex].bounds[HEIGHT];

        b2++;

        int x1, y1, x2, y2;
        int greatestY = -1;

        while (true) {
            for (int i = traverseIndex + 1; i < numOfItems; i++) {
                x1 = items[i].bounds[X];
                y1 = items[i].bounds[Y];
                x2 = x1 + items[i].bounds[WIDTH];
                y2 = y1 + items[i].bounds[HEIGHT];

                if (y2 > greatestY) {
                    greatestY = y2;
                }

                x1 = (a1 > x1) ? a1: x1;
                y1 = (b1 > y1) ? b1: y1;
                x2 = (a2 < x2) ? a2: x2;
                y2 = (b2 < y2) ? b2: y2;

                if (x2 >= x1 & y2 >= y1) {
                    return i;
                }
            }

            b2 = b2 + CELL_SPACING;

            if (b2 > greatestY) {
                break;
            }
        }

        return -1;
    
intfindNearestNeighborUp()
Find the nearest neighbor to the current traversed-to Item moving upward

return
the index of the nearest neighbor up

        // SYNC NOTE: see traverse()

        if (traverseIndex == -1) {
            return 0;
        }

        int a1 = items[traverseIndex].bounds[X];
        int b1 = items[traverseIndex].bounds[Y];
        int a2 = a1 + items[traverseIndex].bounds[WIDTH];
        int b2 = b1 + items[traverseIndex].bounds[HEIGHT];

        b1--;
        b2--;

        int x1, y1, x2, y2;

        while (b1 >= 0) {
            for (int i = traverseIndex - 1; i >= 0; i--) {
                x1 = items[i].bounds[X];
                y1 = items[i].bounds[Y];
                x2 = x1 + items[i].bounds[WIDTH];
                y2 = y1 + items[i].bounds[HEIGHT];

                x1 = (a1 > x1) ? a1: x1;
                y1 = (b1 > y1) ? b1: y1;
                x2 = (a2 < x2) ? a2: x2;
                y2 = (b2 < y2) ? b2: y2;

                if (x2 >= x1 & y2 >= y1) {
                    return i;
                }
            }

            b1 = b1 - CELL_SPACING;
        }

        return -1;
    
voidformTraverse(int dir)
Traverse this Form in the given direction

param
dir the direction of traversal

        // SYNC NOTE: rather than copy datasets all the time, we simply
        // protect ourselves from unexpected errors using a try/catch in
        // the traverse() method itself. The only problem that could occur
        // would be if the application added or removed Items on the fly
        // while the user was traversing. This could lead to an error
        // condition, but it would be rectified immediately by the
        // pending validation caused by the append/delete. This seems
        // preferable to wasting a lot of heap or employing complicating
        // locking behavior

        if (numOfItems == 0) {
            return;
        }

        // if that is the initial traverse in the form,
        // do it first
        if (dir == CustomItem.NONE) {
            setTraverseIndex(dir, traverseIndex, 
                             traverseIndex == -1 ? 0 : traverseIndex);

            return;
        }

        // If we need to scroll to fit the current item,
        // don't do a traverse, just return
        if (traverseIndex >= 0 &&
            !items[traverseIndex].shouldSkipTraverse() &&
            scrollForBounds(dir, items[traverseIndex].bounds)) {              
                validateVisibility = true;
                repaintContents();

                return;
        }

        int bendDir = dir;
        // We know we must do a "traversal". We always
        // perform a "direction bend" to achieve our
        // desired traversal semantics
        if (dir == Canvas.DOWN) {
            bendDir = Canvas.RIGHT;
        } else if (dir == Canvas.UP) {
            bendDir = Canvas.LEFT;
        }

        // Find new traverseIndex 
        int oldIndex, newIndex = traverseIndex;
            
        do {

            oldIndex = newIndex;

            // Traverse to the next Item on the Form in the
            // given direction
            switch (bendDir) {
            case Canvas.UP:
                int ni1 = findNearestNeighborUp();
                if (ni1 != -1) {
                    newIndex = ni1;
                }
                break;
            case Canvas.DOWN:
                int ni2 = findNearestNeighborDown();
                if (ni2 != -1) {
                    newIndex = ni2;
                }
                break;
            case Canvas.LEFT:
                if (newIndex > 0) {
                    newIndex--;
                }
                break;
            case Canvas.RIGHT:
                if (newIndex < (numOfItems - 1)) {
                    newIndex++;
                }
                break;
            }

            // If we can't traverse any further in the given 
            // direction, return 
            if (oldIndex == newIndex) {
                return;
            }

        } while (items[newIndex].shouldSkipTraverse());

        setTraverseIndex(dir, traverseIndex, newIndex);
    
public Itemget(int itemNum)
Gets the item at given position. The contents of the Form are left unchanged. The itemNum parameter must be within the range [0..size()-1], inclusive.

param
itemNum the index of item
return
the item at the given position
throws
IndexOutOfBoundsException if itemNum is invalid

        synchronized (Display.LCDUILock) {
            if (itemNum < 0 || itemNum >= numOfItems) {
                throw new IndexOutOfBoundsException();
            }

            return items[itemNum];
        }
    
ItemgetCurrentItem()
Gets item currently in focus. This is will be only applicable to Form. The rest of the subclasses will return null.

return
the item currently in focus in this Displayable; if there are no items in focus, null is returned

        // SYNC NOTE: getCurrentItem is always called from within
        // a hold on LCDUILock
        return traverseIndex < 0 ? null : items[traverseIndex];
    
public intgetHeight()
Returns the height in pixels of the displayable area available for items. This value is the height of the form that can be displayed without scrolling. The value may depend on how the device uses the screen and may be affected by the presence or absence of the ticker, title, or commands.

return
the height of the displayable area of the Form in pixels
since
MIDP 2.0

        return viewport[HEIGHT] - CELL_SPACING - CELL_SPACING;
    
private intgetItemHeight(int index, int pW)
Get item's height based on the width

param
index the index of the item which height is being calculated
param
pW the width set for the item
return
the height of the item

        // SYNC NOTE: protected by the lock around calls to layout()

        int pH;

        // If the Item can be shrunken, we use its minimum height,
        // and its preferred height if it is not
        if (items[index].shouldVShrink()) {
            pH = items[index].callMinimumHeight();
        } else {
            pH = items[index].lockedHeight;
            if (pH == -1) {
                pH = items[index].callPreferredHeight(pW);
            }
        }
        
        // If we can't scroll vertically, clip the item's height
        // if it can't fit in the view. We would also enforce a
        // notion of a "maximum vertical height" here if we had one
        if (!SCROLLS_VERTICAL &&
            (pH > (viewport[HEIGHT] - CELL_SPACING - CELL_SPACING))) {
            pH = viewport[HEIGHT] - CELL_SPACING - CELL_SPACING;
        }
    
        return pH;
    
ItemStateListenergetItemStateListener()
Retrieve the ItemStateListener for this Form. NOTE: calls to this method should only occur from within a lock on LCDUILock.

return
ItemStateListener The ItemStateListener of this Form, null if there isn't one set

        return itemStateListener;
    
public intgetWidth()
Returns the width in pixels of the displayable area available for items. The value may depend on how the device uses the screen and may be affected by the presence or absence of the ticker, title, or commands. The Items of the Form are laid out to fit within this width.

return
the width of the Form in pixels
since
MIDP 2.0

        return viewport[WIDTH] - CELL_SPACING - CELL_SPACING;
    
private intinflateHExpandables(int rowStart, int rowEnd, int space)
Inflate all the horizontally 'expandable' items on a row

param
rowStart the index of the first row element
param
rowEnd the index of the last row element
param
space the amount of empty space on this row
return
the amount of empty space after expansion

        // SYNC NOTE: protected by the lock around calls to layout()

        if (space == 0) {
            return 0;
        }

        int numExp = 0;
        // We first loop through and count the expandables
        for (int i = rowStart; i <= rowEnd; i++) {
            if (items[i].shouldHExpand()) {
                numExp++;
            }
        }

        if (numExp == 0 || space < numExp) {
            return space;
        }

        space = space / numExp;

        // We then add the same amount to each Expandable
        for (int i = rowStart; i <= rowEnd; i++) {
            if (items[i].shouldHExpand()) {
                items[i].bounds[WIDTH] += space;
                items[i].bounds[HEIGHT] = 
                    getItemHeight(i, items[i].bounds[WIDTH]); 

                // We right shift each subsequent item on the row
                for (int j = i + 1; j <= rowEnd; j++) {
                    items[j].bounds[X] += space;
                }
            }
        }

        space = viewport[WIDTH] - CELL_SPACING -
                (items[rowEnd].bounds[X] + items[rowEnd].bounds[WIDTH]);

        return space;
    
private intinflateHShrinkables(int rowStart, int rowEnd, int space)
Inflate all the horizontally 'shrinkable' items on a row

param
rowStart the index of the first row element
param
rowEnd the index of the last row element
param
space the amount of empty space left in pixels in this row
return
the amount of empty space on this row after shinkage

        // SYNC NOTE: protected by the lock around calls to layout()

        if (space == 0) {
            return 0;
        }

        // To inflate shrinkables, we make a first pass gathering
        // the smallest proportion (the baseline)
        int baseline = Integer.MAX_VALUE;
        int pW, prop = 0;

        for (int i = rowStart; i <= rowEnd; i++) {
            if (items[i].shouldHShrink()) {
                pW = items[i].lockedWidth;
                if (pW == -1) {
                    pW = items[i].callPreferredWidth(items[i].lockedHeight);
                }
                prop = pW - items[i].getMinimumWidth();
                if (prop > 0 && prop < baseline) {
                    baseline = prop;
                }
            }
        }

        // If there are no shrinkables, return;
        if (baseline == Integer.MAX_VALUE) {
            return space;
        }

        prop = 0;

        // Now we loop again, adding up all the proportions so
        // we can find the adder
        for (int i = rowStart; i <= rowEnd; i++) {
            if (items[i].shouldHShrink()) {
                pW = items[i].lockedWidth;
                if (pW == -1) {
                    pW = items[i].callPreferredWidth(items[i].lockedHeight);
                }
                prop += ((pW - items[i].getMinimumWidth()) / baseline);
            }
        }


        // Now we compute the adder, and add it to each of the
        // shrinkables, times its proportion
        int adder = space / prop;

        for (int i = rowStart; i <= rowEnd; i++) {
            if (items[i].shouldHShrink()) {
                pW = items[i].lockedWidth;
                if (pW == -1) {
                    pW = items[i].callPreferredWidth(items[i].lockedHeight);
                }
                space = pW - items[i].getMinimumWidth();

                // The proportionate amount of space to add
                prop = adder * (space / baseline);

                // We only enlarge the item to its preferred width at
                // a maximum
                if (space > prop) {
                    space = prop;
                }

                items[i].bounds[WIDTH] += space;
                items[i].bounds[HEIGHT] = 
                    getItemHeight(i, items[i].bounds[WIDTH]);

                // Now we have to shift the rest of the elements on the line
                for (int j = i + 1; j <= rowEnd; j++) {
                    items[j].bounds[X] += space;
                }
            }
        }

        // NTS: If an item only enlarges to its preferred size, it throws
        // off the algorithm a bit in that there will be empty space left
        // over. Shouldn't really matter though

        space = viewport[WIDTH] - CELL_SPACING -
                (items[rowEnd].bounds[X] + items[rowEnd].bounds[WIDTH]);

        return space;
    
public voidinsert(int itemNum, Item item)
Inserts an item into the Form just prior to the item specified. The size of the Form grows by one. The itemNum parameter must be within the range [0..size()], inclusive. The index of the last item is size()-1, and so there is actually no item whose index is size(). If this value is used for itemNum, the new item is inserted immediately after the last item. In this case, the effect is identical to {@link #append(Item) append(Item)}.

The semantics are otherwise identical to {@link #append(Item) append(Item)}.

param
itemNum the index where insertion is to occur
param
item the item to be inserted
throws
IndexOutOfBoundsException if itemNum is invalid
throws
IllegalStateException if the item is already owned by a container
throws
NullPointerException if item is null

        synchronized (Display.LCDUILock) {
            // NullPointerException will be thrown
            // by item.getOwner() if item == null
            if (item.getOwner() != null) {
                throw new IllegalStateException();
            }

            if (itemNum < 0 || itemNum > numOfItems) {
                throw new IndexOutOfBoundsException();
            }
            insertImpl(itemNum, item);
        }
    
private intinsertImpl(int itemNum, Item item)
Insert an Item into this Form

param
itemNum The index into the Item array to insert this Item
param
item The Item to insert
return
int The index at which the newly inserted Item can be found


        if (traverseIndex >= itemNum || traverseIndex == -1) {
            traverseIndex++;
        }

        if (items.length == numOfItems) {
            Item newItems[] = new Item[numOfItems + GROW_SIZE];
            System.arraycopy(items, 0, newItems, 0, itemNum);
            System.arraycopy(items, itemNum, newItems, itemNum + 1,
                             numOfItems - itemNum);
            items = newItems;
        } else {
            // if we're not appending
            if (itemNum != numOfItems) {
                System.arraycopy(items, itemNum, items, itemNum + 1,
                                 numOfItems - itemNum);
            }
        }

        numOfItems++;

        //
        // the arraycopy copied the reference to the item at this
        // spot. if we call setImpl without setting the index to null
        // setImpl will set the items owner to null.
        //
        items[itemNum] = null;

        setImpl(itemNum, item);
        return itemNum;
    
booleanitemTraverse(int dir)
Perform an internal traverse on the currently traversed-to Item in the given direction.

param
dir the direction of traversal
return
true if this item performed internal traversal

        // SYNC NOTE: rather than copy datasets all the time, we simply
        // protect ourselves from unexpected errors using a try/catch in
        // the traverse() method itself. The only problem that could occur
        // would be if the application added or removed Items on the fly
        // while the user was traversing. This could lead to an error
        // condition, but it would be rectified immediately by the
        // pending validation caused by the append/delete. This seems
        // preferable to wasting a lot of heap or employing complicating
        // locking behavior

        if (traverseIndex == -1) {
            return false;
        }

        // If we need to scroll to fit the current internal traversal
        // bounds, just return
        if (formMode == ITEM_TRAVERSE && scrollForBounds(dir, visRect)) {
            validateVisibility = true;
            repaintContents();
            return true;
        }

        visRect[X] = visRect[Y] = 0;
        visRect[WIDTH] = items[traverseIndex].bounds[WIDTH];
        visRect[HEIGHT] = items[traverseIndex].bounds[HEIGHT];

        if (items[traverseIndex].callTraverse(dir, viewport[WIDTH],
                                              viewport[HEIGHT], visRect)) {

            // Since visRect is sent to the Item in its own coordinate
            // space, we translate it back into the overall Form's
            // coordinate space
            visRect[X] += items[traverseIndex].bounds[X];
            visRect[Y] += items[traverseIndex].bounds[Y];

            formMode = ITEM_TRAVERSE;

            if (scrollForTraversal(dir, visRect)) {
                validateVisibility = true;
                repaintContents();
            }

            return true;

        } else {
            visRect[X] += items[traverseIndex].bounds[X];
            visRect[Y] += items[traverseIndex].bounds[Y];
            return false;
        }
    
voidlayout()
Layout the contents of this Form, and call super.layout() to layout the contents of the parent Screen or Displayable

        // SYNC NOTE: layout() is always called from within a hold
        // on LCDUILock

        super.layout();

        // If we don't have any Items, just return
        if (numOfItems == 0) {
            return;
        }

        // The index of the first Item in the horizontal row
        int rowStart = 0;

        // The 'viewable' represents the viewpane. It starts out life the
        // size of the viewport, but gets whittled down as each Item gets
        // laid out and occupies space in it. It effectively keeps a running
        // total of what space is available due to the Items which have
        // already been laid out
        if (viewable == null) {
            viewable = new int[4];
        }

        // We only allow space for the traversal indicator if we
        // have more than one item - because we only draw the traversal
        // indicator if we have more than one item to traverse to.
        // View's width is set to the maximum allowable width,
        // while view's height is initialized with initial padding and
        // and grows when new row is added.
        if (numOfItems > 1) {
            viewable[X] = CELL_SPACING;
            viewable[Y] = CELL_SPACING;
            viewable[WIDTH] = viewport[WIDTH] - CELL_SPACING;
            viewable[HEIGHT] = viewport[HEIGHT] - CELL_SPACING;

            view[WIDTH] = viewable[WIDTH] - CELL_SPACING;
            view[HEIGHT] = CELL_SPACING;

        } else {
            viewable[X] = 1;
            viewable[Y] = 1;
            viewable[WIDTH] = viewport[WIDTH] - 1;
            viewable[HEIGHT] = viewport[HEIGHT] - 1;

            view[WIDTH] = viewable[WIDTH] - 1;
            view[HEIGHT] = 1;
        }

        // A running variable which maintains the height of the
        // tallest item on a line
        int lineHeight = 0;
        int pW, pH;

        // We loop through all the Items. NTS: we may be able to improve
        // this given we know which Item called invalidate()f;
        for (int index = 0; index < numOfItems; index++) {
            
            // If the Item can be shrunken, get its minimum width,
            // and its preferred if it is not
            if (items[index].shouldHShrink()) {
                pW = items[index].callMinimumWidth();
            } else {
                if (items[index].lockedWidth != -1) {
                    pW = items[index].lockedWidth;
                } else {
                    // if height is locked default preferred width
                    // will be used, otherwise width will be calculated
                    // based on lockedHeight
                    pW = items[index].callPreferredWidth(
                                      items[index].lockedHeight);
                }
            }
        
            // We have a notion of the maximum allowable width an Item can
            // have, so we enforce it first here:
            if (!SCROLLS_HORIZONTAL && (pW > view[WIDTH])) {
                pW = view[WIDTH];
            }

            // We use a separate boolean here to check for each case of
            // requiring a new line (rather than the if() from hell)
            boolean newLine = (index > 0 && items[index - 1].equateNLA());
            newLine = (newLine || items[index].equateNLB());
            newLine = (newLine || (pW > (viewable[WIDTH] - CELL_SPACING)));

            // We linebreak if there is an existing row
            if (newLine && (lineHeight > 0)) {

                // First, handle current row's layout directives
                try {
                    lineHeight = layoutRowHorizontal(rowStart, index - 1, 
                                                     viewable[WIDTH],
                                                     lineHeight);
                    layoutRowVertical(rowStart, index - 1, lineHeight);
                    if (numOfItems > 1) {
                        view[HEIGHT] += lineHeight + CELL_SPACING;
                    } else {
                        view[HEIGHT] += lineHeight + 1;
                    }
                } catch (Throwable t) {
                    Display.handleThrowable(t);
                }

                // Then, reset the viewable, lineHeight, and rowStart
                viewable[X] = CELL_SPACING;
                viewable[Y] = view[HEIGHT];
                viewable[WIDTH] = viewport[WIDTH] - CELL_SPACING;
                viewable[HEIGHT] -= (lineHeight + CELL_SPACING);

                lineHeight = 0;
                rowStart = index;
            }

            pH = getItemHeight(index, pW);

            // If the Item has never been laid out before, instantiate
            // its bounds[]
            if (items[index].bounds == null) {
                items[index].bounds = new int[4];
            }

            // If the Item is changing size, set the flag so that callPaint()
            // will call the Item's sizeChanged() method before painting
            if (items[index].bounds[WIDTH] != pW ||
                items[index].bounds[HEIGHT] != pH) {
                items[index].sizeChanged = true;
            }

            // We assign the item a bounds which is its pixel location,
            // width, and height in coordinates which represent offsets
            // of the viewport origin (that is, are in the viewport
            // coordinate space)
            items[index].bounds[X] = viewable[X];
            items[index].bounds[Y] = viewable[Y];
            items[index].bounds[WIDTH] = pW;
            items[index].bounds[HEIGHT] = pH;

            /*
            System.err.println("index: " +index);
            System.err.print("\t" + items[index].bounds[X]);
            System.err.print(", " + items[index].bounds[Y]);
            System.err.print(", " + items[index].bounds[WIDTH]);
            System.err.println(", " + items[index].bounds[HEIGHT]);
            */

            // If this Item is currently the tallest on the line, its
            // height becomes our prevailing lineheight
            if (pH > lineHeight) {
                lineHeight = pH;
            }

            // We whittle down the viewpane by the Item's dimensions,
            // effectively maintaining how much space is left for the
            // remaining Items, IFF the item has some positive width
            if (pW > 0) {
                viewable[WIDTH] -= (pW + CELL_SPACING);
                viewable[X]     += (pW + CELL_SPACING);
            }

        } // for

        // Before we quit, layout the last row we did in the loop
        try {
            lineHeight = layoutRowHorizontal(rowStart, numOfItems-1, 
                                             viewable[WIDTH], lineHeight);
            layoutRowVertical(rowStart, numOfItems-1, lineHeight);
            if (numOfItems > 1) {
                view[HEIGHT] += lineHeight + CELL_SPACING;
            } else {
                view[HEIGHT] += lineHeight + 1;
            }
        } catch (Throwable t) {
            Display.handleThrowable(t);
        }
    
private intlayoutRowHorizontal(int rowStart, int rowEnd, int hSpace, int rowHeight)
After the contents of a row have been determined, layout the items on that row, taking into account the individual items' horizontally oriented layout directives.

param
rowStart the index of the first row element
param
rowEnd the index of the last row element
param
hSpace the amount of empty space in pixels in this row before inflation
param
rowHeight the old row height
return
the new rowHeight for this row after all of the inflations

        // SYNC NOTE: protected by the lock around calls to layout()

        hSpace = inflateHShrinkables(rowStart, rowEnd, hSpace);
        hSpace = inflateHExpandables(rowStart, rowEnd, hSpace);


        // if any of the items were inflated we have to recalculate
        // the new row height for this row
        rowHeight = 0;
        for (int i = rowStart; i <= rowEnd; i++) {
            if (rowHeight < items[i].bounds[HEIGHT]) {
                rowHeight = items[i].bounds[HEIGHT];
            }
        }

        if (hSpace == 0) {
            return rowHeight;
        }

        // layout the group of items with LAYOUT_RIGHT layout
        for (; rowStart <= rowEnd; rowEnd--) {

            int layout = items[rowEnd].callGetLayout();
            if ((layout & LAYOUT_HMASK) != Item.LAYOUT_RIGHT) {
                break;
            }

            items[rowEnd].bounds[X] += hSpace;
        }
 
        int hLayout = 0;

        // skip the group of items with LAYOUT_LEFT layout;
        // layout the group of items with LAYOUT_CENTER layout by
        // shifting  x bound to the right by half the amount of the empty space

        hSpace = hSpace/2;

        for (; rowStart <= rowEnd; rowStart++) {

            hLayout = items[rowStart].callGetLayout() & LAYOUT_HMASK;
            if (hLayout == Item.LAYOUT_LEFT) {
                continue;
            } else if (hLayout != Item.LAYOUT_CENTER) {
                break;
            }

            items[rowStart].bounds[X] += hSpace;
        }
   
        return rowHeight;
    
private voidlayoutRowVertical(int rowStart, int rowEnd, int lineHeight)
After the contents of a row have been determined, layout the items on that row, taking into account the individual items' vertically oriented layout directives.

param
rowStart the index of the first row element
param
rowEnd the index of the last row element
param
lineHeight the overall height in pixels of the line

        // SYNC NOTE: protected by the lock around calls to layout()

        int space = 0;
        int pH = 0;

        for (int i = rowStart; i <= rowEnd; i++) {

            // Items that have the LAYOUT_VSHRINK  directive are expanded 
            // to their preferred  height or to the height of the row, 
            // whichever is smaller. Items that are still shorter than 
            // the row height and that have the LAYOUT_VEXPAND directive 
            // will expand to the height of the row.
            if (items[i].shouldVExpand()) {
                items[i].bounds[HEIGHT] = lineHeight;

            } else if (items[i].shouldVShrink()) {
                pH = items[i].lockedHeight;
                if (pH == -1) {
                    pH = items[i].callPreferredHeight(items[i].bounds[WIDTH]);
                }
                if (pH > lineHeight) {
                    pH = lineHeight;
                }
                items[i].bounds[HEIGHT] = pH;
            }

            // By default, layout() puts the Item at the top so we simply
            // add on to the height
            switch (items[i].callGetLayout() & LAYOUT_VMASK) {
                case Item.LAYOUT_VCENTER:
                    space = lineHeight - items[i].bounds[HEIGHT];
                    if (space > 0) {
                        items[i].bounds[Y] += (space / 2);
                    }
                    break;
                case Item.LAYOUT_BOTTOM:
                    space = lineHeight - items[i].bounds[HEIGHT];
                    if (space > 0) {
                        items[i].bounds[Y] += space;
                    }
                    break;
                case Item.LAYOUT_TOP:
                default:
                    // by default, layout() puts the Item at the top
                    break;
            }
        }
    
voidpaintItem(Item item, Graphics g, int[] clip)
Paint an item

param
item the Item to paint
param
g the Graphics object to paint to
param
clip the original graphics clip to restore

        // SYNC NOTE: see callPaint()

        // NOTE: Its possible, that an Item is in an invalid state
        // during a requested repaint. Its ok to simply return,
        // because it means there is a validation event coming on
        // the event thread. When the form re-validates, the Item
        // will be given a proper bounds and will be repainted
        if (item.bounds == null) {
            return;
        }

        int tX = item.bounds[X] + viewport[X] - view[X];
        int tY = item.bounds[Y] + viewport[Y] - view[Y];

        // If we're already beyond the clip, quit looping, as long
        // as we're not validating the visibility of Items after a
        // scroll (calling show/hideNotify())
        if (((tY + item.bounds[HEIGHT] < clip[Y]) || 
             (tY > (clip[Y] + clip[HEIGHT]))) && 
            !validateVisibility) {
            return;
        }

        // Clip the dirty region to only include the item
        g.clipRect(tX, tY, item.bounds[WIDTH], item.bounds[HEIGHT]);

        // If the Item is inside the clip, go ahead and paint it
        if (g.getClipWidth() > 0 && g.getClipHeight() > 0) {

            if (validateVisibility && !item.visible) {
                item.callShowNotify();
            }

            if (item.sizeChanged) {
                item.callSizeChanged(
                    item.bounds[WIDTH], item.bounds[HEIGHT]);
                item.sizeChanged = false;
            }

            // Translate into the Item's coordinate space
            g.translate(tX, tY);

            // NYI: call showNotify() on the Item first

            // We translate the Graphics into the Item's
            // coordinate space
            item.callPaint(g, item.bounds[WIDTH], item.bounds[HEIGHT]);

            // We don't need to do a translate because we are
            // doing a reset() below that will negate it

        } else if (validateVisibility && item.visible) {
            item.callHideNotify();
        }

        // Restore the clip to its original context so
        // future clipRect() calls will have the correct intersection
        g.reset(clip[X], clip[Y],
                clip[X] + clip[WIDTH], clip[Y] + clip[HEIGHT]);

        // Paint the traversal indicator
        if (traverseIndex >= 0 && numOfItems > 1 &&
            item == items[traverseIndex] && indicateTraverse) {

            g.clipRect(tX - CELL_SPACING, tY - CELL_SPACING,
                       item.bounds[WIDTH] + (2 * CELL_SPACING),
                       item.bounds[HEIGHT] + (2 * CELL_SPACING));
            paintTraversalIndicator(g, tX, tY);
            g.setClip(clip[X], clip[Y], clip[WIDTH], clip[HEIGHT]);
        }
    
voidpaintTraversalIndicator(Graphics g, int x, int y)
Paint the traversal indicator. The width/height are obtained from the current traversal item's bounds.

param
g the Graphics to paint on
param
x the x orign coordinate to paint the traversal indicator
param
y the y origin coordinate to paint the traversal indicator

        // SYNC NOTE: see callPaint()

        // NTS: This may need to special case StringItem?
        g.setColor(TRAVERSE_INDICATOR_COLOR);
        if (TRAVERSE_INDICATOR == ONE_PIXEL_BOX) {
            g.drawRect(x - (CELL_SPACING - 1), y - (CELL_SPACING - 1),
                    items[traverseIndex].bounds[WIDTH] + (CELL_SPACING + 1),
                    items[traverseIndex].bounds[HEIGHT] + (CELL_SPACING + 1));
        } else if (TRAVERSE_INDICATOR == TRIANGLE_CORNERS) {
            g.fillTriangle(items[traverseIndex].bounds[WIDTH] + x,
                           y,
                           items[traverseIndex].bounds[WIDTH] + x,
                           y - (CELL_SPACING),
                           items[traverseIndex].bounds[WIDTH] + x +
                                (CELL_SPACING),
                           y);
            g.fillTriangle(x,
                           items[traverseIndex].bounds[HEIGHT] + y,
                           x,
                           items[traverseIndex].bounds[HEIGHT] + y +
                                (CELL_SPACING - 1),
                           x - (CELL_SPACING - 1),
                           items[traverseIndex].bounds[HEIGHT] + y);
            /*
            This code block puts the triangles at the upper left
            and lower right corners
            g.fillTriangle(x, y,
                           x, y - (CELL_SPACING),
                           x - (CELL_SPACING), y);
            g.fillTriangle(items[traverseIndex].bounds[WIDTH] + x,
                           items[traverseIndex].bounds[HEIGHT] + y,
                           items[traverseIndex].bounds[WIDTH] + x +
                                (CELL_SPACING - 1),
                           items[traverseIndex].bounds[HEIGHT] + y,
                           items[traverseIndex].bounds[WIDTH] + x,
                           items[traverseIndex].bounds[HEIGHT] + y +
                                (CELL_SPACING - 1));
            */
        }
        g.setColor(Display.FG_COLOR);
    
booleanscrollForBounds(int dir, int[] bounds)
Scroll the viewport to fit the bounds after an intial traversal has been made

param
dir the direction of traversal into the Item
param
bounds the bounding box of the traversal location
return
True if the viewport had to be scrolled to fit the item

        // SYNC NOTE: see traverse()

        // We can just short circuit scrolling all together if
        // we know our view is smaller than the viewport
        if (view[HEIGHT] < viewport[HEIGHT]) {
            return false;
        }

        switch (dir) {
            case Canvas.UP:
                if (bounds[Y] >= view[Y]) {
                    return false;
                } else {
                    view[Y] -= Screen.CONTENT_HEIGHT;
                    if (view[Y] < 0) {
                        view[Y] = 0;
                    }
                    return true;
                }
            case Canvas.DOWN:
                if (bounds[Y] + bounds[HEIGHT]  + CELL_SPACING <=
                        view[Y] + viewport[HEIGHT]) {
                    return false;
                } else {
                    view[Y] += Screen.CONTENT_HEIGHT;
                    if (view[Y] > view[HEIGHT] - viewport[HEIGHT]) {
                        view[Y] = view[HEIGHT] - viewport[HEIGHT];
                    }
                    return true;
                }
            case Canvas.LEFT:
                // we don't scroll horizontally just yet
                break;
            case Canvas.RIGHT:
                // we don't scroll horizontally just yet
                break;
        }
        return false;
    
booleanscrollForTraversal(int dir, int[] bounds)
Scroll the viewport to fit the item when initially traversing to the Item (or within the Item) in the given direction.

param
dir the direction of traversal into the Item
param
bounds the bounding box of the traversal location
return
True if the viewport had to be scrolled to fit the item

        // SYNC NOTE: see traverse()

        // We can just short circuit scrolling all together if
        // we know our view is smaller than the viewport
        if (view[HEIGHT] < viewport[HEIGHT]) {
            if (view[Y] > 0) {
                view[Y] = 0;
                return true;
            }
            return false;
        }

        // If the bounds are fully in our view, just return false
        if (bounds[Y] > view[Y] &&
                (bounds[Y] + bounds[HEIGHT] < view[Y] + viewport[HEIGHT])) {
            return false;
        } else {
            if (SCROLLS_VERTICAL) {
                if (bounds[HEIGHT] > viewport[HEIGHT]) {
                    if (dir == Canvas.DOWN || dir == Canvas.LEFT ||
                            dir == CustomItem.NONE) {
                        view[Y] = bounds[Y] - CELL_SPACING;
                    } else if (dir == Canvas.UP || dir == Canvas.RIGHT) {
                        view[Y] = bounds[Y] + bounds[HEIGHT] + CELL_SPACING -
                            viewport[HEIGHT];
                    }
                } else {
                    if (dir == Canvas.DOWN || dir == Canvas.LEFT ||
                            dir == CustomItem.NONE) {
                        view[Y] = bounds[Y] + bounds[HEIGHT] + CELL_SPACING -
                            viewport[HEIGHT];
                    } else if (dir == Canvas.UP || dir == Canvas.RIGHT) {
                        view[Y] = bounds[Y] - CELL_SPACING;
                    }
                }

                if ((view[Y] + viewport[HEIGHT]) > view[HEIGHT]) {
                    view[Y] = view[HEIGHT] - viewport[HEIGHT];
                }
                if (view[Y] < 0) {
                    view[Y] = 0;
                }
                return true;
            } else if (SCROLLS_HORIZONTAL) {
                // Not supported
            }
        }

        return false;
    
public voidset(int itemNum, Item item)
Sets the item referenced by itemNum to the specified item, replacing the previous item. The previous item is removed from this Form. The itemNum parameter must be within the range [0..size()-1], inclusive.

The end result is equal to insert(n, item); delete(n+1);
although the implementation may optimize the repainting and usage of the array that stores the items.

param
itemNum the index of the item to be replaced
param
item the new item to be placed in the Form
throws
IndexOutOfBoundsException if itemNum is invalid
throws
IllegalStateException if the item is already owned by a container
throws
NullPointerException if item is null

        synchronized (Display.LCDUILock) {
            // NullPointerException will be thrown
            // by item.getOwner() if item == null
            if (item.getOwner() != null) {
                throw new IllegalStateException();
            }

            if (itemNum < 0 || itemNum >= numOfItems) {
                throw new IndexOutOfBoundsException();
            }

            setImpl(itemNum, item);
        }
    
voidsetCurrentItem(Item i)
Set the current traversal location to the given Item. This call has no effect if the given Item is the current traversal item, or if the given Item is not part of this Form.

param
i the Item to make the current traversal item

        // SYNC NOTE: This method is called from Display which holds
        // LCDUILock around the call

        if (i == null || i.owner != this) {
            return;
        }

        if (traverseIndex != -1 && items[traverseIndex] == i) {
            return;
        }

        for (int x = 0; x < numOfItems; x++) {
            if (items[x] == i) {
                setTraverseIndex(CustomItem.NONE, traverseIndex, x);
                ((Screen)paintDelegate).resetToTop = false;
                return;
            }
        }
    
private voidsetImpl(int itemNum, Item item)
Set a specific Item index to be a new Item

param
itemNum The Item index to change
param
item The new Item to set

        Item oldItem = items[itemNum];
        if (oldItem != null) {
            oldItem.setOwner(null);
        }

        item.setOwner(this);

        items[itemNum] = item;

        invalidate(null);
    
public voidsetItemStateListener(ItemStateListener iListener)
Sets the ItemStateListener for the Form, replacing any previous ItemStateListener. If iListener is null, simply removes the previous ItemStateListener.

param
iListener the new listener, or null to remove it

        synchronized (Display.LCDUILock) {
            itemStateListener = iListener;
        }
    
voidsetTraverseIndex(int dir, int oldIndex, int newIndex)
Sets traverseIndex on this Form

param
dir the direction of traversal
param
oldIndex the old traverseIndex
param
newIndex the new traverseIndex to be set

        // SYNC NOTE: rather than copy datasets all the time, we simply
        // protect ourselves from unexpected errors using a try/catch in
        // the traverse() method itself. The only problem that could occur
        // would be if the application added or removed Items on the fly
        // while the user was traversing. This could lead to an error
        // condition, but it would be rectified immediately by the
        // pending validation caused by the append/delete. This seems
        // preferable to wasting a lot of heap or employing complicating
        // locking behavior

        if (dir == CustomItem.NONE) {
            // Make sure that initial traverseIndex is 
            // not pointing to an item that is not traversable
            int newTraveseIndex = newIndex;

            // find first item with index from traverseIndex till 
            // numOfItems-1 that is traversable
            boolean allSkipped = false;
            while (!allSkipped && items[newIndex].shouldSkipTraverse()) {
                newIndex++;
                if (newIndex == numOfItems) {
                    allSkipped = true;
                }
            }

            // if all items with index from traverseIndex till numOfItems-1 
            // were skipped, we need to check if any of the items with index
            // from traverseIndex-1 till 0 that is traversable
            if (allSkipped) {
                if (newTraveseIndex > 0) {
                    newIndex = newTraveseIndex - 1;
                    while (items[newIndex].shouldSkipTraverse()) {
                        newIndex--;
                        if (newIndex == -1) {
                            return;
                        }
                    } 
                } else {
                    return;
                }
            }
        }

        formMode = FORM_TRAVERSE;

        traverseIndex = newIndex;

        if (oldIndex >= 0 && oldIndex < numOfItems) {
            items[oldIndex].callTraverseOut();
        }

        // setTraverseIndex is called from setCurrentItem.
        // It is possible that there was no layout done all.
        // In that case just return and layout will be done
        // when this form is first shown
        if (items[traverseIndex].bounds == null) {
            return;
        }

        if (dir == CustomItem.NONE) {
            dir = newIndex >= oldIndex ? Canvas.DOWN : Canvas.UP;
        }

        // If the newly traversed-to Item is outside the
        // viewport, we change our mode to FORM_SCROLL
        // so that subsequent 'traverses' will scroll the viewport
        // and bring the Item into view
        scrollForTraversal(dir, items[traverseIndex].bounds);
        itemTraverse(dir);
        validateVisibility = true;
        repaintContents();
        updateCommandSet();
        // FIX ME: improve the painting, a full repaintContents() should not
        // always be necessary
    
public intsize()
Gets the number of items in the Form.

return
the number of items

        // SYNC NOTE: return of atomic value, no locking necessary
        return numOfItems;
    
voidtraverse(int dir)
The generic traverse method. This method determines the current "mode" of traversal and delegates the traverse to the appropriate handler, either formTraverse(), itemTraverse()

param
dir the direction of "traversal"

        // SYNC NOTE: formMode can only ever change as a result of a
        // traverse, which means its single-threaded (on the event thread)
        switch (formMode) {
        case FORM_TRAVERSE:
            try {
                formTraverse(dir);
            } catch (Throwable t) {
                // Swallow the error and move on
            }
            break;
        case ITEM_TRAVERSE:
            try {
                if (!itemTraverse(dir)) {
                    formTraverse(dir);
                }
            } catch (Throwable t) {
                // Swallow the error and move on
            }
            break;
        }