FileDocCategorySizeDatePackage
StringItem.javaAPI DocJ2ME MIDP 2.019441Thu Nov 07 12:02:28 GMT 2002javax.microedition.lcdui

StringItem.java

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

package javax.microedition.lcdui;

import com.sun.midp.lcdui.Text;

/**
 * An item that can contain a string. A <code>StringItem</code> is
 * display-only; the user
 * cannot edit the contents. Both the label and the textual content of a
 * <code>StringItem</code> may be modified by the application. The
 * visual representation
 * of the label may differ from that of the textual contents.
 */
public class StringItem extends Item {

    /**
     * Creates a new <code>StringItem</code> object.  Calling this
     * constructor is equivalent to calling
     * 
     * <TABLE BORDER="2">
     * <TR>
     * <TD ROWSPAN="1" COLSPAN="1">
     *    <pre><code>
     *     StringItem(label, text, PLAIN);     </code></pre>
     * </TD>
     * </TR>
     * </TABLE>
     * @param label the <code>Item</code> label
     * @param text the text contents
     * @see #StringItem(String, String, int)
     */
    public StringItem(String label, String text) {
        this(label, text, Item.PLAIN);
    }

    /**
     * Creates a new <code>StringItem</code> object with the given label,
     * textual content, and appearance mode.
     * Either label or text may be present or <code>null</code>.
     *
     * <p>The <code>appearanceMode</code> parameter
     * (see <a href="Item.html#appearance">Appearance Modes</a>)
     * is a hint to the platform of the application's intended use
     * for this <code>StringItem</code>.  To provide hyperlink- or
     * button-like behavior,
     * the application should associate a default <code>Command</code> with this
     * <code>StringItem</code> and add an
     * <code>ItemCommandListener</code> to this
     * <code>StringItem</code>.
     * 
     * <p>Here is an example showing the use of a
     * <code>StringItem</code> as a button: </p>
     * <TABLE BORDER="2">
     * <TR>
     * <TD ROWSPAN="1" COLSPAN="1">
     *    <pre><code>
     *     StringItem strItem = 
     *         new StringItem("Default: ", "Set",     
     *                        Item.BUTTON);    
     *     strItem.setDefaultCommand(
     *         new Command("Set", Command.ITEM, 1);    
     *     // icl is ItemCommandListener 
     *     strItem.setItemCommandListener(icl);     </code></pre>
     * </TD>
     * </TR>
     * </TABLE>
     * @param label the <code>StringItem's</code> label, or <code>null</code>
     * if no label
     * @param text the <code>StringItem's</code> text contents, or
     * <code>null</code> if the contents are initially empty
     * @param appearanceMode the appearance mode of the <code>StringItem</code>,
     * one of {@link #PLAIN}, {@link #HYPERLINK}, or {@link #BUTTON}
     * @throws IllegalArgumentException if <code>appearanceMode</code> invalid
     * 
     * @since MIDP 2.0
     */
    public StringItem(java.lang.String label,
                      java.lang.String text,
                      int appearanceMode) {
        super(label);

        synchronized (Display.LCDUILock) {
            switch (appearanceMode) {
                case Item.PLAIN:
                case Item.HYPERLINK:
                case Item.BUTTON:
                    this.appearanceMode = appearanceMode;
                    break;
                default:
                    throw new IllegalArgumentException();
            }

            this.str = text;
            this.font = Screen.CONTENT_FONT;

            int labelFontHeight = LABEL_FONT.getHeight();
            minimumLineHeight = Screen.CONTENT_HEIGHT;

            if (minimumLineHeight < labelFontHeight) {
                minimumLineHeight = labelFontHeight;
            }

            checkTraverse();
        }
    }

    /**
     * Gets the text contents of the <code>StringItem</code>, or
     * <code>null</code> if the <code>StringItem</code> is
     * empty.
     * @return a string with the content of the item
     * @see #setText
     */
    public String getText() {
        // SYNC NOTE: return of atomic value, no locking necessary
        return str;
    }

    /**
     * Sets the text contents of the <code>StringItem</code>. If text
     * is <code>null</code>,
     * the <code>StringItem</code>
     * is set to be empty.
     * @param text the new content
     * @see #getText
     */
    public void setText(String text) {
        synchronized (Display.LCDUILock) {
            this.str = text;
            checkTraverse();
            invalidate();
        }
    }

    /** 
     * Returns the appearance mode of the <code>StringItem</code>.
     * See <a href="Item.html#appearance">Appearance Modes</a>.
     *
     * @return the appearance mode value,
     * one of {@link #PLAIN}, {@link #HYPERLINK}, or {@link #BUTTON}
     * 
     * @since MIDP 2.0
     */
    public int getAppearanceMode() {
        // SYNC NOTE: return of atomic value, no locking necessary
        return appearanceMode;
    }

    /**
     * Sets the application's preferred font for
     * rendering this <code>StringItem</code>.
     * The font is a hint, and the implementation may disregard
     * the application's preferred font.
     *
     * <p> The <code>font</code> parameter must be a valid <code>Font</code>
     * object or <code>null</code>. If the <code>font</code> parameter is
     * <code>null</code>, the implementation must use its default font
     * to render the <code>StringItem</code>.</p>
     *
     * @param font the preferred font to use to render this
     *             <code>StringItem</code>
     * @see #getFont
     * @since MIDP 2.0
     */
    public void setFont(Font font) {
        this.font = font;
    }

    /**
     * Gets the application's preferred font for
     * rendering this <code>StringItem</code>. The
     * value returned is the font that had been set by the application,
     * even if that value had been disregarded by the implementation.
     * If no font had been set by the application, or if the application
     * explicitly set the font to <code>null</code>, the value is the default
     * font chosen by the implementation.
     *
     * @return the preferred font to use to render this
     *         <code>StringItem</code>
     * @see #setFont
     * @since MIDP 2.0
     */
    public Font getFont() {
        // SYNC NOTE: return of atomic value, no locking necessary
            return font;
    }

    /**
     * Sets the preferred width and height for this <code>Item</code>.
     * Values for width and height less than <code>-1</code> are illegal.
     * If the width is between zero and the minimum width, inclusive,
     * the minimum width is used instead.
     * If the height is between zero and the minimum height, inclusive,
     * the minimum height is used instead.
     *
     * <p>Supplying a width or height value greater than the minimum width or
     * height <em>locks</em> that dimension to the supplied
     * value.  The implementation may silently enforce a maximum dimension for
     * an <code>Item</code> based on factors such as the screen size.
     * Supplying a value of
     * <code>-1</code> for the width or height unlocks that dimension.
     * See <a href="#sizes">Item Sizes</a> for a complete discussion.</p>
     *
     * <p>It is illegal to call this method if this <code>Item</code>
     * is contained within  an <code>Alert</code>.</p>
     *
     * @param width the value to which the width should be locked, or
     * <code>-1</code> to unlock
     * @param height the value to which the height should be locked, or
     * <code>-1</code> to unlock
     * @throws IllegalArgumentException if width or height is less than
     * <code>-1</code>
     * @throws IllegalStateException if this <code>Item</code> is contained
     * within an <code>Alert</code>
     * @see #getPreferredHeight
     * @see #getPreferredWidth
     * @since MIDP 2.0
     */
    public void setPreferredSize(int width, int height) {
        super.setPreferredSize(width, height);
    }

    // package private

    /**
     * Get the minimum width of this Item
     *
     * @return the minimum width
     */
    int callMinimumWidth() {
        int pW = callPreferredWidth(-1);

        // FIX ME: paint() does not know to paint in a width which is less
        // than preferred
        int w = font.charWidth('W') * 8 + 6 * font.charWidth('.');
        if (w > pW) {
            w = pW;
        }

        if (isButton()) {
            w += 2 * (BUTTON_BORDER + BUTTON_PAD);
        }
        return w;
    }

    /**
     * Get the preferred width of this Item
     *
     * @param h the tentative content height in pixels, or -1 if a
     * tentative height has not been computed
     * @return the preferred width
     */
    int callPreferredWidth(int h) {

        // FIX ME: we ignore the 'h' value and just return
        // a basic width based on our contents. That is, this
        // StringItem's preferred width is always based on
        // being one line high
        
        if (isButton()) {
            int buttonPad = 2 * (BUTTON_BORDER + BUTTON_PAD);
            int prefW     = Text.getTwoStringsWidth(label, str, LABEL_FONT, 
                                                    font, 
                                                    DEFAULT_WIDTH - buttonPad,
                                                    LABEL_PAD);
            return (prefW > 0 ? prefW + buttonPad : 0);
        }

        return Text.getTwoStringsWidth(label, str, LABEL_FONT, font, 
                                       DEFAULT_WIDTH, LABEL_PAD);
    }

    /**
     * Get the minimum height of this Item
     *
     * @return the minimum height
     */
    int callMinimumHeight() {
        // IF we decide to have minHeight as one line
        // we need to change the way painting is done 
        // (to divide allocated space between label and string)
        return callPreferredHeight(-1);
    }

    /**
     * Get the preferred height of this Item
     *
     * @param w the tentative content width in pixels, or -1 if a
     * tentative width has not been computed
     * @return the preferred height
     */
    int callPreferredHeight(int w) {
        if (w == -1) {
            w = DEFAULT_WIDTH;
        }

        if (isButton()) {
            int buttonPad = 2 * (BUTTON_BORDER + BUTTON_PAD);
            int prefH     = Text.getTwoStringsHeight(label, str, 
                                                     LABEL_FONT, font,
                                                     w - buttonPad, 
                                                     LABEL_PAD);
            return (prefH > 0 ? prefH + buttonPad : 0);
        }

        return Text.getTwoStringsHeight(label, str, LABEL_FONT, font,
                                        w, LABEL_PAD);
    }

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

        // If label starts with a\n,
        // put this StringItem on a newline no matter what
        if (label != null && label.length() > 0) {
            if (label.charAt(0) == '\n') {
                return true;
            }
        } else if (str != null && str.length() > 0) {
            // If there is no label and our content starts with a \n,
            // this StringItem starts on a newline
            if (str.charAt(0) == '\n') {
                return true;
            }
        } else {
            // empty StringItem
            return false;
        }

        if ((layout & Item.LAYOUT_2) == Item.LAYOUT_2) {
            return ((layout & Item.LAYOUT_NEWLINE_BEFORE)
                            == Item.LAYOUT_NEWLINE_BEFORE);
        }
            
        // in MIDP1.0 new any StringItem with a non-null label would
        // go on a new line
        return label != null && label.length() > 0;
    }

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

        // If content ends with a \n,
        // there is a newline after this StringItem no matter what
        if (str != null && str.length() > 0) {
            if (str.charAt(str.length() - 1) == '\n') {
                return true;
            }
        } else if (label != null && label.length() > 0) {
            // If there is no content and our label ends with a \n, 
            // there is a newline after this StringItem
            if (label.charAt(label.length() - 1) == '\n') {
                return true;
            }
        } else {
            // empty StringItem
            return false;
        }

        if ((layout & Item.LAYOUT_2) == Item.LAYOUT_2) {
            return ((layout & Item.LAYOUT_NEWLINE_AFTER)
                            == Item.LAYOUT_NEWLINE_AFTER);
        }
        return false;
    }

    /**
     * Determine if Form should not traverse to this StringItem
     *
     * @return true if Form should not traverse to this StringItem
     */
    boolean shouldSkipTraverse() {
        if ((label == null || label.length() == 0) && 
            (str == null || str.length() == 0)) {
            return true;
        }

        return skipTraverse;
    }

    /**
     * Paint this StringItem
     *
     * @param g the Graphics object to paint to
     * @param width the width of this item
     * @param height the height of this item
     */
    void callPaint(Graphics g, int width, int height) {

        int w = width;
        int h = height;
        
        int translateY = 0;
        int translateX = 0;

        if (isButton()) {
            translateX = translateY = BUTTON_BORDER + BUTTON_PAD;
            w -= 2*translateX;
            h -= 2*translateY;
            g.translate(translateX, translateY);
        }

        Font lFont = (LABEL_BOLD_ON_TRAVERSE && !hasFocus) ?
                     Screen.CONTENT_FONT : LABEL_FONT;

        int labelHeight = getLabelHeight(w);

        int offset = Text.paint(label, lFont, 
                                g, w, labelHeight, 0, Text.NORMAL, null);
        if (offset > 0) {
            offset += LABEL_PAD;
        }

        int mode = Text.NORMAL;
        if (numCommands > 0 && appearanceMode == Item.HYPERLINK) { 
            mode = hasFocus ? (Text.INVERT | Text.HYPERLINK) :
                              (Text.NORMAL | Text.HYPERLINK);
        }

        int yOffset = labelHeight;
        if (labelHeight > 0) {
            yOffset -= (lFont.getHeight() < font.getHeight() ?
                        lFont.getHeight() : font.getHeight());
        }

        g.translate(0, yOffset);
        translateY += yOffset;

        Text.paint(str, font, g, w, h - yOffset, offset, mode, null);

        g.translate(-translateX, -translateY);

        if (isButton()) {
            drawButtonBorder(g, 0, 0, width, height, hasFocus);
        }
    }

    /**
     * Called by the system to signal a key press
     *
     * @param keyCode the key code of the key that has been pressed
     * @see #getInteractionModes
     */
    void callKeyPressed(int keyCode) {
        if (keyCode != Display.KEYCODE_SELECT) {
            return;
        }

        // StringItem takes focus only if there are one or more Item Commands
        // attached to it
        if (!(getCommandCount() > 0) || commandListener == null) {
            return;
        }

        ItemCommandListener cl = null;
        Command defaultCmd = null;

        synchronized (Display.LCDUILock) {
            cl = commandListener;
            defaultCmd = defaultCommand;
        } // synchronized

        // SYNC NOTE: The call to the listener must occur outside
        // of the lock
        if (cl != null) {
            try {
                // SYNC NOTE: We lock on calloutLock around any calls
                // into application code
                synchronized (Display.calloutLock) {
                    if (defaultCmd != null) {
                        cl.commandAction(defaultCmd, this);
                    } else {
                        // REMINDER : Needs HI decision
                        // either call the first command
                        // from  the command list or
                        // invoke the menu
                    }
                }
            } catch (Throwable thr) {
                Display.handleThrowable(thr);
            }
        }
    }

    /**
     *  Adds a context sensitive Command to the item.
     * @param cmd the command to be removed
     */
    void addCommandImpl(Command cmd) {
        synchronized (Display.LCDUILock) {
            // The super class will update the command set of the Form
            super.addCommandImpl(cmd);

            if ((numCommands >= 1) && (appearanceMode == Item.PLAIN)) {
                // restore the value of the original appearanceMode
                // if it is a button
                // otherwise simple change the appearanceMode
                // to hyperlink
                this.appearanceMode =
                    originalAppearanceMode == Item.BUTTON ?
                    Item.BUTTON : Item.HYPERLINK;
            }

            checkTraverse();
            invalidate();
        } // synchronized
    }

    /**
     *  Removes the context sensitive command from item.
     * @param cmd the command to be removed
     */
    void removeCommandImpl(Command cmd) {
        synchronized (Display.LCDUILock) {
            // The super class will update the command set of the Form
            super.removeCommandImpl(cmd);

            if ((numCommands < 1) && (appearanceMode != Item.PLAIN)) {
                // store value of the original appearanceMode
                originalAppearanceMode = this.appearanceMode;
                // change the appearanceMode to plain
                this.appearanceMode = Item.PLAIN;
            }

            checkTraverse();
            invalidate();
        } // synchronized
    }

    /**
     * Check that given the label, text, and commands, Form
     * should traverse this StringItem. Updates the internal
     * 'skipTraverse' variable.
     */
    private void checkTraverse() {
        if (str == null && label == null) {
            skipTraverse = true;
        } else if (str == null && label.trim().equals("")) {
            skipTraverse = true;
        } else if (label == null && str.trim().equals("")) {
            skipTraverse = true;
        } else {
            skipTraverse = false;
        }
    }

    /**
     * Determines if this StringItem should be rendered with the button border.
     * @return true if the StringItem should be rendered with 
     *         the button border, false - otherwise
     */
    private boolean isButton() {
        return (numCommands > 0 && appearanceMode == Item.BUTTON);
    }

    /** The text of this StringItem */
    private String str;

    /** The Font to render this StringItem's text in */
    private Font font;

    /** The appearance hint */
    private int appearanceMode;

    /** The original appearance hint before switching */
    private int originalAppearanceMode;

    /** Minimum height for one line */
    private int minimumLineHeight;

    /**
     * An internal flag. True if Form should not traverse
     * to this StringItem
     */
    private boolean skipTraverse;
  
}