/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package javax.microedition.lcdui;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;
import com.sun.midp.configurator.Constants;
import com.sun.midp.chameleon.skins.ScreenSkin;
import com.sun.midp.chameleon.skins.PTISkin;


/**
 * Look and feel class for Form.
 * See DisplayableLF.java for naming convention.
 */
class FormLFImpl extends ScreenLFImpl implements FormLF {

    /**
     * Creates FormLF associated with passed in form.
     * FormLFImpl maintains an array of views associated with its items.
     * 
     * @param form the Form object associated with this FormLF
     * @param items the array of Items using which the passed in Form
     *        was created
     * @param numOfItems current number of elements
     */
    FormLFImpl(Form form, Item items[], int numOfItems) {
        
        super(form);
        
        // Initialize the in-out rect for Item traversal
        visRect = new int[4];
        
        if (items == null) {
            itemLFs = new ItemLFImpl[GROW_SIZE];
            // numOfLFs was initialized to 0
            // so there is no need to update it
        } else {
            this.itemLFs = new ItemLFImpl[items.length > GROW_SIZE ?
                                         items.length : GROW_SIZE];

            for (int i = 0; i < numOfItems; i++) {
                itemLFs[i] = (ItemLFImpl)items[i].getLF();
            }       
            // right now we have the same number of views as items
            numOfLFs = numOfItems;
        }        
    }
    
    
    /**
     * Creates FormLF for the passed in screen.
     * Passed in ItemLF is added as the only itemLF present.
     * This constructor is used by List and TextBox.
     * 
     * @param screen the Screen object associated with this FormLFImpl
     * @param item the Item to be added to this screen
     */
    FormLFImpl(Screen screen, Item item) {
        super(screen);

        itemLFs = new ItemLFImpl[1];
        itemLFs[0] = (ItemLFImpl)item.getLF();
        numOfLFs = 1;

        visRect = new int[4];

        if (screen instanceof TextBox && item instanceof TextField) {
            TextField textField = (TextField)item;
            ((TextFieldLFImpl)textField.textFieldLF).setBorder(false);
        }
    }

    // ************************************************************
    //  public methods - FormLF interface implementation
    // ************************************************************

    /**
     * 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. Note that null can be passed in
     * clear the previously set current item.
     *
     *
     * @param i the Item to make the current traversal item
     */ 
    public void uItemMakeVisible(Item i) {

        synchronized (Display.LCDUILock) {

            if (i == null) {
                pendingCurrentItem = null;
            }

            /**
             * Display could be made visible using display.setCurrentItem()
             * call. In those cases foregroung will be granted after there
             * there is a screen change event and after uItemMakeVisible()
             * is called. In such cases we need to set pendingCurrentItem and
             * call uItemMakeVisible() again when the FormLF changes its
             * state to SHOWN.
             */
            if (state != SHOWN) {
                pendingCurrentItem = i;
                return;
            }
            lScrollToItem(i);
        }
    }

    /**
     * Scrolls to the passed in Item. 
     * @param item The Item that should be shown on the screen.
     */
    private void lScrollToItem(Item item) {
        if (item == null || item.owner != owner) {
            return;
        }

        int index = -1;
        
        ItemLFImpl itemLF = null;
        if (traverseIndex != -1 && (itemLFs[traverseIndex].item == item)) {
            index = traverseIndex;
        } else {
            for (int i = 0; i < numOfLFs; i++) {
                if (itemLFs[i].item == item) {
                    index = i;
                    break;
                }
            }
        }

        itemLF = itemLFs[index];

        if (index != traverseIndex) {
        
            // Ensure the item is visible
            if (!itemCompletelyVisible(itemLF)) {
                viewable[Y] = itemLF.bounds[Y];
                
                if (viewable[Y] + viewport[HEIGHT] > viewable[HEIGHT]) {
                    viewable[Y] = viewable[HEIGHT] - viewport[HEIGHT];
                }
            }
            
            // We record the present traverseItem because if it
            // is valid, we will have to call traverseOut() on that
            // item when we process the invalidate call.
            if (traverseIndex != -1) {
                lastTraverseItem = itemLFs[traverseIndex];
            }

            // If the item is not interactive, we just leave it
            // visible on the screen, but set traverseIndex to -1
            // so that any interactive item which is visible will
            // be traversed to when the invalidate occurs
            traverseIndex = itemLF.shouldSkipTraverse() ? -1 : index;
            lRequestInvalidate();
        } else {
            // Ensure the item is visible
            if (!itemPartiallyVisible(itemLF)) {
                viewable[Y] = itemLF.bounds[Y];
                
                if (viewable[Y] + viewport[HEIGHT] > viewable[HEIGHT]) {
                    viewable[Y] = viewable[HEIGHT] - viewport[HEIGHT];
                }
            }
        }
    }


    /**
     * Notifies look&feel object of an item deleted in the corresponding
     * Form.
     * 
     * @param itemNum - the index of the item set
     * @param item - the item set in the corresponding Form
     *
     */
    public void lSet(int itemNum, Item item) {
        itemLFs[itemNum] = (ItemLFImpl)item.itemLF;
        itemsModified = true;

        // current optimization: the new item is marked invalid, so when
        // the callInvalidate arrives, we'll know to update it and
        // the minimum set of neighbor Items.

        lRequestInvalidate();
    }

    /**
     * Notifies look&feel object of an item inserted in the corresponding
     * Form.
     * 
     * @param itemNum - the index of the deleted item
     * @param item - the item inserted in the corresponding Form
     *
     */
    public void lInsert(int itemNum, Item item) {
        if (itemLFs.length == numOfLFs) {
            ItemLFImpl newItemLFs[] = 
                new ItemLFImpl[numOfLFs + GROW_SIZE];
            System.arraycopy(itemLFs, 0, newItemLFs, 0, itemNum);
            System.arraycopy(itemLFs, itemNum, newItemLFs, itemNum + 1,
                             numOfLFs - itemNum);
            itemLFs = newItemLFs;
        } else {
            // if we're not appending
            if (itemNum != numOfLFs) {
                System.arraycopy(itemLFs, itemNum, itemLFs, itemNum + 1,
                                 numOfLFs - itemNum);
            }
        }
        
        itemLFs[itemNum] = (ItemLFImpl)((Form)owner).items[itemNum].getLF();
        
        numOfLFs++;
        itemsModified = true;

        if (traverseIndex >= itemNum) {
            traverseIndex++;
        }

        lRequestInvalidate();
    }

    /**
     * Notifies look&feel object of an item deleted in the corresponding
     * Form.
     * 
     * @param itemNum - the index of the deleted item
     * @param deleteditem - the item deleted in the corresponding form
     *
     */
    public void lDelete(int itemNum, Item deleteditem) {

        // start optimization...

        // if the previous item has new line after, or the next item has
        // new line before, and it's not the last item,
        // than we could just mark the next item as actualBoundsInvalid[Y]
        if (itemNum < (numOfLFs-1)) {
            if (((itemNum > 0) && (itemLFs[itemNum-1].equateNLA()) ||
                itemLFs[itemNum+1].equateNLB()) &&
                itemLFs[itemNum+1].isNewLine) {

                if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                    Logging.report(Logging.INFORMATION, 
                                   LogChannels.LC_HIGHUI_FORM_LAYOUT,
                                   "[F]setting actualBoundsInvalid[Y] #" + 
                                   (itemNum + 1));
                    if (itemNum > 0) {
                        Logging.report(Logging.INFORMATION, 
                                       LogChannels.LC_HIGHUI_FORM_LAYOUT,
                                       "[F]  | itemLFs[itemNum-1] = " +
                                       itemLFs[itemNum-1]);
                    }
                    Logging.report(Logging.INFORMATION, 
                                   LogChannels.LC_HIGHUI_FORM_LAYOUT,
                                   "[F]  | itemLFs[itemNum] = " +
                                   itemLFs[itemNum]);
                    if (itemNum < numOfLFs-1) {
                        Logging.report(Logging.INFORMATION, 
                                       LogChannels.LC_HIGHUI_FORM_LAYOUT,
                                       "[F]  | itemLFs[itemNum+1] = "+
                                       itemLFs[itemNum+1]);
                    }
                }
                itemLFs[itemNum+1].actualBoundsInvalid[Y] = true;
            } else {
                itemLFs[itemNum+1].actualBoundsInvalid[X] = true;
            }
        }

        if (traverseIndex == itemNum) {
            lastTraverseItem = itemLFs[traverseIndex];
        }

        if (traverseIndex >= 0 && traverseIndex >= itemNum) {
            traverseIndex--;
        }

        numOfLFs--;
        itemsModified = true;

        if (itemNum < numOfLFs) {
            System.arraycopy(itemLFs, itemNum + 1, itemLFs, itemNum,
                             numOfLFs - itemNum);
        }
        
        // Delete reference to the last item view 
        // that was left after array copy
        itemLFs[numOfLFs] = null;
        

        if (pendingCurrentItem == deleteditem) {
            pendingCurrentItem = null;
        }

        lRequestInvalidate();
    }

    /**
     * Notifies look&feel object of an item deleted in the corresponding
     * Form.
     * 
     */
    public void lDeleteAll() {
        if (traverseIndex != -1) {
            lastTraverseItem = itemLFs[traverseIndex];
        }
        // Dereference all ItemLFImpls so they can be GC'ed
        while (numOfLFs > 0) {
            itemLFs[--numOfLFs] = null;
        }
        traverseIndex = -1;
        itemsModified = true;
        pendingCurrentItem = null;
        lRequestInvalidate();
    }

    /**
     * This method is responsible for:
     * (1) Re-validate the contents of this Form, possibly due to an
     * individual item
     * (2) setup the viewable/scroll position
     * (3) repaint the currently visible Items
     */
    public void uCallInvalidate() {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, 
                           LogChannels.LC_HIGHUI_FORM_LAYOUT,
                           "[F] *-* FormLFImpl: dsInvalidate ");
        }


        synchronized (Display.LCDUILock) {
            pendingInvalidate = false;
        }
        
        // It could be that setCurrentItem() was called and we
        // have done an 'artificial' traversal. In this case, we
        // manually call traverseOut() on the last traversed item
        // if there is one.
        if (lastTraverseItem != null) {
            try {
                lastTraverseItem.uCallTraverseOut();                
            } catch (Throwable t) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_HIGHUI,
                                  "Throwable while traversing out");
                }
            }
            lastTraverseItem = null;
            updateCommandSet();
        }
        
        synchronized (Display.LCDUILock) {
            // Do nothing if paint is suspended in current Display
            if (!lIsShown()) {
                return;
            }

            // Do not reset the Form from the top since this is an update
            resetToTop = false;
        }

        // IMPL NOTES: Remove this line after UDPATE_LAYOUT is fixed
        firstShown = true;
        // Update contents
        uShowContents(false);

        // Request a repaint
        uRequestPaint();
    }

    /**
     * Paint the contents of this Form
     *
     * @param g the Graphics object to paint on
     * @param target the target Object of this repaint
     */
    public void uCallPaint(Graphics g, Object target) {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION,
                           LogChannels.LC_HIGHUI_FORM_LAYOUT,
                           "[F] *-* FormLFImpl: dsPaint " +
                           target);
        }

        super.uCallPaint(g, target);

        // IMPL_NOTE: Thread safety
        // Cannot call paintItem inside this block
        synchronized (Display.LCDUILock) {
            // 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 (numOfLFs == 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] <= 0) {
                    return;
                }

                if (!scrollInitialized) {
                    setVerticalScroll();
                    scrollInitialized = true;
                }

                if (target instanceof Item) {

                    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                        Logging.report(Logging.INFORMATION,
                                       LogChannels.LC_HIGHUI_FORM_LAYOUT,
                                       "[F] FormLFImpl: dsPaint ONE Item " +
                                       target);
                    }

                    if (((Item)target).owner == this.owner) {
                        ((ItemLFImpl)((Item)target).getLF())
                            .paintItem(g, clip,
                                       0 - viewable[X],
                                       0 - viewable[Y]);
                    }
                } else {

                    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                        Logging.report(Logging.INFORMATION, 
                                       LogChannels.LC_HIGHUI_FORM_LAYOUT,
                                       "[F] FormLFImpl: dsPt. ALL Items ");
                    }

                    for (int i = 0; i < numOfLFs; i++) {
                        itemLFs[i].paintItem(g, clip,
                                             0 - viewable[X],
                                             0 - viewable[Y]);
                    }
                }
            } catch (Throwable t) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_HIGHUI,
                                  "Throwable while paint item");
                }
            }
        } // synchronized
    }

    /**
     * Called by Display to notify current FormLF of a change in its peer 
     * state. For Java widgets, there is no peer for either Form or Items.
     * This implementation does nothing.
     *
     * @param modelVersion the version of the peer's data model
     * @param itemPeerId the id of the ItemLF's peer whose state has changed
     * @param hint some value that is interpreted only between the peers
     */
    public void uCallPeerStateChanged(int modelVersion,
                                      int itemPeerId, int hint) {
        // No peer. Not expected to be called
    }

    /**
     * notify this Form it is being shown on the given Display
     */
    public void uCallShow() {
        super.uCallShow();
        uShowContents(true);

        synchronized (Display.LCDUILock) {
           scrollInitialized = false;
           
           if (pendingCurrentItem != null) {

              lScrollToItem(pendingCurrentItem);
              pendingCurrentItem = null;
           }
        }
    }
    
    /**
     * notify this Form it is being hidden on the given Display
     */
    public void uCallHide() {
        int oldState;

        synchronized (Display.LCDUILock) {
            oldState = state;
            pendingCurrentItem = null;
        } 

        super.uCallHide();

        if (oldState == SHOWN) {
            uCallItemHide();
        }
    }

    
    /**
     * notify this Form it is being frozen on the given Display
     */
    public void uCallFreeze() {
        int oldState = state;

        super.uCallFreeze();

        if (oldState == SHOWN) {
            uCallItemHide();
        }
    }  
    
    /**
     * Updates command set if this Displayable is visible
     *
     * SYNC NOTE: Caller should hold LCDUILock.
     */
    public void updateCommandSet() {
        if (state == SHOWN) {
            currentDisplay.updateCommandSet();
        }
    }

    /**
     * Returns the width of the area available to the application.
     * @return width of the area available to the application
     */
    public int lGetWidth() {
        int w = super.lGetWidth();
        return (w > 2 * ScreenSkin.PAD_FORM_ITEMS ? 
                w - 2 * ScreenSkin.PAD_FORM_ITEMS : 0);
    }
    
    /**
     * Returns the height of the area available to the application.
     * @return height of the area available to the application
     */
    public int lGetHeight() {
        int h = super.lGetHeight();
        return (h > 2 * ScreenSkin.PAD_FORM_ITEMS ? 
                h - 2 * ScreenSkin.PAD_FORM_ITEMS : 0);
    }

    // *****************************************************
    //  Package private methods
    // *****************************************************
    
    /**
     * Handle a key press
     *
     * @param keyCode the key code of the key which was pressed
     */
    void uCallKeyPressed(int keyCode) {
        if (keyCode == Constants.KEYCODE_UP
            || keyCode == Constants.KEYCODE_DOWN
            || keyCode == Constants.KEYCODE_LEFT
            || keyCode == Constants.KEYCODE_RIGHT) 
        {
            int dir = KeyConverter.getGameAction(keyCode);
            
            // If 2D traversal is not enabled, we "direction-bend"
            // and traverse in a typewriter-like fashion, line-by-line
            // from left to right
            if (!Constants.TRAVERSAL2D) {
                if (dir == Canvas.DOWN) {
                    dir = Canvas.RIGHT;
                } else if (dir == Canvas.UP) {
                    dir = Canvas.LEFT;
                }
            }
            uTraverse(dir);
            return;
        } else {
            ItemLFImpl v = null;
            synchronized (Display.LCDUILock) {
                if (numOfLFs == 0 || traverseIndex < 0) {
                    return;
                }
                
                v = itemLFs[traverseIndex];
                
            } // synchronized
    
            // SYNC NOTE: uCallKeyPressed may result in a call to the
            // application, so we make sure we do this outside of the
            // LCDUILock
            if (v != null) {
                // pass the keypress onto the current item
                v.uCallKeyPressed(keyCode);
            }
        }
    }

    /**
     * Handle a key release event
     *
     * @param keyCode the key which was released
     */
    void uCallKeyReleased(int keyCode) {
        if (keyCode == Constants.KEYCODE_UP
            || keyCode == Constants.KEYCODE_DOWN
            || keyCode == Constants.KEYCODE_LEFT
            || keyCode == Constants.KEYCODE_RIGHT) 
        {
            return;
        } else {
            ItemLFImpl v = null;
            synchronized (Display.LCDUILock) {
                if (numOfLFs == 0 || traverseIndex < 0) {
                    return;
                }
                
                v = itemLFs[traverseIndex];
                
            } // synchronized
    
            // SYNC NOTE: uCallKeyPressed may result in a call to the
            // application, so we make sure we do this outside of the
            // LCDUILock
            if (v != null) {
                // pass the keypress onto the current item
                v.uCallKeyReleased(keyCode);
            }
        }
    }

    /**
     * Handle a key repeat
     *
     * @param keyCode the key code of the key which was repeated
     */
    void uCallKeyRepeated(int keyCode) {
        if (keyCode == Constants.KEYCODE_UP
            || keyCode == Constants.KEYCODE_DOWN
            || keyCode == Constants.KEYCODE_LEFT
            || keyCode == Constants.KEYCODE_RIGHT) 
        {
            int dir = KeyConverter.getGameAction(keyCode);
            
            // If 2D traversal is not enabled, we "direction-bend"
            // and traverse in a typewriter-like fashion, line-by-line
            // from left to right
            if (!Constants.TRAVERSAL2D) {
                if (dir == Canvas.DOWN) {
                    dir = Canvas.RIGHT;
                } else if (dir == Canvas.UP) {
                    dir = Canvas.LEFT;
                }
            }
            uTraverse(dir);
            return;
        } else {
            ItemLFImpl v = null;
            synchronized (Display.LCDUILock) {
                if (numOfLFs == 0 || traverseIndex < 0) {
                    return;
                }
                
                v = itemLFs[traverseIndex];
                
            } // synchronized
    
            // SYNC NOTE: uCallKeyPressed may result in a call to the
            // application, so we make sure we do this outside of the
            // LCDUILock
            if (v != null) {
                // pass the keypress onto the current item
                v.uCallKeyRepeated(keyCode);
            }
        }
    }

    
    /**
     * Return the item containing the pointer {x, y}
     * @param x - x demension
     * @param y - y demension
     * @return the item containing pointer,
     * if such item is not found returns null
     */
    private ItemLFImpl findItemByPointer(int x, int y) {
        ItemLFImpl item = null;
        for (int i = 0; i < numOfLFs; i++) {
            if (!itemLFs[i].shouldSkipTraverse()) {
                if (itemLFs[i].itemContainsPointer(x + viewable[X], y + viewable[Y])) {
                    item = itemLFs[i];
                    break;
                }
            }
        }
        return item;
    }

    
    /**
     * Handle a pointer pressed event
     *
     * @param x The x coordinate of the press
     * @param y The y coordinate of the press
     */
    void uCallPointerPressed(int x, int y) {
        ItemLFImpl v = null;
        synchronized (Display.LCDUILock) {
            if (numOfLFs == 0) {
                return;
            }
            
            v = findItemByPointer(x, y);
            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
        if (v != null) {
            x = (x + viewable[X]) - v.getInnerBounds(X);
            y = (y + viewable[Y]) - v.getInnerBounds(Y);
            v.uCallPointerPressed(x, y);

            synchronized (Display.LCDUILock) {
                lScrollToItem(v.item);
            }
        }
    }

    /**
     * Handle a pointer released event
     *
     * @param x The x coordinate of the release
     * @param y The y coordinate of the release
     */
    void uCallPointerReleased(int x, int y) {
        ItemLFImpl v = null;

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

            v = findItemByPointer(x, 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
        if (v != null) {
            x = (x + viewable[X]) - v.getInnerBounds(X);
            y = (y + viewable[Y]) - v.getInnerBounds(Y);
            v.uCallPointerReleased(x, y);
        }
    }

    /**
     * Handle a pointer dragged event
     *
     * @param x The x coordinate of the drag
     * @param y The y coordinate of the drag
     */
    void uCallPointerDragged(int x, int y) {
        ItemLFImpl v = null;

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

            v = itemLFs[traverseIndex];

            x = (x + viewable[X]) - v.getInnerBounds(X);
            y = (y + viewable[Y]) - v.getInnerBounds(Y);

        }

        // SYNC NOTE: this call may result in a call to the
        // application, so we make sure we do this outside of the
        // LCDUILock
        v.uCallPointerDragged(x, y);
    }

    /**
     * Called to commit any pending user interaction for the current item.
     * Override the no-op in Displayable.
     */
    public void lCommitPendingInteraction() {
        if (traverseIndex >= 0) {
            itemLFs[traverseIndex].lCommitPendingInteraction();
        }
    }

    
    /**
     * 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
     */
    public Item lGetCurrentItem() {
        // SYNC NOTE: getCurrentItem is always called from within
        // a hold on LCDUILock
        return traverseIndex < 0 ? null : itemLFs[traverseIndex].item;
    }


    /**
     * Ensure CustomItems have their requested size cached.
     */
    void uEnsureRequestedSizes() {
        int i, count = 0;

        synchronized (Display.LCDUILock) {

            // Make a temporary copy of ItemLFs we need to collect sizes from
            ensureDispatchItemArray(numOfLFs);

            // Make sure each Item has native resource
            // and remember all the CustomItemLFImpls
            for (i = 0; i < numOfLFs; i++) {
                if (itemLFs[i] instanceof CustomItemLFImpl) {
                    // Remember this in temporary array
                    dispatchItemLFs[count++] = itemLFs[i];
                }
            }
        } // synchronized

        // Collect min and preferred sizes from CustomItems
        // SYNC NOTE: This may call into app code like
        // CustomItem.getPrefContentWidth(). So do it outside LCDUILock
        for (i = 0; i < count; i++) {
            ((CustomItemLFImpl)dispatchItemLFs[i]).uCallSizeRefresh();
        }

        // Dereference ItemLFImpl objects in dispatchItemLFs
        resetDispatchItemArray(true);
    }

    /**
     * Show all items and give focus to current item.
     * SYNC NOTE: caller must NOT hold LCDUILock since this function may
     * call into app code like getPrefContentWidth(), sizeChanged or paint()
     * of CustomItem. 
     * @param initialTraverse the flag to indicate this is the initial 
     *               traversal focus setup when this Form is being shown
     */
    void uShowContents(boolean initialTraverse) {

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, 
                           LogChannels.LC_HIGHUI_FORM_LAYOUT,
                           "\nFormLFImpl: uShowContents()");
        }        

        synchronized (Display.LCDUILock) {
            if (firstShown) {
                for (int i = 0; i < numOfLFs; i++) {
                    itemLFs[i].cachedWidth = ItemLFImpl.INVALID_SIZE;
                }
            }
        }

        // Collect CustomItem sizes outside LCDUILock
        uEnsureRequestedSizes();

        ItemLFImpl[] itemsCopy = null;
        int traverseIndexCopy = -1;
        
        synchronized (Display.LCDUILock) {
            if (firstShown) {
                super.layout(); // moved from LayoutManager
                LayoutManager.instance().lLayout(LayoutManager.FULL_LAYOUT,
                                                 itemLFs, numOfLFs,
                                                 viewport[WIDTH],
                                                 viewport[HEIGHT],
                                                 viewable);
                firstShown = false;
            } else {            
                LayoutManager.instance().lLayout(LayoutManager.UPDATE_LAYOUT,
                                                 itemLFs, numOfLFs,
                                                 viewport[WIDTH],
                                                 viewport[HEIGHT],
                                                 viewable);
            }
            if (resetToTop) {
                traverseIndex = -1;
                viewable[Y] = 0;
                viewable[X] = 0;
            } else {
                // correct scroll position if any
                if (viewable[HEIGHT] <= viewport[HEIGHT] ||
                    viewable[Y] < 0) {
                    // if viewable height is less than viewport
                    // height just reset viewable y
                    // if viewable y is less than 0 set it to 0 
                    viewable[Y] = 0;
                } else if (viewable[Y] > (viewable[HEIGHT] - viewport[HEIGHT])) {
                    // if viewable y exceeds the max value set it to the max
                    // height just reset viewable y
                    viewable[Y] = viewable[HEIGHT] - viewport[HEIGHT];
                }
            }
            
            
            itemsCopy = new ItemLFImpl[numOfLFs];
            System.arraycopy(itemLFs, 0, itemsCopy, 0, numOfLFs);
            traverseIndexCopy = traverseIndex;
            itemsModified = false;

        } // synchronized
        
        // We issue a default traverse to setup focus
        //
        // SYNC NOTE: Since this may call into CustomItem.traverse/traverseOut,
        // call it outside LCDUILock

                
        uInitItemsInViewport(CustomItem.NONE, itemsCopy, traverseIndexCopy);
        if (initialTraverse) {
            updateCommandSet();
        }

        for (int index = 0; index < numOfLFs; index++) {
            if (itemLFs[index].sizeChanged) {
                itemLFs[index].uCallSizeChanged(itemLFs[index].getInnerBounds(WIDTH),
                        itemLFs[index].getInnerBounds(HEIGHT));
                itemLFs[index].sizeChanged = false;
            }
        }
    }

    /**
     * Initialize the current page of items, perform a traverse if possible.
     * This method is always called when a page is initially "shown".
     * This occurs when the form gains visibility for the very first
     * time as well as after every page up/page down occurs.
     *
     * This method searches for the most appropriate item on the form
     * to receive the interaction focus.
     *
     * @param dir the direction of travel. Can be NONE when a page is
     *        first shown or as the result of an invalidate.
     * @param itemsCopy a copy of the set of ItemLFImpls in this form.
     * @param traverseIndexCopy a copy of taverseIndex to work with itesCopy[]
     */
    void uInitItemsInViewport(
        int dir, ItemLFImpl[] itemsCopy, int traverseIndexCopy) {
        // Create a copy of the current index for comparisons, below.
        if (itemsCopy.length == 0) {
            setupScroll();
            return;
        }
        
        uHideShowItems(itemsCopy); 
        
        // The result of an invalidate() call
        if (traverseIndexCopy != -1 && dir == CustomItem.NONE) {
            itemTraverse = 
                    uCallItemTraverse(itemsCopy[traverseIndexCopy], dir);
            
            uRequestPaint(); // request to paint contents area
            
            synchronized (Display.LCDUILock) {
                lScrollToItem(itemsCopy[traverseIndexCopy].item);
            }
            setupScroll();
            return;
        }
        
        // Special case: It could be that no item in the current view
        // is interactive, and thus the traverseIndexCopy is -1. If we
        // are scrolling upwards, we artificially set it to be the last
        // item on the form (+1) so that the getNextInteractiveItem()
        // routine will subsequently reduce it by 1 and start searching
        // for an interactive item from the bottom of the form upwards.
        if (dir == Canvas.UP && traverseIndexCopy == -1) {
            traverseIndexCopy = itemsCopy.length;
        }

        if (traverseIndexCopy > -1 && traverseIndexCopy < itemsCopy.length) {
            // If there is a traversable item, we go ahead and traverse
            // to it. We do *not* scroll at all under these circumstances
            // because we have just performed a fresh page view (or scroll)
            itemTraverse = uCallItemTraverse(itemsCopy[traverseIndexCopy], dir);
        }           
        if (!itemTraverse) {
            // If paging "down", we find the interactive item by moving
            // left to right - this ensures we move line by line searching
            // for an interactive item. When paging "up", we search from
            // right to left.
            int nextIndex = (dir == Canvas.DOWN || dir == CustomItem.NONE)
                ? getNextInteractiveItem(
                                         itemsCopy, Canvas.RIGHT, traverseIndexCopy)
                : getNextInteractiveItem(
                                         itemsCopy, Canvas.LEFT, traverseIndexCopy);
            
            if (traverseIndexCopy > -1 && traverseIndexCopy < itemsCopy.length) {
                if (nextIndex != -1 && nextIndex != traverseIndexCopy) {
                    // It could be we need to traverse out of a current
                    // item before paging
                    itemsCopy[traverseIndexCopy].uCallTraverseOut();
                    synchronized (Display.LCDUILock) {
                        traverseIndex = -1;  // reset real index
                        traverseIndexCopy = traverseIndex;
                    }
                }
            } 
            /*
             * NOTE: between these two sync sections itemLFs[] & traverseIndex
             * can change again ...
             */
            synchronized (Display.LCDUILock) {
                if (itemsModified) { 
                    // SYNCHRONIZE itemLFs & itemsCopy, update traverseIndex ...
                    itemsCopy = lRefreshItems(
                                              itemsCopy, traverseIndexCopy, nextIndex);
                } else if ((nextIndex > -1) && (nextIndex < numOfLFs)) {
                    traverseIndex = nextIndex;
                }
                traverseIndexCopy = traverseIndex;
            }
            
            if (traverseIndexCopy == -1 || traverseIndexCopy == itemsCopy.length) {
                // If there is no traversable item on the current page,
                // we simply return, and on the next 'traverse' we will
                // perform a page scroll and repeat this method
            } else {
                // If there is a traversable item, we go ahead and traverse
                // to it. We do *not* scroll at all under these circumstances
                // because we have just performed a fresh page view (or scroll)
                itemTraverse = 
                    uCallItemTraverse(itemsCopy[traverseIndexCopy], dir);
            }
        }
         
        uRequestPaint(); // request to paint contents area                
        setupScroll();
    }
    
    /**
     * Synchronizes itemLFs[] array with itemsCopy[] array, 
     * as well as traverseIndex with traverseIndexCopy & nextIndex. 
     *
     * Since most of work with copies occurs outside of LCDUILock
     * (this is, BTW, the reason, why copies are used instead of original
     * fields), there is a risk, that
     * itemLFs[] content can be changed (ex. insert/delete/replace Item): 
     * traverseIndexCopy can point to a different object 
     * (including non-interactive), 
     * or outside of changed itemLFs array (throws exception), 
     * or we can refer to a deleted item (deleted in itemLFs, 
     * but still exists in a copy).
     *
     * This method tries to find item, referred by nextIndex in itemsCopy[], 
     * in itelLFs[], and if found, sets traverseIndex to foundItem, 
     * else sets traverse index to -1.
     *
     * This method indended to be called in LCDUILock-protected code, 
     * from uInitItemsInViewport(...) & uTraverse(...).
     *
     * @param itemsCopy a copy of the set of ItemLFImpls in this form.
     * @param traverseIndexCopy a copy of traverseIndex to work with itemsCopy
     * @param nextIndex suggested new value of traverseIndex, 
     *        the item from itemsCopy[] to be found in changed itemLFs[]
     *
     * @return updated itemsCopy[] array, synchronized with itemLFs[]
     */
    private ItemLFImpl[] lRefreshItems(
            ItemLFImpl[] itemsCopy, 
            int traverseIndexCopy, 
            int nextIndex) {
        
        final int nextIndexInLFs = nextIndex + 
                (traverseIndex - traverseIndexCopy);
        traverseIndex = (
            (traverseIndex > -1) && 
            /* (traverseIndex < numOfLFs) && */
            (traverseIndexCopy > -1) && 
            /* (traverseIndexCopy < itemsCopy.length) && */
            (nextIndex > -1) && 
            /* (nextIndex < itemsCopy.length) && */
            (nextIndexInLFs > -1) && 
            (nextIndexInLFs < numOfLFs) &&
            !itemLFs[nextIndexInLFs].shouldSkipTraverse())
            /* (itemsCopy[nextIndex] == itemLFs[nextIndexInLFs])) */
            /*
             * Assume that:
             * 1). traverseIndex has always valid value: 
             * i.e. -1 or within [0..numOfLFs[ range of itemLFs[] array.
             * 2). traverseIndexCopy & nextIndex have always valid values: 
             * i.e. -1 or within [0..itemsCopy.length[ range  
             * of itemsCopy[] array.
             * As the result we need to check them only for "-1" value.
             * Computed "nextIndexInLFs" needs to be checked for 
             * being in bounds of itemLFs[].
             *
             * Need check that last condition in the above  "IF" is needed,
             * however it ensures, that the  next current item 
             * will be exactly the same item that has been found 
             * by "getNext...". 
             * Without thast statement we have a risk to point to 
             * a completely different item: still valid & 
             * in the range, but probably NON-interactive :-( 
             * To avoid this, "shouldSkipTraverse()" could be used insead ...
             */
            ? nextIndexInLFs
            : -1;

        // refresh itemsCopy array ...
        itemsCopy = new ItemLFImpl[numOfLFs];
        System.arraycopy(itemLFs, 0, itemsCopy, 0, numOfLFs);
        itemsModified = false;
        return itemsCopy;
    }

    /**
     * Loop through the set of items, making sure they have been
     * properly notified of their visibility via their show/hideNotify
     * methods.
     *
     * @param items the set of items to notify
     */
    void uHideShowItems(ItemLFImpl[] items) {
        // If an item is at least partially visible, it must have
        // its showNotify() called (if not already done). If an
        // item is completely offscreen, it must have its hideNotify()
        // called (if not already done).
        for (int i = 0; i < items.length; i++) {
            if (items[i].visible != itemPartiallyVisible(items[i])) {
                try {
                    if (items[i].visible) {
                        items[i].uCallHideNotify();
                    } else {
                        items[i].uCallShowNotify();
                    }
                } catch (Throwable t) {
                    if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                        Logging.report(Logging.WARNING, LogChannels.LC_HIGHUI,
                                   "Throwable while hideNotify or showNotify");
                    }
                }
            }
        }
    }

    /**
     * Determine if the given item is at least partially visible
     * in the current viewport.
     *
     * @param item the item to determine visibility
     * @return true if at least part of the item is visible
     */
    boolean itemPartiallyVisible(ItemLFImpl item) {
        // If the Form is hidden, all the items are
        // hidden and we just return false
        if (super.state == HIDDEN) {
            return false;
        }
        return !(item.bounds[Y] > viewable[Y] + viewport[HEIGHT] ||
                 item.bounds[Y] + item.bounds[HEIGHT] < viewable[Y]);
    }
    
    
    /**
     * Determine if the given item is at completely visible
     * in the current viewport.
     *
     * @param item the item to determine visibility
     * @return true if at the item is entirely visible
     */
    boolean itemCompletelyVisible(ItemLFImpl item) {
        // If the Form is being hidden, all the items are
        // hidden and we just return false
        if (super.state == HIDDEN) {
            return false;
        }
        
        // If the Item's top and bottom are within the viewport,
        // return true
        return (item.bounds[Y] >= viewable[Y] && 
                item.bounds[Y] + item.bounds[HEIGHT] <= (viewable[Y] + viewport[HEIGHT]));
    }
    
    /**
     * This method will return the index of the next interactive
     * item which is wholly visible on the screen given the traversal
     * direction, or -1 if no visible items in that traversal direction
     * are interactive (or completely visible).
     *
     * @param items the set of items to search
     * @param dir the direction of traversal
     * @param index the "anchor" of the index to start from
     * @return the index of the next interactive item, or -1 if one is
     *         not completely visible or available in the given direction
     */
    int getNextInteractiveItem(ItemLFImpl[] items, int dir, int index) {
              
        try {
            
            while (true) {
                switch (dir) {
                    case Canvas.UP:
                    case Canvas.LEFT:
                        index -= 1;
                        break;
                    case Canvas.DOWN:
                    case Canvas.RIGHT:
                        index += 1;
                        break;
                    case CustomItem.NONE:
                        // no - op
                        break;
                    default:
                        // for safety/completeness.
                        Logging.report(Logging.ERROR, 
                                       LogChannels.LC_HIGHUI_FORM_LAYOUT,
                                       "FormLFImpl: dir=" + dir);
                        return index;
                }

                // If we've exhausted the set, stop looking                
                if (index < 0 || index >= items.length) {
                    break;
                }
                
                // If we've found a non-interactive item, continue
                if (items[index].shouldSkipTraverse()) {
                    continue;
                }
                
                if (itemPartiallyVisible(items[index])) {
                    break;
                }

            } // while                
        } catch (Throwable t) {
            if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                Logging.report(Logging.WARNING, LogChannels.LC_HIGHUI,
                           "Throwable while finding next item for traversal");
            }
            return -1;
        }
        
        // This means there was no interactive item in the currently
        // visible viewport
        if (index < 0 || index >= items.length) {
            return -1;
        }       
        
        return index;
    }
    
    /**
     * Find the nearest neighbor to the given Item index
     * moving upward
     *
     * @param items the set of items to search
     * @param index the index of the "anchor" to begin the search
     * @return the index of the nearest neighbor up
     */
    int findNearestNeighborUp(ItemLFImpl[] items, int index) {
        // SYNC NOTE: see traverse()
        if (index == 0) {
            return -1;
        }

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

        int x1, y1, x2, y2;
        int leastY = viewable[HEIGHT];
        
        while (true) {
            for (int i = index - 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];

                if (y1 < leastY) {
                    leastY = y1;
                }
                
                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;
                }
            }
            
            if (b1 < leastY) {
                break;
            }
        }

        return -1;
    }

    /**
     * Find the nearest neighbor to the given Item index
     * moving downward
     *
     * @param items the set of items to search
     * @param index the index of the "anchor" to begin the search
     * @return the index of the nearest neighbor down
     */
    int findNearestNeighborDown(ItemLFImpl[] items, int index) {
        // SYNC NOTE: see traverse()
        
        if (index == -1) {
            return 0;
        }

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

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

        while (true) {
            for (int i = index + 1; i < items.length; 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;
                }
            }

            if (b2 > greatestY) {
                break;
            }
        }

        return -1;
    }


    /**
     * Calculate the rectangle representing the region of the item that is
     * currently visible. This region might have zero area if no part of the
     * item is visible, for example, if it is scrolled offscreen.
     * @param item item
     * @param visRect  It must be an int[4] array. The information in this array is
     * a rectangle of the form [x,y,w,h]  where (x,y) is the location of the
     * upper-left corner of the rectangle relative to the item's origin, and
     * (w,h) are the width and height of the rectangle.
     */
    private void setVisRect(ItemLFImpl item, int[] visRect) {
        synchronized (Display.LCDUILock) {
            // Initialize the in-out rect for traversal
            visRect[X] = 0;
            visRect[WIDTH] = viewport[WIDTH];
 
            // take the coordinates from the overall
            // coordinate space 
 
            int itemY1 = item.bounds[Y];
            int itemY2 = item.bounds[Y] + item.bounds[HEIGHT];
 
            // vpY1 the y coordinate of the top left visible pixel
            // current scroll position
            int vpY1 = viewable[Y];
            // vpY2 the y coordinate of bottom left pixel
            int vpY2 = vpY1 + viewport[HEIGHT];
                         
            // return only the visible region of item
 
            // item completely visible in viewport
            visRect[Y] = 0;
            visRect[HEIGHT] = item.bounds[HEIGHT];
                         
            if ((itemY1 >= vpY2) || (itemY2 <= vpY1)) { 
                // no part of the item is visible
                // so this region has zero area
                visRect[WIDTH] = 0;
                visRect[HEIGHT] = 0;
            } else {
                if (itemY1 < vpY1) {
                    // upper overlap
                    visRect[Y] =  vpY1 - itemY1;
                    visRect[HEIGHT] -= (vpY1 - itemY1);
                }
                if (itemY2 > vpY2) {
                    // lower overlap
                    visRect[HEIGHT] -= (itemY2 - vpY2);
                }
            } 
        }
    }
    
    /**
     * Perform an internal traversal on the given item in
     * the given direction. The only assertion here is that
     * the item provided must be interactive (or otherwise
     * be a CustomItem). When this method returns, visRect[]
     * will hold the bounding box of the item's internal
     * traversal (in the Form's coordinate space).
     *
     * @param item the item to traverse within
     * @param dir the direction of traversal
     * @return true if this item performed an internal traversal
     *         in the given direction.
     */
    boolean uCallItemTraverse(ItemLFImpl item, int dir) {
        boolean ret = false;
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, 
                           LogChannels.LC_HIGHUI_FORM_LAYOUT,
                           "[F] uCallItemTraverse: dir=" + dir +
                           " traverseIndex=" + traverseIndex);
        }
        
        // Whether the item performs an internal traversal or not,
        // it has the current input focus
        item.hasFocus = true;
        setVisRect(item, visRect);
        // Call traverse() outside LCDUILock
        if (item.uCallTraverse(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] += item.bounds[X];
            visRect[Y] += item.bounds[Y];

            ret = true;
        }
        return ret;
    }
    
    /**
     * Perform a traversal. This method handles traversal within a
     * "page" after the initial page has been shown via the 
     * uInitItemsInViewport() routine. At the point this method is 
     * called, the following conditions must be true:
     *
     * 1.) There are no interactive items at all on the current page
     * or
     * 2.) There is at least one interactive item on the current page
     *     and the traverseIndex is currently set to that item. In this
     *     case, itemTraverse represents the return value of that item's
     *     initial traverse() call.
     *
     * Based on these conditions, this method will either:
     *
     * 1.) Continue the internal traversal on the current item (scrolling
     *     as necessary to display the item's internal traversal location)
     * or
     * 2.) Perform a traversal to the next interactive item on the page
     * or
     * 3.) Perform a page flip (uScrollViewport()) and call the 
     *     uInitItemsInViewport() routine to select an appropriate 
     *     traversal item
     *
     * SYNC NOTE: Maybe call into CustomItem.traverse().
     * So caller must not hold LCDUILock.
     *
     * @param dir the direction of traversal
     */
    void uTraverse(int dir) {

        ItemLFImpl[] itemsCopy;
        int traverseIndexCopy;

        synchronized (Display.LCDUILock) {
            itemsCopy = new ItemLFImpl[numOfLFs];
            traverseIndexCopy = traverseIndex;
            System.arraycopy(itemLFs, 0, itemsCopy, 0, numOfLFs);
            itemsModified = false;
        }
        
        // itemTraverse indicates the return value of the
        // last call to the current item's traverse method.
        // 'true' indicates it is doing internal traversal,
        // 'false' indicates we may traverse out of that item
        // if we have something else to traverse to or scrolling
        // that needs to be done
        if (itemTraverse) {
            
            if (traverseIndexCopy == -1) {
                itemTraverse = false;
                return;
            }
                        
            itemTraverse = 
                    uCallItemTraverse(itemsCopy[traverseIndexCopy], dir);
                
            if (itemTraverse) {
                // We may have to scroll to accommodate the new
                // traversal location 
                if (scrollForBounds(dir, visRect)) {
                    uHideShowItems(itemsCopy);
                    uRequestPaint(); // request to paint contents area
                    setupScroll();
                }
                return;
            }
        } 

        // We are done with the traversal of the current item, so
        // we look to see if another interactive item is available on
        // current page
        int nextIndex = 
                getNextInteractiveItem(itemsCopy, dir, traverseIndexCopy);
        
        if (nextIndex != -1) {
            // NOTE: In traverse(), if there is a "next" interactive
            // item, there must have been a "first" interactive item
            // (which was set initially in uInitItemsInViewport())
            // so traverseIndex should always be valid
            
            // We need to traverse out of the previous item, now that
            // we've found a new item to traverse to

            // NOTE WELL: traverseIndex (and thus traverseIndexCopy) may well 
            // be invalid if there is no currently focused item, the app adds 
            // a focusable item, and then the user traverses before the 
            // resulting invalidation can be processed. Thus, this value must 
            // be guarded anyway. See CR#6254765.

            if (traverseIndexCopy != -1) {
                itemsCopy[traverseIndexCopy].uCallTraverseOut();
                itemsCopy[traverseIndexCopy].uRequestPaint();
            }
            
            /*
             * NOTE: Although we update traverseIndex in a synchronized block
             * and call "lRefreshItems()" to update itemsCopy[] & 
             * traverseIndexCopy, 
             * original itemLFs[] & traverseIndex can change after sync block - 
             * so we still have a risk of referring to a non-existent item...
             */
            synchronized (Display.LCDUILock) {
                if (itemsModified) {
                    // SYNCHRONIZE itemLFs & itemsCopy ...
                    itemsCopy = lRefreshItems(
                            itemsCopy, traverseIndexCopy, nextIndex);
                } else {
                    // Update our traverse index to the new item
                    traverseIndex = nextIndex;
                }
                traverseIndexCopy = traverseIndex;
            }
            if (traverseIndexCopy != -1) {
                // We then need to traverse to the next item
                itemTraverse = 
                        uCallItemTraverse(itemsCopy[traverseIndexCopy], dir);

                if (scrollForBounds(dir, visRect)) {
                    uHideShowItems(itemsCopy);
                    uRequestPaint(); // request to paint contents area
                } else {
                    itemsCopy[traverseIndexCopy].uRequestPaint();
                }
            }
            
            // There is a special case when traversing to the very last
            // item on a Form
            if (traverseIndexCopy == (itemsCopy.length - 1) && 
                !itemCompletelyVisible(itemsCopy[traverseIndexCopy])) 
            {
                // Since its the last item, we may need to
                // perform a partial scroll to fit it.                
                if (viewable[Y] + viewport[HEIGHT] !=
                    itemsCopy[traverseIndexCopy].bounds[Y] + 
                    itemsCopy[traverseIndexCopy].bounds[HEIGHT])
                {
                    viewable[Y] = viewable[HEIGHT] - 
                        viewport[HEIGHT];
                        
                    // We make sure we don't go past the top of the
                    // item, as we must have been going down to reach
                    // the last item
                    if (viewable[Y] > itemsCopy[traverseIndexCopy].bounds[Y]) {
                        viewable[Y] = itemsCopy[traverseIndexCopy].bounds[Y];
                    }
                    uHideShowItems(itemsCopy);
                    uRequestPaint();
                }
            }
            
            // Likewise, there is a special case when traversing up to
            // the very first item on a Form
            if (traverseIndexCopy == 0) {
                // Since its the first item, we may need to
                // perform a partial scroll to fit it.
                if (viewable[Y] != itemsCopy[traverseIndexCopy].bounds[Y]) {
                    viewable[Y] = itemsCopy[traverseIndexCopy].bounds[Y];
                    
                    // We make sure we don't go past the bottom of the
                    // item, as we must have been going up to get to
                    // the first item
                    if (itemsCopy[traverseIndexCopy].bounds[HEIGHT] > 
                            viewport[HEIGHT])
                    {
                        viewable[Y] = 
                            itemsCopy[traverseIndexCopy].bounds[HEIGHT] -
                            viewport[HEIGHT];
                    }
                    uHideShowItems(itemsCopy);
                    uRequestPaint();
                }
            }

            setupScroll();
            updateCommandSet();
        } else {                      
            
            // There is no more interactive items wholly visible on
            // the current page. We may need to scroll to the next page,
            // if we do, then traverse out of the current item and 
            // scroll the page
            
            if ((dir == Canvas.LEFT || dir == Canvas.UP) && viewable[Y] > 0) {
                // Special case. We're at the top-most interactive item, but
                // its internal traversal doesn't allow the very top to be
                // seen, we just scroll the view to show it
                if (traverseIndexCopy != -1 && 
                    (viewable[Y] > itemsCopy[traverseIndexCopy].bounds[Y])) 
                {
                    viewable[Y] -= (viewport[HEIGHT] - PIXELS_LEFT_ON_PAGE);
                    if (viewable[Y] < 0) {
                        viewable[Y] = 0;
                    }
                    uHideShowItems(itemsCopy);
                    setupScroll();
                    uRequestPaint();
                } else {
                    // page up
                    uScrollViewport(Canvas.UP, itemsCopy);
                    uInitItemsInViewport(
                            Canvas.UP, itemsCopy, traverseIndexCopy);
                    updateCommandSet();
                    return;
                }
            } else if ((dir == Canvas.RIGHT || dir == Canvas.DOWN) &&
                (viewable[Y] + viewport[HEIGHT] < viewable[HEIGHT])) 
            {
                // Special case. We're at the bottom-most interactive item,
                // but its internal traversal doesn't allow the very bottom
                // to be seen, we just scroll the view to show it
                if (traverseIndexCopy != -1 &&
                    ((itemsCopy[traverseIndexCopy].bounds[Y] + 
                        itemsCopy[traverseIndex].bounds[HEIGHT]) >
                    (viewable[Y] + viewport[HEIGHT]))) 
                {
                    viewable[Y] += (viewport[HEIGHT] - PIXELS_LEFT_ON_PAGE);
                    if (viewable[Y] > (viewable[HEIGHT] - viewport[HEIGHT])) 
                    {
                        viewable[Y] = viewable[HEIGHT] - viewport[HEIGHT];
                    }
                    uHideShowItems(itemsCopy);
                    setupScroll();
                    uRequestPaint();                    
                } else {            
                    // page down
                    uScrollViewport(Canvas.DOWN, itemsCopy);
                    uInitItemsInViewport(
                            Canvas.DOWN, itemsCopy, traverseIndexCopy);
                    updateCommandSet();
                    return;
                }
            }
            
            // If we don't need to scroll the page and there is nothing
            // to traverse to, we reset the itemTraverse result as if
            // the Item wishes to proceed with internal traversal (as long
            // as there was some initial traverse in the first place, ie,
            // traverseIndex != -1)
            if (traverseIndexCopy != -1) {
                itemTraverse = true;
            }
            updateCommandSet();
        }        
    }
    
    /**
     * Perform a page flip in the given direction. This method will
     * attempt to scroll the view to show as much of the next page
     * as possible. It uses the locations and bounds of the items on
     * the page to best determine a new location - taking into account
     * items which may lie on page boundaries as well as items which
     * may span several pages.
     *
     * @param dir the direction of the flip, either DOWN or UP
     * @param items the set of items on the Form, used to determine
     *        the best suited scroll locations
     */
    void uScrollViewport(int dir, ItemLFImpl[] items) {
                
        if (dir == Canvas.UP) {
            int newY = viewable[Y] - (viewport[HEIGHT] - PIXELS_LEFT_ON_PAGE);
            if (newY < 0) {
                newY = 0;
            }
            
            // We loop upwards until we find the first item which is
            // currently at least partially visible
            int firstVis = items.length;
            for (int i = items.length - 1; i >= 0; i--) {
                if (items[i].visible) {
                    firstVis = i;
                }
            }
            
            // case 1. We're at the top of the item so just
            // traverse normally
            if (items[firstVis].bounds[Y] >= viewable[Y]) {
                viewable[Y] = newY;
                return;
            }
            
            // case 2. We try to fit as much of the partially visible
            // item onscreen as possible.
            int fitY = 
                (items[firstVis].bounds[Y] + items[firstVis].bounds[HEIGHT]) -
                viewport[HEIGHT];
                
            if (fitY > newY && viewable[Y] > fitY) {
                newY = fitY;
            } 
           
            viewable[Y] = newY;
            return;
            
        } else if (dir == Canvas.DOWN) {            
            int newY = viewable[Y] + (viewport[HEIGHT] - PIXELS_LEFT_ON_PAGE);
            if (newY > viewable[HEIGHT] - viewport[HEIGHT]) {
                newY = viewable[HEIGHT] - viewport[HEIGHT];
            }
            
            // We loop downwards until we find the last item which is
            // at least partially visible
            int lastVis = -1;
            for (int i = 0; i < items.length; i++) {
                if (items[i].visible) {
                    lastVis = i;
                }
            }
            
            // case 1. We're at the bottom of the item so just
            // traverse normally
            if (items[lastVis].bounds[Y] + items[lastVis].bounds[HEIGHT] <=
                viewable[Y] + viewport[HEIGHT])
            {
                viewable[Y] = newY;
                return;
            }

            // case 2. We try to fit as much of the partially visible
            // item onscreen as possible unless we're already at the top
            // of the item from a previous scroll
            if (newY > items[lastVis].bounds[Y] && 
                viewable[Y] < items[lastVis].bounds[Y]) 
            {
                newY = items[lastVis].bounds[Y];
            }
            
            viewable[Y] = newY;
            return;
        }
    }
    
    /**
     * Determine if scrolling is needed for a given bounding box,
     * and perform such scrolling if necessary.
     *
     * @param dir the direction of travel
     * @param bounds the bounding box of the traversal location
     * @return <code>true</code> if it was necessary to scroll the view 
     *         in order to best accommodate the bounding box
     */
    boolean scrollForBounds(int dir, int bounds[]) {
        if (bounds == null || bounds[0] == -1) {
            return false;
        }

        // There is a special case whereby the CustomItem
        // spec mandates the upper left corner of the internal
        // traversal rect be visible if the rect is larger than
        // the available viewport
        if (bounds[HEIGHT] >= viewport[HEIGHT] &&
            viewable[Y] != bounds[Y])
        {
            viewable[Y] = bounds[Y];
            return true;
        }
        
        switch (dir) {
            case Canvas.LEFT:
            case Canvas.UP:
                if (bounds[Y] >= viewable[Y]) {
                    return false;
                }

                viewable[Y] -= (viewport[HEIGHT] - PIXELS_LEFT_ON_PAGE);
                if (viewable[Y] < 0) {
                    viewable[Y] = 0;
                }
                return true;
            case Canvas.RIGHT:
            case Canvas.DOWN:
                if (bounds[Y] + bounds[HEIGHT] <=
                    viewable[Y] + viewport[HEIGHT]) 
                {
                    return false;
                }

                viewable[Y] += (viewport[HEIGHT] - PIXELS_LEFT_ON_PAGE);
                if (viewable[Y] > bounds[Y]) {
                    viewable[Y] = bounds[Y];
                }
                if (viewable[Y] + viewport[HEIGHT] > viewable[HEIGHT]) {
                    viewable[Y] = viewable[HEIGHT] - viewport[HEIGHT];
                }
                return true;
            default:
                // for safety/completeness, don't scroll.
                Logging.report(Logging.WARNING, 
                    LogChannels.LC_HIGHUI_FORM_LAYOUT,
                    "FormLFImpl: bounds, dir=" + dir);
                break;
        }
        return false;
    }

    /**
     * Calls uCallItemTraverseOut() and uCallHideNotify() when Form is
     * frozen or hidden
     */
    private void uCallItemHide() {
        
        // 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 {
                itemLFs[traverseIndex].uCallTraverseOut();
            } catch (Throwable t) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_HIGHUI,
                                  "Throwable while traversing out");
                }
            }
        }
        
        // We need to loop through our Items and call hideNotify
        // on those that were visible
        for (int x = 0; x < numOfLFs; x++) {
            try {
                if (itemLFs[x].visible) {
                    itemLFs[x].uCallHideNotify();
                }
            } catch (Throwable t) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_HIGHUI,
                                  "Throwable while hideNotify");
                }
            }
        }
    }

    /**
     * Ensure that dispatchItemLFs array has enough space for use.
     * SYNC NOTE: This function must only be used in event dispatch thread.
     *
     * @param size maximum number of itemLFs needed
     */
    static void ensureDispatchItemArray(int size) {
        if (size > dispatchItemLFs.length) {
            dispatchItemLFs = new ItemLFImpl[size];
        }
    }

    /**
     * Clear contents of dispatchItemLFs array after use.
     * SYNC NOTE: This function must only be used in event dispatch thread.
     *
     * @param alsoShrink true if the array size should be minimized
     */
    static void resetDispatchItemArray(boolean alsoShrink) {

        if (alsoShrink && dispatchItemLFs.length > DISPATCH_ITEM_ARRAY_BLOCK) {
            dispatchItemLFs = new ItemLFImpl[DISPATCH_ITEM_ARRAY_BLOCK];
        } else {
            // Only clean up existing array contents
            for (int i = 0; i < dispatchItemLFs.length; i++) {
                dispatchItemLFs[i] = null;
            }
        }
    }

    /**
     * Calculate the height a displayable would occupy if it was to
     * be displayed.
     *
     * @return the height a displayable would occupy 
     */
    public int getDisplayableHeight() {
        int h = super.getDisplayableHeight();
        if (traverseIndex != -1 && itemLFs != null &&
            itemLFs[traverseIndex] instanceof TextFieldLFImpl) {
            if (((TextFieldLFImpl)itemLFs[traverseIndex]).hasPTI()) {
                h -= PTISkin.HEIGHT;
            }
        }
        return h;
    }


    /**
     * Scroll content inside of the form.
     * @param scrollType scrollType. Scroll type can be one of the following
     * @see ScrollBarLayer.SCROLL_NONE 
     * @see ScrollBarLayer.SCROLL_PAGEUP
     * @see ScrollBarLayer.SCROLL_PAGEDOWN
     * @see ScrollBarLayer.SCROLL_LINEUP
     * @see ScrollBarLayer.SCROLL_LINEDOWN or
     * @see ScrollBarLayer.SCROLL_THUMBTRACK
     * @param thumbPosition
     */
    public void uCallScrollContent(int scrollType, int thumbPosition) {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, 
                           LogChannels.LC_HIGHUI,
                           "FormLF.uCallScrollContent scrollType=" + scrollType + 
                           " thumbPosition=" + thumbPosition); 
        }
        if (owner instanceof TextBox && itemLFs[0] instanceof TextBoxLFImpl) {
            ((TextBoxLFImpl)itemLFs[0]).uCallScrollContent(scrollType, thumbPosition);
        } else {
            super.uCallScrollContent(scrollType, thumbPosition);
        }
    }

    /**
     * Set status of screen rotation
     * @param newStatus
     * @return
     */
    public boolean uSetRotatedStatus (boolean newStatus) {
         boolean status = super.uSetRotatedStatus(newStatus);
         if (status) {
             firstShown = true;
         }
         return status;
     }

    /**
     * Set the vertical scroll indicators for this Screen
     */
    void setVerticalScroll() {
        if (owner instanceof TextBox && itemLFs[0] instanceof TextBoxLFImpl) {
            ((TextBoxLFImpl)itemLFs[0]).setVerticalScroll();
        } else {
            super.setVerticalScroll();
        }
    }
    
    
    /**
     * Perform a page flip in the given direction. This method will
     * attempt to scroll the view to show as much of the next page
     * as possible. It uses the locations and bounds of the items on
     * the page to best determine a new location - taking into account
     * items which may lie on page boundaries as well as items which
     * may span several pages.
     *
     * @param dir the direction of the flip, either DOWN or UP
     */
    protected void uScrollViewport(int dir) {
        ItemLFImpl[] items = null;
        synchronized (Display.LCDUILock) {
            items = new ItemLFImpl[numOfLFs];
            System.arraycopy(itemLFs, 0, items, 0, numOfLFs);
        }
        int oldY = viewable[Y];
        uScrollViewport(dir, items);
        if (oldY != viewable[Y]) {
            uInitItemsInViewport(dir, items, traverseIndex);
            updateCommandSet();
        }
    }

    /**
     * Perform a line scrolling in the given direction. This method will
     * attempt to scroll the view to show next/previous line.
     *
     * @param dir the direction of the flip, either DOWN or UP
     */
    protected void uScrollByLine(int dir) {
        ItemLFImpl[] items = null;
        synchronized (Display.LCDUILock) {
            items = new ItemLFImpl[numOfLFs];
            System.arraycopy(itemLFs, 0, items, 0, numOfLFs);
        }
        int oldY = viewable[Y];
        super.uScrollByLine(dir);
        if (oldY != viewable[Y]) {
            uInitItemsInViewport(dir, items, traverseIndex);
            updateCommandSet();
        }
    }

    /**
     * Perform a scrolling at the given position. 
     * @param context position  
     */
    protected void uScrollAt(int position) {
        ItemLFImpl[] items = null;
        synchronized (Display.LCDUILock) {
            items = new ItemLFImpl[numOfLFs];
            System.arraycopy(itemLFs, 0, items, 0, numOfLFs);
        }
        int oldY = viewable[Y];
        super.uScrollAt(position);
        if (oldY != viewable[Y]) {
            uInitItemsInViewport(viewable[Y] > oldY ? Canvas.DOWN : Canvas.UP,
                                 items, traverseIndex);
            updateCommandSet();
        }
    }

    /**
     * A boolean declaring whether the contents of the viewport
     * can be traversed using the horizontal traversal keys,
     * ie, left and right
     */
    final static boolean TRAVERSE_HORIZONTAL = true;

    /**
     * A boolean declaring whether the contents of the viewport
     * can be traversed using the vertical traversal keys,
     * ie, up and down
     */
    final static boolean TRAVERSE_VERTICAL = true;
    
    /**
     * This is the rate at which the internal array of Items grows if
     * it gets filled up
     */
    static final int GROW_SIZE = 4;

    /**
     * This is the number of pixels left from the previous "page"
     * when a page up or down occurs
     */
    static final int PIXELS_LEFT_ON_PAGE = 15;
    
    /** The item index which has the traversal focus */
    int traverseIndex = -1;

    /** 
     * This is a special case variable which tracks the last
     * traversed item when a new item is traversed to via the
     * setCurrentItem() call.
     */
    ItemLFImpl lastTraverseItem;
    
    /** 
     * A flag indicating the return value of the currently
     * selected Item from its traverse() method
     */
    boolean itemTraverse;
    
    /** 
     * A flag that shows that itemLFs[] storage has been modified
     * (items inserted/deleted) and earlier made copies (extends itemsCopy[])
     * are outdated. 
     * flag is set by item insert/delete operations, 
     * cleared when copy operation is performed. 
     */
    boolean itemsModified; 
    
    /** 
     * true if a callPointerPressed event has occurred without
     * a corresponding callPointerReleased. false otherwise
     */
    boolean pointerPressed;

    /**
     * A flag indicating if scroll has been initialized. When the form
     * is first shown, it can only set the scroll after it has become
     * current, which is after show() and before paint(), but there's
     * hard to set the scroll then until the paint() routine - which
     * is not the best place to do it.
     */
    boolean scrollInitialized;

    /**
     * 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
     */
    int[] visRect;

    /**
     * Array of ItemLFs that correspond to the array of items in Form
     */
    ItemLFImpl[] itemLFs;

    /**
     * Block size of the temporary array of ItemLFs used in dispatch.
     */
    final static int DISPATCH_ITEM_ARRAY_BLOCK = 10;

    /**
     * Temporary array of ItemLFs that is ONLY used in dispatch thread 
     * during show, hide and re-layout this Form.
     *
     * ensureDispatchItemArray() should be called before use and
     * resetDispatchItemArray() should be called when it is no longer needed,
     * to allow ItemLFImpl objects been GC'ed.
     */
    static ItemLFImpl[] dispatchItemLFs =
                new ItemLFImpl[DISPATCH_ITEM_ARRAY_BLOCK];

    /**
     * The number of views present in this FormLF.
     */
    int numOfLFs;
    
    /**
     * optimization flag
     */
    boolean firstShown = true;

    /**
     * Item that was made visible using display.setCurrentItem() call
     * while FormLF was in HIDDEN or FROZEN state.
     */
    Item pendingCurrentItem; // = null

} // class FormLFImpl
