/*
 *  
 *
 * 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 java.util.TimerTask;
import java.util.Timer;

import javax.microedition.lcdui.game.Sprite;
import com.sun.midp.chameleon.skins.ScreenSkin;
import com.sun.midp.chameleon.skins.GaugeSkin;
import com.sun.midp.chameleon.skins.ProgressBarSkin;
import com.sun.midp.chameleon.skins.BusyCursorSkin;
import com.sun.midp.chameleon.skins.UpdateBarSkin;
import com.sun.midp.chameleon.skins.resources.GaugeResources;
import com.sun.midp.chameleon.skins.resources.ProgressBarResources;
import com.sun.midp.chameleon.skins.resources.UpdateBarResources;
import com.sun.midp.chameleon.skins.resources.BusyCursorResources;
import com.sun.midp.chameleon.*;
import com.sun.midp.lcdui.Text;
import com.sun.midp.configurator.Constants;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
* This is the look & feel implementation for Gauge.
*/
class GaugeLFImpl extends ItemLFImpl implements GaugeLF {

    /**
     * Creates GaugeLF for the passed in Gauge.
     * @param gauge the Gauge object associated with this look&feel.
     */
    GaugeLFImpl(Gauge gauge) {
        
        super(gauge);

        this.gauge = gauge;

        lSetMaxValue(0, gauge.maxValue);
        lSetValue(0, gauge.value);

        // IMPL NOTE: Make this smarter so that only the
        // resources we need load. Also, replicate this
        // code in the set* methods so that a gauge which
        // changes on the fly has the proper resources
        if (gauge.interactive) {
            GaugeResources.load();
        } else {
            ProgressBarResources.load();
            UpdateBarResources.load();
            BusyCursorResources.load();
        }
        
        percentLoc = new int[2];
    }


    // *****************************************************
    //  Public methods
    // *****************************************************


    /**
     * Notifies L&F of a value change in the corresponding Gauge.
     * @param oldValue - the old value set in the Gauge
     * @param newValue - the new value set in the Gauge
     */
    public void lSetValue(int oldValue, int newValue) {
        synchronized (Display.LCDUILock) {
            if (gauge.maxValue == Gauge.INDEFINITE) {
                /**
                 *  -- if there are more than one Gauge, we shouldn't
                 *  stop other continuous gauges until we change Displayable.
                 *  IMPL NOTE: see if this is the only Gauge on the form   -au
                 */
                
                if (newValue == Gauge.CONTINUOUS_RUNNING) {
                    // If this gauge is already visible and its new value
                    // is changed to CONTINUOUS_RUNNING, then start update
                    // task here. 
                    // Otherwise, delay update task creation until it becomes 
                    // visible (I.e. lCallShow is called).
                    if (visible) {
                        startGaugeUpdateTask();
                    }
                } else if (oldValue == Gauge.CONTINUOUS_RUNNING) {
                    cancelGaugeUpdateTask();
                }
                if (oldValue != newValue) 
                    lRequestInvalidate(true, true);
                else
                    lRequestPaint();
            } else if (oldValue != newValue) {
                lRequestPaint();
            }
        } // end sync
    }

    /**
     * Gets the current value.
     * @return the current value
     */
    public int lGetValue() {
        return gauge.value;
    }

    /**
     * Notifies L&F of a maximum value change in the corresponding Gauge
     * @param oldMaxValue - the old maximum value set in the Gauge
     * @param newMaxValue - the new maximum value set in the Gauge
     */
    public void lSetMaxValue(int oldMaxValue, int newMaxValue) {        
        // changing the max value will change the scale of the gauge
        if (oldMaxValue != newMaxValue) {        
            lRequestInvalidate(true, true);
        }
    }


    // *****************************************************
    //  Package private methods
    // *****************************************************
    
    /**
     * Determine if this Gauge should not be traversed to
     *
     * @return true if this Gauge should not be traversed to
     */
    boolean shouldSkipTraverse() {
        // Only traverse to gauges which are interactive, or have
        // item-specific commands added to them
        return (gauge.interactive) ? false : super.shouldSkipTraverse(); 
    }

    /**
     * Sets the content size in the passed in array.
     * Content is calculated based on the availableWidth.
     * size[WIDTH] and size[HEIGHT] should be set by this method.
     * @param size The array that holds Item content size and location 
     *             in Item internal bounds coordinate system.
     * @param availableWidth The width available for this Item
     */
    void lGetContentSize(int size[], int availableWidth) {
        if (gauge.interactive) {
            size[WIDTH] = GaugeSkin.WIDTH;
            size[HEIGHT] = GaugeSkin.HEIGHT;
        } else if (gauge.maxValue != Gauge.INDEFINITE) {
            size[WIDTH] = ProgressBarSkin.WIDTH;
            size[HEIGHT] = ProgressBarSkin.HEIGHT;
        } else if ((gauge.value == Gauge.CONTINUOUS_RUNNING) ||
                   (gauge.value == Gauge.CONTINUOUS_IDLE)) {
           size[WIDTH] = BusyCursorSkin.WIDTH;
           size[HEIGHT] = BusyCursorSkin.HEIGHT;
        } else {
            size[WIDTH] = UpdateBarSkin.WIDTH;
            size[HEIGHT] = UpdateBarSkin.HEIGHT;
        }
    }

    /**
     * Determine if this Item should have a newline after it
     *
     * @return true if it should have a newline after
     */
    boolean equateNLA() {
        if (super.equateNLA()) {
            return true;
        }

        return ((gauge.layout & Item.LAYOUT_2) != Item.LAYOUT_2);
    }
               

    /**
     * Determine if this Item should have a newline before it
     *
     * @return true if it should have a newline before
     */
    boolean equateNLB() {
        if (super.equateNLB()) {
            return true;
        }

        return ((gauge.layout & Item.LAYOUT_2) != Item.LAYOUT_2);
    }


    /**
     * Handle traversal within this Gauge
     *
     * @param dir the direction of traversal
     * @param viewportWidth the width of the viewport
     * @param viewportHeight the height of the viewport
     * @param visRect the in/out rectangle for the internal traversal location
     * @return True if traversal occurred within this Gauge
     */
    boolean uCallTraverse(int dir, int viewportWidth, int viewportHeight,
                         int[] visRect) 
    {
        
        super.uCallTraverse(dir, viewportWidth, viewportHeight, visRect);
       
        // If its a non-interactive gauge, there is no internal traversal
        // No need to lock either, we just access the boolean once.
        // (In fact, traverse should never be called on a non-interactive
        // gauge because shouldSkipTraverse() returns true in that case)
        if (!gauge.interactive) {
            return false;
        }
        
        // If it was an invalidate or something, just keep the focus
        // button where it is and reflect there is internal traversal
        if (dir == CustomItem.NONE) {
            intTraverse = true;
        } else {
            // The standard horizontal gauge's orientation is RIGHT, all
            // others accommodate vertical gauges and possible gauges for
            // right-to-left languages (which would be a LEFT orientation)
            
            switch (GaugeSkin.ORIENTATION) {
                // Gauge increases left to right, horizontally
                // (this is the default in chameleon)
            case Graphics.RIGHT:
                switch (dir) {
                case Canvas.LEFT:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_INC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_DEC_BTN);
                        focusBtn = I_DEC_BTN;
                    }
                    break;                        
                case Canvas.RIGHT:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_DEC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_INC_BTN);
                        focusBtn = I_INC_BTN;
                    }
                    break;                        
                case Canvas.UP:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_INC_BTN;
                    }
                    break;
                case Canvas.DOWN:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_DEC_BTN;
                    }
                    break;
                default:
                    Logging.report(Logging.ERROR, 
                                   LogChannels.LC_HIGHUI,
                                   "GaugeLFImpl: uCallTraverse, dir=" +dir);
                    break;
                }
                break;
                // Gauge increases right to left, horizontally
                // (think of right-to-left languages)
            case Graphics.LEFT:
                switch (dir) {
                case Canvas.LEFT:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_DEC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_INC_BTN);
                        focusBtn = I_INC_BTN;
                    }
                    break;                        
                case Canvas.RIGHT:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_INC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_DEC_BTN);
                        focusBtn = I_DEC_BTN;
                    }
                    break;                        
                case Canvas.UP:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_DEC_BTN;
                    }
                    break;
                case Canvas.DOWN:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_INC_BTN;
                    }
                    break;
                default:
                    Logging.report(Logging.ERROR, 
                                   LogChannels.LC_HIGHUI,
                                   "GaugeLFImpl: uCallTraverse, dir=" +dir);
                    break;
                }
                break;
                // Gauge increases bottom to top, vertically
            case Graphics.TOP:
                switch (dir) {
                case Canvas.LEFT:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_INC_BTN;
                    }
                    break;
                case Canvas.RIGHT:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_DEC_BTN;
                    }
                    break;
                case Canvas.UP:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_DEC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_INC_BTN);
                        focusBtn = I_INC_BTN;
                    }
                    break;                        
                case Canvas.DOWN:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_INC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_DEC_BTN);
                        focusBtn = I_DEC_BTN;
                    }
                    break;
                default:
                    Logging.report(Logging.ERROR, 
                                   LogChannels.LC_HIGHUI,
                                   "GaugeLFImpl: uCallTraverse, dir=" +dir);
                    break;                        
                }
                break;
                // Gauge increases top to bottom, vertically
            case Graphics.BOTTOM:
                switch (dir) {
                case Canvas.LEFT:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_DEC_BTN;
                    }
                    break;
                case Canvas.RIGHT:
                    intTraverse = initialTraverse;
                    if (initialTraverse) {
                        focusBtn = I_INC_BTN;
                    }
                    break;
                case Canvas.UP:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_INC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_DEC_BTN);
                        focusBtn = I_DEC_BTN;
                    }
                    break;                        
                case Canvas.DOWN:
                    if (initialTraverse) {
                        intTraverse = true;
                        focusBtn = I_DEC_BTN;
                    } else {
                        intTraverse = (focusBtn != I_INC_BTN);
                        focusBtn = I_INC_BTN;
                    }
                    break;
                default:
                    Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                                   "GaugeLFImpl: uCallTraverse, dir=" +dir);
                    break;
                }
                break;
            default:
                Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                               "GaugeLFImpl: uCallTraverse," +
                               "GaugeSkin.ORIENTATION=" +
                               GaugeSkin.ORIENTATION);
                break;
            }
        }
        
        
        switch (focusBtn) {
        case I_INC_BTN:
            visRect[X] = GaugeSkin.INC_BTN_X;
            visRect[Y] = GaugeSkin.INC_BTN_Y;
            if (GaugeSkin.IMAGE_INC_BTN != null) {
                visRect[WIDTH] = GaugeSkin.IMAGE_INC_BTN.getWidth();
                visRect[HEIGHT] = GaugeSkin.IMAGE_INC_BTN.getHeight();
            } else {
                visRect[WIDTH] = 15;
                visRect[HEIGHT] = 15;
            }
            break;
        case I_DEC_BTN:
            visRect[X] = GaugeSkin.DEC_BTN_X;
            visRect[Y] = GaugeSkin.DEC_BTN_Y;
            if (GaugeSkin.IMAGE_DEC_BTN != null) {
                visRect[WIDTH] = GaugeSkin.IMAGE_DEC_BTN.getWidth();
                visRect[HEIGHT] = GaugeSkin.IMAGE_DEC_BTN.getHeight();
            } else {
                visRect[WIDTH] = 15;
                visRect[HEIGHT] = 15;
            }
            break;
        default:
            Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                           "GaugeLFImpl: uCallTraverse, focusBtn=" +focusBtn);
            break;
        }
        
        // Gauge should always return true on at least the initial
        // traverse, or any internal traverse between the two buttons
        if (initialTraverse || intTraverse) {
            initialTraverse = false;
            uRequestPaint();
            return true;
        } 

        return false;
    }

    /**
     * Called by the system to indicate traversal has left this Item.
     */
    void uCallTraverseOut() {
        super.uCallTraverseOut();
        initialTraverse = true;
    }

    /**
     * Called by the system to signal a key repeat
     *
     * @param keyCode the key code of the key that has been pressed
     */
    void uCallKeyRepeated(int keyCode) {
        uCallKeyPressed(keyCode);
    }
    
    /**
     * Called by the system to signal a key press.
     *
     * @param keyCode the key code of the key that has been pressed
     */
    void uCallKeyPressed(int keyCode) {
        if (keyCode != Constants.KEYCODE_SELECT ||
            !gauge.interactive) {
            return;
        }
        
        Form form = null;

        synchronized (Display.LCDUILock) {
            int maxValue = gauge.maxValue;
            int oldValue = gauge.value;
            int value = oldValue;

            switch (focusBtn) {
            case I_INC_BTN:
                value++;
                break;
            case I_DEC_BTN:
                value--;
                break;
            default:
                Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                               "GaugeLFImpl: uCallKeyPressed, focusBtn=" +
                               focusBtn);
                break;
            }
            
            gauge.setValueImpl(value);

            // IMPL NOTE: paint optimization
            lRequestPaint();

            if (value != oldValue) {
                // notify the ItemStateChangedListener
                form = (Form)gauge.owner;
            }
            
        } // end synchronized

        // SYNC NOTE: We make sure we notify the ItemStateChangedListener
        // outside of LCDUILock
        if (form != null) {
            form.uCallItemStateChanged(gauge);
        }
    }

    
    /**
     * Called by the system to signal a pointer press
     *
     * @param x the x coordinate of the pointer down
     * @param y the y coordinate of the pointer down
     *
     * @see #getInteractionModes
     */
    void uCallPointerPressed(int x, int y) {
        if (gauge.interactive) {
            pointerArea = getPointerArea(x, y);

            switch(pointerArea) {
            case DEC_BTN_AREA:
                focusBtn = I_DEC_BTN;
                uRequestPaint();
                break;
            case INC_BTN_AREA:
                focusBtn = I_INC_BTN;
                uRequestPaint();
                break;
            default:
                break;
            }
        }
    }
   
    /**
     * Get the area of gauge the pointer cliked in.
     * @param x x coordinate of pointer 
     * @param y y coordinate of pointer 
     * @return the gauge area is returned. The possible values are
     *
     * @see #INVALID_AREA if pointer is out of gauge's bounds 
     * @see #INC_BTN_AREA if pointer is on the increment button
     * @see #DEC_BTN_AREA if pointer is on the decrement button
     * @see #METER_AREA if pointer is on the meter
     */
    private int getPointerArea(int x, int y) {
        int area = INVALID_AREA;

        x -= contentBounds[X];
        y -= contentBounds[Y];

        if (area == INVALID_AREA &&
            GaugeSkin.IMAGE_DEC_BTN != null &&
            // check coordinates
            x >= GaugeSkin.DEC_BTN_X &&
            y > GaugeSkin.DEC_BTN_Y &&
            x <= (GaugeSkin.DEC_BTN_X +
                  GaugeSkin.IMAGE_DEC_BTN.getWidth()) &&
            y <= (GaugeSkin.DEC_BTN_Y +
                  GaugeSkin.IMAGE_DEC_BTN.getHeight())) {
            
            area = DEC_BTN_AREA;
        }
        if (area == INVALID_AREA &&
            GaugeSkin.IMAGE_INC_BTN != null &&
            // check coordinates
            x >= GaugeSkin.INC_BTN_X &&
            y > GaugeSkin.INC_BTN_Y &&
            x <= (GaugeSkin.INC_BTN_X +
                  GaugeSkin.IMAGE_INC_BTN.getWidth()) &&
            y <= (GaugeSkin.INC_BTN_Y +
                  GaugeSkin.IMAGE_INC_BTN.getHeight())) {
            
            area = INC_BTN_AREA;
        }
        if (area == INVALID_AREA &&
            // check coordinates
            x >= GaugeSkin.METER_X && y > GaugeSkin.METER_Y &&
            x <= (GaugeSkin.METER_X +
                  GaugeSkin.IMAGE_METER_FULL.getWidth()) && 
            y <= (GaugeSkin.METER_Y +
                  GaugeSkin.IMAGE_METER_FULL.getHeight())) {
            
            area = METER_AREA;
        }
        
        return area;
    }
    
    /**
     * Called by the system to signal a pointer release
     *
     * @param x the x coordinate of the pointer up
     * @param y the x coordinate of the pointer up
     */
    void uCallPointerReleased(int x, int y) {
        if (gauge.interactive) {
            int newArea = getPointerArea(x, y);
        
            if (pointerArea == newArea) {
                switch (pointerArea) {
                case DEC_BTN_AREA:
                case INC_BTN_AREA:
                    uCallKeyPressed(Constants.KEYCODE_SELECT);
                    break;
                case METER_AREA:
                    {
                        Form form = null;
                        synchronized (Display.LCDUILock) {
                            int oldValue = gauge.value;
                            int locationOnMeter = x - contentBounds[X] - GaugeSkin.METER_X;
                            float percent = locationOnMeter * 100 / GaugeSkin.IMAGE_METER_FULL.getWidth(); 
                            float value = percent / 100 * gauge.maxValue;
                            /* round the value */
                            int intValue = (int)value;
                            float remainder = value - intValue;
                            if (remainder > 0.5) {
                                intValue++;
                            }
                            
                            gauge.setValueImpl(intValue);
                            lRequestPaint();
                            if (intValue != oldValue) {
                                // notify the ItemStateChangedListener
                                form = (Form)gauge.owner;
                            }
                        }
                        // SYNC NOTE: We make sure we notify the ItemStateChangedListener
                        // outside of LCDUILock
                        if (form != null) {
                            form.uCallItemStateChanged(gauge);
                        }
                    }
                    break;
                default:
                    break;
                } // end of switch 
            } // pointerArea == newArea
        } // interactive gauge
    }

    
    /**
     * Called by the system to notify this Item it is being shown
     *
     * <p>The default implementation of this method updates
     * the 'visible' state
     */
    void lCallShowNotify() {
        
        super.lCallShowNotify();

        // Start update task for CONTINUOUS_RUNNING gauge upon visible
        if (gauge.maxValue == Gauge.INDEFINITE && 
            gauge.value == Gauge.CONTINUOUS_RUNNING) {
            startGaugeUpdateTask();
        }
    }
    
    /**
     * Called by the system to notify this Item it is being hidden
     *
     * <p>The default implementation of this method updates
     * the 'visible' state
     */
    void lCallHideNotify() {
        
        super.lCallHideNotify();

        cancelGaugeUpdateTask();
    }

    /**
     * Paints the content area of this Gauge. 
     * Graphics is translated to contents origin.
     * @param g The graphics where Gauge content should be painted
     * @param w The width available for the Item's content
     * @param h The height available for the Item's content
     */
    void lPaintContent(Graphics g, int w, int h) {     
        if (gauge.interactive) {
            lPaintInteractiveGauge(g, w, h, gauge.maxValue, gauge.value);
        } else if (gauge.maxValue == Gauge.INDEFINITE) {
            lPaintIndefinite(g, w, h, gauge.maxValue, gauge.value);
        } else {
            lPaintProgressBar(g, w, h, gauge.maxValue, gauge.value);
        }
    }
    
    void lPaintInteractiveGauge(Graphics g, int w, int h, 
                                int maxValue, int value)
    {
        // case: interactive gauge
        // draw background, if present
        if (GaugeSkin.IMAGE_BG != null) {
            g.drawImage(GaugeSkin.IMAGE_BG, 0, 0,
                        Graphics.LEFT | Graphics.TOP);
        } else {
            g.setColor(0xCCCCCC);
            g.fillRect(0, 0, GaugeSkin.WIDTH, GaugeSkin.HEIGHT);
            g.setColor(0);
            return; 
            // we return early on interactive gauge if there are
            // no images because its rather complicated to draw
            // otherwise, at least right now            
        }

        // computation to decide how much of filled & unfilled
        // portions to be displayed
        int body_full;
        int body_empty;
        if (GaugeSkin.ORIENTATION == Graphics.LEFT ||
            GaugeSkin.ORIENTATION == Graphics.RIGHT)
        {
            body_empty = GaugeSkin.IMAGE_METER_FULL.getWidth();
            body_full = (((value * 100) / maxValue) * body_empty) / 100;
            body_empty -= body_full;
            
            if (GaugeSkin.ORIENTATION == Graphics.RIGHT) {
                g.drawRegion(GaugeSkin.IMAGE_METER_FULL, 0, 0, body_full, 
                             GaugeSkin.IMAGE_METER_FULL.getHeight(), 
                             Sprite.TRANS_NONE, GaugeSkin.METER_X, 
                             GaugeSkin.METER_Y, 
                             Graphics.LEFT | Graphics.TOP);
                             
                g.drawRegion(GaugeSkin.IMAGE_METER_EMPTY, body_full, 0, 
                             body_empty, 
                             GaugeSkin.IMAGE_METER_EMPTY.getHeight(), 
                             Sprite.TRANS_NONE, GaugeSkin.METER_X + 
                             body_full, GaugeSkin.METER_Y, 
                             Graphics.LEFT | Graphics.TOP);
            } else {
                g.drawRegion(GaugeSkin.IMAGE_METER_EMPTY, 0, 0, body_empty, 
                             GaugeSkin.IMAGE_METER_EMPTY.getHeight(), 
                             Sprite.TRANS_NONE, GaugeSkin.METER_X, 
                             GaugeSkin.METER_Y, 
                             Graphics.LEFT | Graphics.TOP);
                             
                g.drawRegion(GaugeSkin.IMAGE_METER_FULL, body_empty, 0, 
                             body_full, 
                             GaugeSkin.IMAGE_METER_FULL.getHeight(), 
                             Sprite.TRANS_NONE, GaugeSkin.METER_X + 
                             body_empty, GaugeSkin.METER_Y, 
                             Graphics.LEFT | Graphics.TOP);
            }                               
                         
        } else {
            body_empty = GaugeSkin.IMAGE_METER_FULL.getHeight();
            body_full = (((value * 100) / maxValue) * body_empty) / 100;
            body_empty -= body_full;
            
            if (GaugeSkin.ORIENTATION == Graphics.TOP) {
                g.drawRegion(GaugeSkin.IMAGE_METER_FULL, 0, body_empty,
                             GaugeSkin.IMAGE_METER_FULL.getWidth(), 
                             body_full, 
                             Sprite.TRANS_NONE, GaugeSkin.METER_X, 
                             GaugeSkin.METER_Y + body_empty, 
                             Graphics.LEFT | Graphics.TOP);
                             
                g.drawRegion(GaugeSkin.IMAGE_METER_EMPTY, 0, 0,
                             GaugeSkin.IMAGE_METER_EMPTY.getWidth(), 
                             body_empty,
                             Sprite.TRANS_NONE, GaugeSkin.METER_X, 
                             GaugeSkin.METER_Y, 
                             Graphics.LEFT | Graphics.TOP);
            } else {
                g.drawRegion(GaugeSkin.IMAGE_METER_EMPTY, 0, body_full,
                             GaugeSkin.IMAGE_METER_EMPTY.getWidth(), 
                             body_empty, 
                             Sprite.TRANS_NONE, GaugeSkin.METER_X, 
                             GaugeSkin.METER_Y + body_full, 
                             Graphics.LEFT | Graphics.TOP);
                             
                g.drawRegion(GaugeSkin.IMAGE_METER_FULL, 0, 0,
                             GaugeSkin.IMAGE_METER_FULL.getWidth(), 
                             body_full,
                             Sprite.TRANS_NONE, GaugeSkin.METER_X, 
                             GaugeSkin.METER_Y, 
                             Graphics.LEFT | Graphics.TOP);
            }                               
            
        }

        // display current value of gauge
        drawNumber(g, GaugeSkin.IMAGE_VALUES, 11, value, 
                   GaugeSkin.VALUE_X, GaugeSkin.VALUE_Y,
                   (value * 100) / maxValue);

        if (GaugeSkin.IMAGE_DEC_BTN != null) {
            // decrease button            
            g.drawImage(GaugeSkin.IMAGE_DEC_BTN,
                        GaugeSkin.DEC_BTN_X,
                        GaugeSkin.DEC_BTN_Y,
                        Graphics.LEFT | Graphics.TOP);
            if (hasFocus && focusBtn == I_DEC_BTN) {
                g.setColor(ScreenSkin.COLOR_TRAVERSE_IND);
                g.drawRect(GaugeSkin.DEC_BTN_X, 
                           GaugeSkin.DEC_BTN_Y, 
                           GaugeSkin.IMAGE_DEC_BTN.getWidth(), 
                           GaugeSkin.IMAGE_DEC_BTN.getHeight());
                g.setColor(0);
            }
        } else {
            g.drawString("-", GaugeSkin.DEC_BTN_X, GaugeSkin.DEC_BTN_Y,
                         Graphics.LEFT | Graphics.TOP);
            if (hasFocus && focusBtn == I_DEC_BTN) {
                g.setColor(ScreenSkin.COLOR_TRAVERSE_IND);
                g.drawRect(GaugeSkin.DEC_BTN_X,
                           GaugeSkin.DEC_BTN_Y,
                           g.getFont().charWidth('-'),
                           g.getFont().charWidth('-'));
                g.setColor(0);
            }
        }
        
        if (GaugeSkin.IMAGE_INC_BTN != null) {
            // increase button
            g.drawImage(GaugeSkin.IMAGE_INC_BTN,
                        GaugeSkin.INC_BTN_X,
                        GaugeSkin.INC_BTN_Y,
                        Graphics.LEFT | Graphics.TOP);
            if (hasFocus && focusBtn == I_INC_BTN) {
                g.setColor(ScreenSkin.COLOR_TRAVERSE_IND);
                g.drawRect(GaugeSkin.INC_BTN_X,
                           GaugeSkin.INC_BTN_Y, 
                           GaugeSkin.IMAGE_INC_BTN.getWidth(), 
                           GaugeSkin.IMAGE_INC_BTN.getHeight());
                g.setColor(0);
            }
        } else {
            g.drawString("+", GaugeSkin.INC_BTN_X, GaugeSkin.INC_BTN_Y,
                         Graphics.LEFT | Graphics.TOP);
            
            if (hasFocus && focusBtn == I_INC_BTN) {
                g.setColor(ScreenSkin.COLOR_TRAVERSE_IND);
                g.drawRect(GaugeSkin.INC_BTN_X,
                           GaugeSkin.INC_BTN_Y,
                           g.getFont().charWidth('+'),
                           g.getFont().charWidth('+'));
                g.setColor(0);
            }
        }
    } // lPaintInteractiveGauge
            
    void lPaintIndefinite(Graphics g, int w, int h, 
                          int maxValue, int value)
    {            
        switch (value) {
        case Gauge.CONTINUOUS_RUNNING:
            drawBusyCursor(g, nextFrame);
            
            // increment the frame counter of the incremental gauge
            // and remember to reset it when it hits the total number
            // of frames, so it can start all over again
            if (BusyCursorSkin.FRAME_SEQUENCE != null) {
                nextFrame = ++nextFrame % BusyCursorSkin.FRAME_SEQUENCE.length;
            }
            break;
        case Gauge.CONTINUOUS_IDLE:
            drawBusyCursor(g, 0);
            break;
        case Gauge.INCREMENTAL_UPDATING:
            drawUpdateBar(g, nextFrame);
            
            // increment the frame counter of the incremental gauge
            // and remember to reset it when it hits the total number
            // of frames, so it can start all over again
            if (UpdateBarSkin.FRAME_SEQUENCE != null) {
                nextFrame = ++nextFrame % UpdateBarSkin.FRAME_SEQUENCE.length;
            }
            break;
        case Gauge.INCREMENTAL_IDLE:
            drawUpdateBar(g, 0);
            break;
        default:
            Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                           "GaugeLFImpl: lPaintIndefinite, value=" +value);
            break;
        }
    }
            
    void lPaintProgressBar(Graphics g, int w, int h, 
                           int maxValue, int value)
    {
            
        // case: non-interactive definite range gauge/progressbar
        // draw background, if present
        if (ProgressBarSkin.IMAGE_BG != null) {
            g.drawImage(ProgressBarSkin.IMAGE_BG, 0, 0,
                        Graphics.LEFT | Graphics.TOP);
        } else {
            g.setColor(0xCCCCCC);
            g.fillRect(0, 0, ProgressBarSkin.WIDTH, ProgressBarSkin.HEIGHT);
            g.setColor(0);
        }

        // first compute current value of gauge as a percentage
        int n = (value * 100) / maxValue;

        // decide how much of filled & unfilled portions to be shown
        int body_full = 0;
        if (ProgressBarSkin.IMAGE_METER_FULL != null) {
            body_full = 
                (n * ProgressBarSkin.IMAGE_METER_FULL.getWidth()) / 100;
            g.drawRegion(ProgressBarSkin.IMAGE_METER_FULL, 0, 0, 
                         body_full, 
                         ProgressBarSkin.IMAGE_METER_FULL.getHeight(), 
                         Sprite.TRANS_NONE, 
                         ProgressBarSkin.METER_X, 
                         ProgressBarSkin.METER_Y, 
                         Graphics.LEFT | Graphics.TOP);
        } else {
            body_full =
                (n * ProgressBarSkin.WIDTH - ProgressBarSkin.VALUE_WIDTH - 
                    10) / 100;
            g.fillRect(ProgressBarSkin.METER_X, ProgressBarSkin.METER_Y,
                       body_full, ProgressBarSkin.HEIGHT - 
                            (2 * ProgressBarSkin.METER_Y));            
        }
        
        int body_empty = 0;
        if (ProgressBarSkin.IMAGE_METER_FULL != null) {
            body_empty = 
                ProgressBarSkin.IMAGE_METER_FULL.getWidth() - body_full;
                g.drawRegion(ProgressBarSkin.IMAGE_METER_EMPTY, body_full,
                             0, body_empty, 
                             ProgressBarSkin.IMAGE_METER_EMPTY.getHeight(), 
                             Sprite.TRANS_NONE, 
                             ProgressBarSkin.METER_X + body_full, 
                             ProgressBarSkin.METER_Y, 
                             Graphics.LEFT | Graphics.TOP);
        } else {
            body_empty = 
                (ProgressBarSkin.WIDTH - ProgressBarSkin.VALUE_WIDTH -
                    10) - body_full;
            g.setColor(0xFFFFFF);
            g.fillRect(ProgressBarSkin.METER_X + body_full,
                       ProgressBarSkin.METER_Y,
                       body_empty,
                       ProgressBarSkin.HEIGHT -
                        (2 * ProgressBarSkin.METER_Y));
        }

        // current value of gauge as a percentage 
        drawNumber(g, ProgressBarSkin.IMAGE_VALUES, 11, n,
                   ProgressBarSkin.VALUE_X, ProgressBarSkin.VALUE_Y,
                   (value * 100) / maxValue);

        // the percentage symbol
        drawPercentage(g, ProgressBarSkin.IMAGE_PERCENTS, value);           
    }               
               
    
    /**
     * Paints the given frame of the incremental updating/idle gauge.
     *
     * @param g the graphics to paint to
     * @param frameToDraw the frame index of the frame to be drawn
     *                    from a frame-sequence
     */
    void drawUpdateBar(Graphics g, int frameToDraw) {
        if (UpdateBarSkin.FRAME_SEQUENCE == null ||
            frameToDraw > UpdateBarSkin.FRAME_SEQUENCE.length) 
        {
            return;
        }
        
        frameToDraw = UpdateBarSkin.FRAME_SEQUENCE[frameToDraw];
        
        // draw background, if present
        if (UpdateBarSkin.IMAGE_BG != null) {
            g.drawImage(UpdateBarSkin.IMAGE_BG, 0, 0,
                        Graphics.LEFT | Graphics.TOP);
        } else {
            g.setColor(0xCCCCCC);
            g.fillRect(0, 0, UpdateBarSkin.WIDTH, UpdateBarSkin.HEIGHT);
            g.setColor(0);
        }
        
        if (UpdateBarSkin.IMAGE_FRAME != null 
            && frameToDraw < UpdateBarSkin.IMAGE_FRAME.length) 
        {
            // draw the frame
            g.drawImage(UpdateBarSkin.IMAGE_FRAME[frameToDraw],
                        UpdateBarSkin.FRAME_X,
                        UpdateBarSkin.FRAME_Y,
                        Graphics.LEFT | Graphics.TOP);
        } else {
            g.fillRect(2, 2, 
                UpdateBarSkin.WIDTH - 4, UpdateBarSkin.HEIGHT - 4);
            int width = 
                (UpdateBarSkin.WIDTH / UpdateBarSkin.FRAME_SEQUENCE.length) *
                frameToDraw;
            if (width < 0) {
                width = 0;
            }
            g.setColor(0xFFFFFF);
            g.fillRect(2, 2, width - 4, UpdateBarSkin.HEIGHT - 4);
            g.setColor(0);
        }
    }

    /**
     * Paints the given frame of the continuous running/idle gauge.
     *
     * @param g the graphics to paint to
     * @param frameToDraw the frame index of the frame to be drawn
     *                    from a frame-sequence
     */
    void drawBusyCursor(Graphics g, int frameToDraw) {
        if (BusyCursorSkin.FRAME_SEQUENCE == null ||
            frameToDraw > BusyCursorSkin.FRAME_SEQUENCE.length) 
        {
            return;
        }
        
        frameToDraw = BusyCursorSkin.FRAME_SEQUENCE[frameToDraw];
        
        // draw background, if present
        if (BusyCursorSkin.IMAGE_BG != null) {
            g.drawImage(BusyCursorSkin.IMAGE_BG, 0, 0,
                        Graphics.LEFT | Graphics.TOP);
        } else {
            g.setColor(0xCCCCCC);
            g.fillRect(0, 0, BusyCursorSkin.WIDTH, BusyCursorSkin.HEIGHT);
            g.setColor(0);
        }
        
        if (BusyCursorSkin.IMAGE_FRAME != null &&
            frameToDraw < BusyCursorSkin.IMAGE_FRAME.length) 
        {
            // draw the frame
            g.drawImage(BusyCursorSkin.IMAGE_FRAME[frameToDraw],
                        BusyCursorSkin.FRAME_X,
                        BusyCursorSkin.FRAME_Y,
                        Graphics.LEFT | Graphics.TOP);
        } else {
            switch (frameToDraw % 3) {
                case 0:
                    g.drawString("-", 
                                 (int)(BusyCursorSkin.WIDTH / 2), 
                                 (int)(BusyCursorSkin.HEIGHT / 2),
                                 Graphics.HCENTER | Graphics.BOTTOM);
                    break;
                case 1:
                    g.drawString("\\", 
                                 (int)(BusyCursorSkin.WIDTH / 2), 
                                 (int)(BusyCursorSkin.HEIGHT / 2),
                                 Graphics.HCENTER | Graphics.BOTTOM);
                    break;
                case 2:
                    g.drawString("|", 
                                 (int)(BusyCursorSkin.WIDTH / 2), 
                                 (int)(BusyCursorSkin.HEIGHT / 2),
                                 Graphics.HCENTER | Graphics.BOTTOM);
                    break;
                default:
                    g.drawString("/", 
                                 (int)(BusyCursorSkin.WIDTH / 2), 
                                 (int)(BusyCursorSkin.HEIGHT / 2),
                                 Graphics.HCENTER | Graphics.BOTTOM);
                    break;
            }
        }
    }

    /**
     * Accepts a numeric value and draws the number graphically.
     *
     * @param g the graphics to paint to
     * @param valuesImg the image to clip from
     * @param numDigits the number of digits in the image     
     * @param index the index of the digit that needs to be drawn graphically
     * @param locationX the x co-ordinate of the location to start drawing 
     *                  the digits
     * @param locationY the y co-ordinate of the location to start drawing 
     *                  the digits
     * @param n the value as a percentage, equal to (value * 1000) / maxValue
     */
    void drawNumber(Graphics g, Image valuesImg, int numDigits, int index,
                    int locationX, int locationY, int n) 
    {
        /*
         * Find the first leftmost, non-zero digit.
         *
         * Keep using a smaller divisor to get to a non-zero
         * digit in n. If the divisor itself becomes zero,
         * this is the case where n = 0. We break out of the
         * loop, in that case.
         *
         * Note that the test for divisor == 0 is done before
         * extracting the next digit in the "while" part, to
         * avoid a divide-by-zero ArithmeticException.
         */
        
        int divisor = 1000;   // assume the max displayed is 4 digit num
        int digit = 0;
        int digitCount = 4;        
        
        int digitWidth = g.getFont().charWidth('9');
        if (valuesImg != null) {
            digitWidth = (int)(valuesImg.getWidth() / numDigits);
        }
                
        // keep whittling down the number until it's in range.
        while (n > 9999) n /= 10;

        // figure out how many digits... adjust divisor accordingly.
        while (divisor != 0 && (digit = n / divisor) == 0) {
            divisor /= 10;
            digitCount--;
        }

        // special case for 0
        if (digitCount == 0) digitCount = 1;

        // position where to start displaying the gauge value
        int start_x = locationX;

        // offset by a digit's width in case of shorter values
        // to make it more presentable
        start_x += (digitCount < 3) ? digitWidth : 0;
        
        // now show the digits
        switch (divisor) {
        case 1000:
            digit = n / 1000;
            n %= divisor;
            divisor /= 10;

            paintDigit(g, valuesImg, numDigits, digit, start_x, locationY);
            start_x += digitWidth;
            // fall through to handle next lower digit.

        case 100:
            digit = n / 100;
            n %= divisor;
            divisor /= 10;

            paintDigit(g, valuesImg, numDigits, digit, start_x, locationY);
            start_x += digitWidth;
            // fall through to handle next lower digit.

        case 10:
            digit = n / 10;
            n %= divisor;
            divisor /= 10;

            paintDigit(g, valuesImg, numDigits, digit, start_x, locationY);
            start_x += digitWidth;
            // fall through to handle next lower digit.

        case 1:
            digit = n;
            paintDigit(g, valuesImg, numDigits, digit, start_x, locationY);
            break;
            
        case 0:  // special case when n = 0
            // paint the non-highlight 0
            paintDigit(g, valuesImg, numDigits, numDigits - 1, 
                       start_x, locationY);
            break;
        default:
            Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                           "GaugeLFImpl: drawNumber," +
                           " divisor=" +divisor);
            break;
        }

        // ASSERT: Make sure this array gets filled with the x,y 
        // co-ordinates where the digits end, so as to facilitate 
        // drawing, say a percentage sign after the value, in some 
        // cases like the progressbar. Removing this notion will
        // cause *malfunction* of the drawPercentage() method, in
        // not being able to locate where to draw the percentage 
        // sign correctly.

        percentLoc[0] = start_x + digitWidth;
        percentLoc[1] = locationY;
    }
    
    /**
     * Calculates the position of the digit to be clipped from the values
     * image (based on the "digitLocation" parameter passed in) and 
     * paints it.
     *
     * @param g the graphics to paint t
     * @param valuesImg the image to clip from
     * @param numDigits the number of digits in the image
     * @param digitIndex the pixel location of this digit inside the 
     *                      values image, used to extract the digit images
     *                      from
     * @param x the x-coordinate where to draw this digit
     * @param y the y-coordinate where to draw this digit
     */
    void paintDigit(Graphics g, Image valuesImg, int numDigits,
                    int digitIndex, int x, int y) 
    {
        if (valuesImg != null) {
            int digitWidth = (int)(valuesImg.getWidth() / numDigits);
            
            g.drawRegion(valuesImg, 
                         digitIndex * digitWidth, 0, 
                         digitWidth,
                         valuesImg.getHeight(), 
                         Sprite.TRANS_NONE, x, y, 
                         Graphics.LEFT | Graphics.TOP);
        } else {
            if (digitIndex == numDigits - 1) {
                digitIndex = 0;
            }
            g.drawString("" + digitIndex, x, y, Graphics.LEFT | Graphics.TOP);
        }
    }

    /**
     * Draws a percentage sign.
     *
     * @param g the graphics to paint to
     * @param percentImg the image to clip from
     * @param value the value of the gauge
     */
    void drawPercentage(Graphics g, Image percentImg, int value) {

        if (percentImg != null && percentWidth == -1) {
            percentWidth = (int)(percentImg.getWidth() / 2);
            percentHeight = percentImg.getHeight();
        }
        
        if (percentImg != null) {
            // Note: The percentLoc[] array holds the x,y location
            // where the percentage sign should be painted. This is filled
            // by the drawNumber() method after it finishes drawing the
            // numeric value graphically.
    
            g.drawRegion(percentImg, 
                         (value == 0) ? percentWidth : 0, 0, 
                         percentWidth, percentHeight, 
                         Sprite.TRANS_NONE, 
                         percentLoc[0], percentLoc[1], 
                         Graphics.LEFT | Graphics.TOP);
        } else {
            g.drawString("%", percentLoc[0], percentLoc[1], 
                         Graphics.LEFT | Graphics.TOP);
        }
    }

    /**
     * A helper class to update a continuous running gauge.
     */
    class GaugeUpdateTask extends TimerTask {
        /**
         * the gauge to repaint
         */
        GaugeLFImpl myGaugeLF;

        /**
         * Construct a new GaugeUpdateTask.
         *
         * @param gaugeLF the gauge to repaint
         */
        GaugeUpdateTask(GaugeLF gaugeLF) {
            super();
            myGaugeLF = (GaugeLFImpl)gaugeLF;
        }

        /**
         * required method in TimerTask derivatives
         */
        public final void run() {
            // increment the frame counter of the continuous gauge
            // and remember to reset it when it hits the total number 
            // of frames, so it can start all over again
            myGaugeLF.lRequestPaint();
        }
    }

    /**
     * Start the GaugeUpdateTask running
     */
    void startGaugeUpdateTask() {
        if (updateHelper == null) {
            updateHelper = new GaugeUpdateTask(this);
            gaugeUpdateTimer.schedule(updateHelper, 100, 100);
        }
    }
    
    /**
     * Stop the GaugeUpdateTask from running.
     */
    void cancelGaugeUpdateTask() {
        if (updateHelper != null) {
            updateHelper.cancel();
            updateHelper = null;
        }
    }

    /** Gauge instance associated with this view */
    Gauge gauge;
        
    /** 
     * A Timer which will handle scheduling repaints of an 
     * indefinite range gauge when value == Gauge.CONTINUOUS_RUNNING
     */
    static Timer gaugeUpdateTimer; 

    /**
     * Constant representing the increment button. It is an arbitrary 
     * integer. The focusBtn attribute is set to this value if, during 
     * traversal, the focus is gained by the increment button and later
     * on used to check for focus highlight painting. 
     */
    static final int I_INC_BTN = 66;

    /**
     * Constant representing the decrement button. It is an arbitrary 
     * integer. The focusBtn attribute is set to this value if, during 
     * traversal, the focus is gained by the decrement button and later
     * on used to check for focus highlight painting. 
     */
    static final int I_DEC_BTN = 33;
    
    static {
        /*
         * The Timer to schedule update/repaint events with
         * in Gauge.CONTINUOUS_RUNNING mode.
         */
        gaugeUpdateTimer = new Timer();
        
    } // static 

    /** 
     * A TimerTask which will schedule repaints of an indefinite 
     * range gauge when value == Gauge.CONTINUOUS_RUNNING
     */
    GaugeUpdateTask updateHelper;
   
    /**
     * The cached value for the width of the graphical percent sign
     */
    int percentWidth = -1;
    
    /**
     * The cached value for the height of the graphical percent sign
     */
    int percentHeight = -1;
    
    /**
     * Used to remember internally the current button in focus 
     * (increment/decrement) when traversal happens and focus is gained
     * by an interactive gauge from another widget, or during internal
     * traversal, between increment and decrement buttons. The constant
     * values it can assume could be:
     *    I_INC_BTN: if focus is on increment button
     *    I_DEC_BTN: if focus is on decrement button (default initial value)
     * This is later used to check for focus highlight painting. 
     */
    int focusBtn = I_DEC_BTN;
    
    /**
     * An array storing the x,y co-ordinates of the location where the 
     * percentage sign should be displayed for a progressbar. 
     * This *MUST BE FILLED* inside the drawNumber() method,
     * so that the drawPercentage() method knows where to draw the
     * percentage sign correctly.
     */
    int[] percentLoc;

    /**
     * An index that keeps track of the next image that should be drawn
     * to animate a continuous running gauge, from an array of frames.
     */
    int nextFrame = 0;

    /**
     * A flag to mark when the call to a gauge's traverse method is the
     * initial traverse or not. True by default, 
     * (reset to true in traverseOut).
     */
    boolean initialTraverse = true;

    /**
     * A flag to indicate that internal traversal has occurred within
     * the gauge itself.
     */
    boolean intTraverse;
    
    /**
     * the area accepting the pointer event
     */ 
    private int pointerArea = INVALID_AREA;

    /** 
     * the different areas of an interactive gauge, used for pointer events
     */ 
    private static final int INVALID_AREA = -1;
    private static final int DEC_BTN_AREA = 0;
    private static final int INC_BTN_AREA = 1;
    private static final int METER_AREA = 2;
    
    
} // GaugeView
