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

Displayable.java

/*
 * @(#)Displayable.java	1.136 02/10/14 @(#)
 *
 * Copyright (c) 1999-2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package javax.microedition.lcdui;

import java.util.TimerTask;
import java.util.Timer;

import com.sun.midp.lcdui.Text;

/**
 * An object that has the capability of being placed on the display.  A 
 * <code>Displayable</code> object may have a title, a ticker,
 * zero or more commands and a listener associated with it.  The
 * contents displayed and their interaction with the user are defined by 
 * subclasses.
 *
 * <p>The title string may contain
 * <A HREF="Form.html#linebreak">line breaks</a>.
 * The display of the title string must break accordingly.
 * For example, if only a single line is available for a
 * title and the string contains a line break then only the characters
 * up to the line break are displayed.</p>
 *
 * <p>Unless otherwise specified by a subclass, the default state of newly 
 * created <code>Displayable</code> objects is as follows:</p>
 *
 * <ul>
 * <li>it is not visible on the <code>Display</code>;</li>
 * <li>there is no <code>Ticker</code> associated with this
 * <code>Displayable</code>;</li>
 * <li>the title is <code>null</code>;</li>
 * <li>there are no <code>Commands</code> present; and</li>
 * <li>there is no <code>CommandListener</code> present.</li>
 * </ul>
 *
 * @since MIDP 1.0
 */

abstract public class Displayable {

// ************************************************************
//  public member variables
// ************************************************************

// ************************************************************
//  protected member variables
// ************************************************************

// ************************************************************
//  package private member variables
// ************************************************************

    /** The current Display object */
    Display currentDisplay;

    /** An array of Commands added to this Displayable */
    Command commands[];

    /** The number of Commands added to this Displayable */
    int numCommands;

    /** The CommandListener for Commands added to this Displayable */
    CommandListener listener;

    /** Used as an index into the viewport[], for the x origin */
    final static int X      = 0;

    /** Used as an index into the viewport[], for the y origin */
    final static int Y      = 1;

    /** Used as an index into the viewport[], for the width */
    final static int WIDTH  = 2;

    /** Used as an index into the viewport[], for the height */
    final static int HEIGHT = 3;

    /**
     * The viewport coordinates.
     * Index 0: x origin coordinate (in the Display's coordinate space)
     * Index 1: y origin coordinate (in the DIsplay's coordinate space)
     * Index 2: width
     * Index 3: height
     */
    int[] viewport;

    /** True, if this Displayable is in full screen mode */
    boolean fullScreenMode;

    /**
     * True, indicates that before being painted, this Displayable should
     * be notified that its size has changed via callSizeChanged()
     */
    boolean sizeChangeOccurred;

    /**
     * In some circumstances (such as List), we need to delegate the
     * paint ownership. For example: List uses an internal Form to
     * do its rendering. If the Form requests a repaint, it will get
     * denied because the Form is not actually current - the List is.
     * So, we introduce the delegate, so the List can set the delegate
     * to itself, and its internal Form can schedule repaints.
     */
    Displayable paintDelegate;

// ************************************************************
//  private member variables
// ************************************************************

    /** Special title font */
    private final static  Font TITLE_FONT =
        Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM);

    /** Special title height */
    private final static int TITLE_HEIGHT = TITLE_FONT.getHeight() + 1;

    /** The title for this Displayable */
    private String title;

    /** The ticker that may be set for this Displayable */
    private Ticker ticker;

    /** A Timer which will handle firing repaints of the TickerPainter */
    private final static Timer tickerTimer;

    /** A TimerTask which will repaint the Ticker on a repeated basis */
    private TickerPainter tickerPainter;

    /** Convenience int to avoid garbage during repainting */
    private int tickerHeight;

    /** Convenience int to avoid garbage during repainting */
    private int totalHeight;

    /** The vertical scroll position */
    private int vScrollPosition     = 0;

    /** The vertical scroll proportion */
    private int vScrollProportion   = 100;


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

    static {
    	tickerTimer = new Timer();
    }

    /**
     * Create a new Displayable
     */
    Displayable() {
        setupViewport();
        translateViewport();
        paintDelegate = this;
    }

// ************************************************************
//  public methods
// ************************************************************

    /**
     * Gets the title of the <code>Displayable</code>. Returns
     * <code>null</code> if there is no title.
     * @return the title of the instance, or <code>null</code> if no title
     * @since MIDP 2.0
     * @see #setTitle
     */
    public String getTitle() {
        // SYNC NOTE: return of atomic value, no lock necessary
        return title;
    }

    /**
     * Sets the title of the <code>Displayable</code>. If
     * <code>null</code> is given,
     * removes the title. 
     *
     * <P>If the <code>Displayable</code> is actually visible on
     * the display,
     * the implementation should update 
     * the display as soon as it is feasible to do so.</P>
     * 
     * <P>The existence of a title  may affect the size
     * of the area available for <code>Displayable</code> content. 
     * Addition, removal, or the setting of the title text at runtime
     * may dynamically change the size of the content area.
     * This is most important to be aware of when using the
     * <code>Canvas</code> class.
     * If the available area does change, the application will be notified
     * via a call to {@link #sizeChanged(int, int) sizeChanged()}. </p>
     *
     * @param s the new title, or <code>null</code> for no title
     * @since MIDP 2.0
     * @see #getTitle
     */
    public void setTitle(String s) {
        synchronized (Display.LCDUILock) {
            setTitleImpl(s);
        }
    }

    /**
     * Gets the ticker used by this <code>Displayable</code>.
     * @return ticker object used, or <code>null</code> if no
     * ticker is present
     * @since MIDP 2.0
     * @see #setTicker
     */
    public Ticker getTicker() {
        // SYNC NOTE: return of atomic value, no locking necessary
        return ticker;
    }

    /**
     * Sets a ticker for use with this <code>Displayable</code>,
     * replacing any
     * previous ticker.
     * If <code>null</code>, removes the ticker object
     * from this <code>Displayable</code>. The same ticker may be shared by 
     * several <code>Displayable</code>
     * objects within an application. This is done by calling
     * <code>setTicker()</code>
     * with the same <code>Ticker</code> object on several
     * different <code>Displayable</code> objects.
     * If the <code>Displayable</code> is actually visible on the display,
     * the implementation should update 
     * the display as soon as it is feasible to do so.
     * 
     * <p>The existence of a ticker may affect the size
     * of the area available for <code>Displayable's</code> contents. 
     * Addition, removal, or the setting of the ticker at runtime
     * may dynamically change the size of the content area.
     * This is most important to be aware of when using the
     * <code>Canvas</code> class.
     * If the available area does change, the application will be notified
     * via a call to {@link #sizeChanged(int, int) sizeChanged()}. </p>
     *
     * @param ticker the ticker object used on this screen
     * @since MIDP 2.0
     * @see #getTicker
     */
    public void setTicker(Ticker ticker) {
        synchronized (Display.LCDUILock) {
            setTickerImpl(ticker);
        }
    }

    /**
     * Checks if the <code>Displayable</code> is actually visible
     * on the display.  In order
     * for a <code>Displayable</code> to be visible, all of the
     * following must be true:
     * the <code>Display's</code> <code>MIDlet</code> must be
     * running in the foreground, the <code>Displayable</code>
     * must be the <code>Display's</code> current screen, and the
     * <code>Displayable</code> must not be
     * obscured by a <a href="Display.html#systemscreens">
     * system screen</a>.
     *
     * @return <code>true</code> if the
     * <code>Displayable</code> is currently visible
     */
    public boolean isShown() {
        synchronized (Display.LCDUILock) {
            return (currentDisplay == null) ?
                false : currentDisplay.isShown(this);
        }
    }

    /**
     * Adds a command to the <code>Displayable</code>. The
     * implementation may choose,
     * for example,
     * to add the command to any of the available soft buttons or place it 
     * in a menu.
     * If the added command is already in the screen (tested by comparing the
     * object references), the method has no effect.
     * If the <code>Displayable</code> is actually visible on the
     * display, and this call
     * affects the set of visible commands, the implementation should update 
     * the display as soon as it is feasible to do so.
     * 
     * @param cmd the command to be added
     *
     * @throws NullPointerException if <code>cmd</code> is
     * <code>null</code>
     */
    public void addCommand(Command cmd) {
        if (cmd == null) {
            throw new NullPointerException();
        }

        synchronized (Display.LCDUILock) {
            addCommandImpl(cmd);
        }
    }

    /**
     * Removes a command from the <code>Displayable</code>.
     * If the command is not in the <code>Displayable</code>
     * (tested by comparing the
     * object references), the method has no effect.
     * If the <code>Displayable</code> is actually visible on the
     * display, and this call
     * affects the set of visible commands, the implementation should update 
     * the display as soon as it is feasible to do so.
     * If <code>cmd</code> is <code>null</code>, this method
     * does nothing.
     * 
     * @param cmd the command to be removed
     */
    public void removeCommand(Command cmd) {
        synchronized (Display.LCDUILock) {
            removeCommandImpl(cmd);
        }
    }

    /**
     * Sets a listener for {@link Command Commands} to this
     * <code>Displayable</code>,
     * replacing any previous <code>CommandListener</code>. A
     * <code>null</code> reference is
     * allowed and has the effect of removing any existing listener.
     *
     * @param l the new listener, or <code>null</code>.
     */
    public void setCommandListener(CommandListener l) {
        synchronized (Display.LCDUILock) {
            listener = l;
        }
    }

    /**
     * Gets the width in pixels of the displayable area available to the 
     * application.  The value returned is appropriate for the particular 
     * <code>Displayable</code> subclass.  This value may depend
     * on how the device uses the
     * display and may be affected by the presence of a title, a ticker, or 
     * commands.
     * This method returns the proper result at all times, even if the
     * <code>Displayable</code> object has not yet been shown.
     * 
     * @return width of the area available to the application
     * @since MIDP 2.0
     */
    public int getWidth() {
        // SYNC NOTE: return of atomic value
        return viewport[WIDTH];
    }

    /**
     * Gets the height in pixels of the displayable area available to the 
     * application.  The value returned is appropriate for the particular 
     * <code>Displayable</code> subclass.  This value may depend
     * on how the device uses the
     * display and may be affected by the presence of a title, a ticker, or 
     * commands.
     * This method returns the proper result at all times, even if the
     * <code>Displayable</code> object has not yet been shown.
     * 
     * @return height of the area available to the application
     * @since MIDP 2.0
     */
    public int getHeight() {
        // SYNC NOTE: return of atomic value
        return viewport[HEIGHT];
    }

// ************************************************************
//  protected methods
// ************************************************************

    /**
     * The implementation calls this method when the available area of the
     * <code>Displayable</code> has been changed. 
     * The "available area" is the area of the display that
     * may be occupied by
     * the application's contents, such as <code>Items</code> in a
     * <code>Form</code> or graphics within
     * a <code>Canvas</code>.  It does not include space occupied
     * by a title, a ticker,
     * command labels, scroll bars, system status area, etc.  A size change
     * can occur as a result of the addition, removal, or changed contents of 
     * any of these display features.
     *
     * <p> This method is called at least once before the
     * <code>Displayable</code> is shown for the first time.
     * If the size of a <code>Displayable</code> changes while
     * it is visible,
     * <CODE>sizeChanged</CODE> will be called.  If the size of a
     * <code>Displayable</code>
     * changes while it is <em>not</em> visible, calls to
     * <CODE>sizeChanged</CODE> may be deferred.  If the size had changed
     * while the <code>Displayable</code> was not visible,
     * <CODE>sizeChanged</CODE> will be
     * called at least once at the time the
     * <code>Displayable</code> becomes visible once
     * again.</p>
     *
     * <p>The default implementation of this method in <code>Displayable</code>
     * and its
     * subclasses defined in this specification must be empty.
     * This method is intended solely for being overridden by the
     * application. This method is defined on <code>Displayable</code>
     * even though applications are prohibited from creating 
     * direct subclasses of <code>Displayable</code>.
     * It is defined here so that applications can override it in
     * subclasses of <code>Canvas</code> and <code>Form</code>.
     * This is useful for <code>Canvas</code> subclasses to tailor
     * their graphics and for <code>Forms</code> to modify
     * <code>Item</code> sizes and layout
     * directives in order to fit their contents within the the available
     * display area.</p>
     * 
     * @param w the new width in pixels of the available area
     * @param h the new height in pixels of the available area
     * @since MIDP 2.0
     */ 
    protected void sizeChanged(int w, int h) {
	// this method is intended to be overridden by the application
    }

// ************************************************************
//  package private methods
// ************************************************************

    /**
     * Called to commit any pending user interaction
     */
    void commitPendingInteraction() { }

    /**
     * Called to schedule an "invalidate" for this Displayable. Invalidation
     * is caused by things like size changes, content changes, or spontaneous
     * traversal within the Item
     *
     * @param src The Item who is causing the invalidation. If NULL,
     *            all contents should be considered invalid.
     */
    void invalidate(Item src) {
        Display d = currentDisplay;
        if (d != null) {
            d.invalidate(src);
        }
    }

    /**
     * Called by the event handler to perform an
     * invalidation of this Displayable
     *
     * @param src The Item who is causing the invalidation. If NULL,
     *            all contents should be considered invalid.
     */
    void callInvalidate(Item src) {
    }

    /**
     * Called to schedule a call to itemStateChanged() due to
     * a change in the given Item.
     *
     * @param src the Item which has changed
     */
    void itemStateChanged(Item src) {
        Display d = currentDisplay;
        if (d != null) {
            d.itemStateChanged(src);
        }
    }

    /**
     * Called by the event handler to notify any ItemStateListener
     * of a change in the given Item
     *
     * @param src The Item which has changed
     */
    void callItemStateChanged(Item src) {
    }

    /**
     * Set the ticker for this Displayable
     *
     * @param t the ticker to set
     */
    void setTickerImpl(Ticker t) {
        // Return early if there's nothing to do
        if (this.ticker == t) {
            return;
        }

        Ticker oldTicker = this.ticker;
        this.ticker = t;

        // CASES:
        // 1. Had an invisible non-null ticker, setting a null ticker
        //    - We need to set the new ticker. There's no need to re-layout
        //      or start the new ticker
        // 2. Had an invisible non-null ticker, setting a non-null ticker
        //    - We need to set the new ticker. There's no need to re-layout
        //      or start the new ticker
        // 3. Had a visible non-null ticker, setting a null ticker
        //    - We need to set the new ticker and re-layout. There's no
        //      need to start the new ticker.
        // 4. Had a null ticker, setting a visible non-null ticker
        //    - We need to set the new ticker, re-layout, and
        //      start up the new ticker
        // 5. Had a visible non-null ticker, setting a non-null ticker
        //    - We need to set the new ticker. There's no need to re-layout

        boolean sizeChange =
            ((oldTicker != null) && (ticker == null)) ||
            ((oldTicker == null) && (ticker != null));

        if (sizeChange) {
            if (ticker != null) {
                ticker.reset();
                startTicker();
            } else {
                stopTicker();
            }
            layout();
            callSizeChanged(viewport[WIDTH], viewport[HEIGHT]);
            callRepaint();
        } else {
            ticker.reset();
        }
    }

    /**
     * Package private unsynchronized version of setTitle(String)
     *
     * @param s Title to set on this Displayable.
     */
    void setTitleImpl(String s) {
        if (title == s || (title != null && title.equals(s))) {
            return;
        }

        String oldTitle = this.title;
        this.title = s;

        if (fullScreenMode) {
            return;
        }

        boolean sizeChange =
            ((oldTitle != null) && (title == null)) ||
            ((oldTitle == null) && (title != null));

        if (sizeChange) {
            layout();
            callSizeChanged(viewport[WIDTH], viewport[HEIGHT]);
            callRepaint();
        } else {
            repaintTitle();
        }
    }

    /**
     * Package private equivalent of sizeChanged()
     *
     * @param w the new width
     * @param h the new height
     *
     */
    void callSizeChanged(int w, int h) {
        // If there is no Display, or if this Displayable is not
        // currently visible, we simply record the fact that the
        // size has changed
        sizeChangeOccurred =
            (currentDisplay == null) || (!currentDisplay.isShown(this));
    }

    /**
     * Display calls this method on it's current Displayable.
     * Displayable uses this oppportunity to do necessary stuff
     * on the Graphics context, this includes,
     * paint Ticker, paint Title, translate as necessary.
     *
     * <p>The target Object of this repaint may be some Object
     * initially set by this Displayable when the repaint was
     * requested - allowing this Displayable to know exactly
     * which Object it needs to call to service this repaint,
     * rather than potentially querying all of its Objects to
     * determine the one(s) which need painting.
     *
     * SYNC NOTE: The caller of this method handles synchronization.
     *
     * @param g the graphics context to paint into.
     * @param target the target Object of this repaint
     */
    void callPaint(Graphics g, Object target) {
        /*
        System.err.println("Displayable:Clip: " +
            g.getClipX() + "," + g.getClipY() + "," +
            g.getClipWidth() + "," + g.getClipHeight());
        */

        synchronized (Display.LCDUILock) {

            if (!(fullScreenMode || (title == null && ticker == null))) {

                if (g.getClipY() < totalHeight) {
                    // We always paint "something" for the ticker, rather
                    // than make the title paint more complicated. If there
                    // is no ticker, we draw the darkgray/white saparator line
                    if (g.getClipY() < tickerHeight) {
                        paintTicker(g);
                    }

                    if (title != null) {
                        if (g.getClipY() + g.getClipHeight() >
                                totalHeight - tickerHeight + 1) {

                            g.translate(0, tickerHeight);
                            paintTitle(g);
                            g.translate(0, -tickerHeight);
                        }
                    }
                }
            } else {
                g.setColor(Item.DARK_GRAY_COLOR);
                g.drawLine(0, 0, Display.WIDTH, 0);
                g.setColor(Display.FG_COLOR);
            }


        } // synchronized
    }

    /**
     * Paint the ticker if it exists, on this graphics object.
     *
     * @param g The Graphics object to paint this ticker on.
     */
    void paintTicker(Graphics g) {
        // paint the ticker here.
        if (ticker != null) {
            ticker.paintContent(g);
        } else if (title != null) {
            g.setColor(Item.DARK_GRAY_COLOR);
            g.drawLine(0, 0, Display.WIDTH, 0);
            g.setColor(Display.ERASE_COLOR);
            g.drawLine(0, 1, Display.WIDTH, 1);
            g.setColor(Display.FG_COLOR);
        }
    }

    /**
     * Paints the title of this Displayable, including it's border.
     * The graphics context is then translated by the height occupied
     * by the title area.
     *
     * @param g The graphics object to paint this title on.
     *
     */
    void paintTitle(Graphics g) {

        g.setColor(Item.LIGHT_GRAY_COLOR);
        g.fillRect(0, 0, Display.WIDTH, TITLE_HEIGHT - 1);
        g.setColor(Display.FG_COLOR);

        Text.paint(title, TITLE_FONT, g, Display.WIDTH,
                   TITLE_HEIGHT, 1, Text.NORMAL, null);

        g.setColor(Item.DARK_GRAY_COLOR);
        g.drawLine(0, TITLE_HEIGHT - 1, Display.WIDTH, TITLE_HEIGHT - 1);
        g.setColor(Display.FG_COLOR);
    }

    /**
     * Perform any necessary layout, and update the viewport
     * as necessary
     */
    void layout() {
        setupViewport();
        translateViewport();
    }

    /**
     * Set the full screen mode of this Displayable. If true,
     * this Displayable will take up as much screen real estate
     * as possible
     *
     * @param onOff true if full screen mode should be turned on
     */
    void fullScreenMode(boolean onOff) {
        if (fullScreenMode == onOff) {
            return;
        }

        fullScreenMode = onOff;

        layout();
        updateCommandSet();
        callSizeChanged(viewport[WIDTH], viewport[HEIGHT]);

        callRepaint();

        if (fullScreenMode) {
            stopTicker();
        } else {
            startTicker();
        }
    }

    /**
     * By default, the viewport array is configured to be
     * at origin 0,0 with width Display.WIDTH and height
     * either Display.HEIGHT or Display.ADORNEDHEIGHT, depending
     * on full screen mode.
     */
    private void setupViewport() {
        // setup the default viewport, the size of the Display
        if (viewport == null) {
            viewport = new int[4];
        }

        viewport[X] = 
        viewport[Y] = 0;

        viewport[WIDTH]  = Display.WIDTH;
        viewport[HEIGHT] = (fullScreenMode) 
                         ? Display.HEIGHT 
                         : Display.ADORNEDHEIGHT;
    }

    /**
     * Translate the viewport for any decorations by this Displayable
     * such as a title or ticker
     */
    private void translateViewport() {

        if (!(fullScreenMode || (title == null && ticker == null))) {

            //
            // determine the right tickerHeight
            //
            if (ticker != null) {
                tickerHeight = Ticker.PREFERRED_HEIGHT;
            } else {
                if (title != null) {
                    tickerHeight = 2;
                } else {
                    tickerHeight = 0;
                }
            }
 
            //
            // add to any title height
            //
            totalHeight = (title != null)
                        ? TITLE_HEIGHT + tickerHeight
                        : tickerHeight;
        } else {
            //
            // in fullscreen, or with no title or ticker we have
            // a single dark line under the status bar
            //
            totalHeight = 1;
        }

        viewport[Y] += totalHeight;
        viewport[HEIGHT] -= totalHeight;
    }

    /**
     * Handle a key press
     *
     * @param keyCode The key that was pressed
     */
    void callKeyPressed(int keyCode) { }
    /**
     * Handle a repeated key press
     *
     * @param keyCode The key that was pressed
     */
    void callKeyRepeated(int keyCode) { }
    /**
     * Handle a key release
     *
     * @param keyCode The key that was released
     */
    void callKeyReleased(int keyCode) { }
    /**
     * Handle a key that was typed from the keyboard
     *
     * @param c The char that was typed
     */
    void callKeyTyped(char c) {}
    /**
     * Handle a pointer press event
     *
     * @param x The x coordinate of the press
     * @param y The y coordinate of the press
     */
    void callPointerPressed(int x, int y) { }
    /**
     * Handle a pointer drag event
     *
     * @param x The x coordinate of the drag
     * @param y The y coordinate of the drag
     */
    void callPointerDragged(int x, int y) { }
    /**
     * Handle a pointer release event
     *
     * @param x The x coordinate of the release
     * @param y The y coordinate of the release
     */
    void callPointerReleased(int x, int y) { }

    /**
     * Repaint this Displayable
     *
     * @param x The x coordinate of the region to repaint
     * @param y The y coordinate of the region to repaint
     * @param width The width of the region to repaint
     * @param height The height of the region to repaint
     * @param target an optional paint target to receive the paint request
     *               when it returns via callPaint()
     */
    final void callRepaint(int x, int y, int width, int height, Object target) {
        if (currentDisplay != null) {
            // Note: Display will not let anyone but the current
            // Displayable schedule repaints
            currentDisplay.repaintImpl(paintDelegate, x, y, width, height,
                                       target);
        }
    }

    /**
     * Repaints this Displayable. 
     * This is the same as calling 
     * callRepaint(0, 0, 
     *     viewport[X] + viewport[WIDTH], 
     *     viewport[Y] + viewport[HEIGHT], null)
     */
    final void callRepaint() {
        callRepaint(0, 0, 
                    viewport[X] + viewport[WIDTH],
                    viewport[Y] + viewport[HEIGHT], null);
    }

    /**
     * Repaint the viewport region of this Displayable
     */
    final void repaintContents() {
        callRepaint(viewport[X], viewport[Y],
            viewport[WIDTH], viewport[HEIGHT], null);
    }

    /**
     * Set the vertical scroll position and proportion
     *
     * @param scrollPosition The vertical scroll position to set on a
     *                       scale of 0-100
     * @param scrollProportion The vertical scroll proportion to set on
     *                         a scale of 0-100. For example, if the viewport
     *                         is 25 pixels high and the Displayable is 100
     *                         pixels high, then the scroll proportion would
     *                         be 25, since only 25% of the Displayable can
     *                         be viewed at any one time. This proportion
     *                         value can be used by implementations which
     *                         render scrollbars to indicate scrollability
     *                         to the user.
     */
    void setVerticalScroll(int scrollPosition, int scrollProportion) {
        synchronized (Display.LCDUILock) {
            this.vScrollPosition = scrollPosition;
            this.vScrollProportion = scrollProportion;

            if (currentDisplay != null) {
                currentDisplay.setVerticalScroll(scrollPosition,
                                                 scrollProportion);
            }
        }
    }

    /**
     * Get the current vertical scroll position
     *
     * @return int The vertical scroll position on a scale of 0-100
     */
    int getVerticalScrollPosition() {
        // SYNC NOTE: return of atomic value
        return vScrollPosition;
    }

    /**
     * Get the current vertical scroll proportion
     *
     * @return ing The vertical scroll proportion on a scale of 0-100
     */
    int getVerticalScrollProportion() {
        // SYNC NOTE: return of atomic value
        return vScrollProportion;
    }

    /**
     * Notify this Displayable it is being shown on the given Display
     *
     * @param d the Display showing this Displayable
     */
    void callShowNotify(Display d) {
        synchronized (Display.LCDUILock) {
            currentDisplay = d;

            // call grab full screen to let the native
            // layer know whether to be in 
            // fullscreen or normal mode.
            grabFullScreen(fullScreenMode);

            if (sizeChangeOccurred) {
                callSizeChanged(viewport[WIDTH], viewport[HEIGHT]);
            }
            // display the ticker if we have a visible one.
            startTicker();
        }
    }

    /**
     * Notify this Displayable it is being hidden on the given Display
     *
     * @param d the Display hiding this Displayable
     */
    void callHideNotify(Display d) {
        synchronized (Display.LCDUILock) {
            currentDisplay = null;
            stopTicker();
        }
    }

    /**
     * Get the set of Commands that have been added to this Displayable
     *
     * @return Command[] The array of Commands added to this Displayable
     */
    Command[] getCommands() {
        return commands;
    }

    /**
     * Get the number of commands that have been added to this Displayable
     *
     * @return int The number of commands that have been added to this
     *              Displayable
     */
    int getCommandCount() {
        return numCommands;
    }

    /**
     * Gets item currently in focus. This is will be only applicable to
     * Form. The rest of the subclasses will return null.
     * @return Item The item currently in focus in this Displayable;
     *          if there are no items in focus, null is returned
     */
    Item getCurrentItem() {
        return null;
    }

    /**
     * Get the CommandListener for this Displayable
     *
     * @return CommandListener The CommandListener listening to Commands on
     *                          this Displayable
     */
    CommandListener getCommandListener() {
        return listener;
    }

    /**
     * Add a Command to this Displayable
     *
     * @param cmd The Command to add to this Displayable
     */
    void addCommandImpl(Command cmd) {
        for (int i = 0; i < numCommands; ++i) {
            if (commands[i] == cmd) {
                return;
            }
        }

        if ((commands == null) || (numCommands == commands.length)) {
            Command[] newCommands = new Command[numCommands + 4];
            if (commands != null) {
                System.arraycopy(commands, 0, newCommands, 0, numCommands);
            }
            commands = newCommands;
        }

        commands[numCommands] = cmd;
        ++numCommands;
        updateCommandSet();
    }

    /**
     * Remove a Command from this Displayable
     *
     * @param cmd The Command to remove from this Displayable
     */
    void removeCommandImpl(Command cmd) {
        for (int i = 0; i < numCommands; ++i) {
            if (commands[i] == cmd) {
                commands[i] = commands[--numCommands];
                commands[numCommands] = null;
                updateCommandSet();
                break;
            }
        }
    }

    /**
     * Updates command set if this Displayable is visible
     */
    void updateCommandSet() {
        // SYNC NOTE: Display requires calls to updateCommandSet to
        // be synchronized
        synchronized (Display.LCDUILock) {
            if ((currentDisplay != null) && currentDisplay.isShown(this)) {
                currentDisplay.updateCommandSet();
            }
        }
    }

    /**
     * Decide if the given Command has been added to this
     * Displayable's set of abstract commands.
     *
     * @param command The Command to check. This value should
     *                never be null (no checks are made).
     * @return True if the Command has been previously added
     *         via the addCommand() method
     */
    boolean commandInSetImpl(Command command) {
        for (int i = 0; i < numCommands; i++) {
            if (commands[i] == command) {
                return true;
            }
        }
        return false;
    }

// ************************************************************
//  private methods
// ************************************************************

    /**
     * Repaint the title area.
     */
    private void repaintTitle() {
        if (currentDisplay != null) {
            currentDisplay.repaintImpl(paintDelegate, 0,
                (ticker != null) 
                    ? Ticker.PREFERRED_HEIGHT 
                    : 2,
                viewport[WIDTH], TITLE_HEIGHT, title);
	    }
    }

    /**
     * Starts the "ticking" of the ticker.
     *
     */
    private void startTicker() {
        if (ticker == null || fullScreenMode) {
            return;
        }

        stopTicker();
        tickerPainter = new TickerPainter();
        tickerTimer.schedule(tickerPainter, 0, Ticker.TICK_RATE);
    }

    /**
     * Stop the ticking of the ticker.
     *
     */
    private void stopTicker() {
        if (tickerPainter == null) {
            return;
        }
        tickerPainter.cancel();
        tickerPainter = null;
    }

    /**
     * Paints the ticker's text area.
     *
     */
    private void repaintTickerText() {

        if (currentDisplay != null &&
                currentDisplay.isShown(paintDelegate)) {

            currentDisplay.repaintImpl(paintDelegate, 0,
                                       Ticker.DECORATION_HEIGHT,
                                       viewport[WIDTH], Screen.CONTENT_HEIGHT,
                                       ticker);
        }
    }

    /**
     * This method is called from showNotify(Display )
     * It checks if a size change has occurred since the last time
     * a size change occurred or since the object was constructed.
     *
     * If a size change has occurred, it calls callSizeChanged()
     * for package private handling of size change events, and returns true,
     * else returns false.
     * 
     * @return true  if a size change has occurred since the last size change
     *               or since the construction of this Displayable.
     *         false otherwise
     *
     */
    private boolean sizeChangeImpl() {
        boolean flag = sizeChangeOccurred;
        sizeChangeOccurred = false;

        if (flag) {
            callSizeChanged(viewport[WIDTH], viewport[HEIGHT]);
        }

        return flag;
    }

    /**
     * Grabs the requested area of the display
     *
     * @param mode if true, grabs the entire display area for Canvas'
     *             display
     *             if false, occupies only part of the entire area
     *             leaving space for the status and the command labels.
     */
    native void grabFullScreen(boolean mode);
    

// ************************************************************
//  Inner Class, TickerPainter
// ************************************************************

    /**
     * A special helper class to repaint the Ticker
     * if one has been set
     */
    private class TickerPainter extends TimerTask {
        /**
         * Repaint the ticker area of this Screen
         */
        public final void run() {
            synchronized (Display.LCDUILock) {
                repaintTickerText();
            }
        }
    }

} // Displayable