CanvasTextBox.java

package com.nokia.example;
/*
 * Copyright © 2012 Nokia Corporation. All rights reserved. Nokia and Nokia
 * Connecting People are registered trademarks of Nokia Corporation. Oracle and
 * Java are trademarks or registered trademarks of Oracle and/or its affiliates.
 * Other product and company names mentioned herein may be trademarks or trade
 * names of their respective owners. See LICENSE.TXT for license information.
 */

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import com.nokia.mid.ui.CanvasGraphicsItem;
import com.nokia.mid.ui.TextEditor;
import com.nokia.mid.ui.TextEditorListener;

/**
 * This class implements CanvasTextBox control with label, text editing area
 * decorations and a keyboard indicator.
 *
 * CanvasTextBox is based on CanvasGraphicsItem, on which label and text editor
 * borders are drawn. On Symbian platform, the keyboard indicator will be
 * relocated from its default position. Also a very simple scrollbar is drawn to
 * indicate position in case CanvasTextBox is constructed as multiline.
 *
 * CanvasTextBox can have normal, focused or dimmed (disabled) state. State is
 * encapsulated in TextBoxState objects (class TextBoxState is implemented in
 * this file).
 */
public class CanvasTextBox extends CanvasGraphicsItem implements
TextEditorListener {

    private Font labelFont = null;
    private String label;
    private TextEditor textEditor;
    private TextBoxState normalState;
    private TextBoxState focusedState;
    private TextBoxState dimmedState;
    private TextBoxState currentState;
    private boolean enabled = true;
    private boolean focused = false;
    private TextEditorListener listener;
    private int controlWidth = 200;
    private int textLimit;
    private boolean multiline;
    private Scrollbar scrollbar;
    private Controls controls;

    // Enable indicator on JRT version 2.1 for symbian devices by uncommenting the following code. 
    // JRT versions 2.2 and newer already contain the indicator in the virtual keyboard.
    // private boolean showIndicator;


    // Default control's margin
    private final int margin = 3;
    // Margin applied around text editor and its border
    private final int textEditorBorderMargin = 9;
    // Margin applied on text editor it self
    private final int textEditorMargin = 12;

    public CanvasTextBox(Canvas parent, String label, int type, int textLimit,
            boolean multiline) {
        super(1, 1);
        this.setParent(parent);
        this.label = label;
        this.textLimit = textLimit;
        this.multiline = multiline;

        // Enable indicator on JRT version 2.1 for symbian devices by uncommenting the following code. 
        // JRT versions 2.2 and newer already contain the indicator in the virtual keyboard.
        /*
            this.showIndicator = false;
            String platform = System.getProperty("microedition.platform");
            if (platform != null) {
                // Disable indicator on JRT versions 2.2 and newer, since the
                // virtual keyboard contains indicator already.
                this.showIndicator = platform.indexOf("java_build_version=2.1") != -1;
        }*/

        this.labelFont = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN,
                Font.SIZE_LARGE);
        this.initializeTextEditor(parent, type);
        this.createStates();
        this.setAutomaticSize();
    }

    public CanvasTextBox(Canvas parent, String label, int type, int textLimit) {
        // Construct single line text box with default width
        this(parent, label, type, textLimit, false);
    }

    public String getText() {
        return textEditor.getContent();
    }

    public boolean isEmpty() {
        return textEditor.size() == 0;
    }

    public void clearChar() {
        int caret = textEditor.getCaretPosition() - 1;
        if (caret >= 0) {
            textEditor.delete(textEditor.getCaretPosition() - 1, 1);
            repaint();
        }
    }

    public void setVisible(boolean visible) {
        super.setVisible(visible);
        textEditor.setVisible(visible);
    }

    /**
     * Sets the control position.
     *
     * Repositions are also nested TextEditor and indicator (if supported).
     */
    public void setPosition(int x, int y) {
        // Set position of the underlying CanvasGraphicsItem
        super.setPosition(x, y);

        // Calculate and sed position of TextEditor
        int editorX = x + (this.controlWidth - this.editorWidth()) / 2;
        int editorY = y + labelFont.getHeight() + 2 * this.margin
                + (this.currentState.height - this.textEditor.getHeight()) / 2;
        this.textEditor.setPosition(editorX, editorY);

        // Where supported, re-position also keyboard indicator
        /*
               //Enable indicator on JRT version 2.1 for symbian devices by uncommenting the following code. 
             //JRT versions 2.2 and newer already contain the indicator in the virtual keyboard.

        if (this.textEditor instanceof com.nokia.mid.ui.S60TextEditor) {
            com.nokia.mid.ui.S60TextEditor s60Editor = (com.nokia.mid.ui.S60TextEditor) this.textEditor;
            s60Editor.setIndicatorLocation(editorX, y + labelFont.getHeight()
                    + this.currentState.getHeight() + 3 * this.margin);
            s60Editor.setIndicatorVisibility(this.focused && this.showIndicator);
        }*/
    }


    /**
     * Sets size of the CanvasTextBox.
     *
     * This method re-layouts nested TextEditor and indicator (if supported).
     */
    public void setSize(int w, int h) {
        // Set size of underlying CanvasGraphicsItem
        super.setSize(w, h);

        // Calculate the size of  TextEditor.
        this.controlWidth = w;
        int editorHeight = h - labelFont.getHeight() - 3 * this.margin - 2
                * this.textEditorBorderMargin;

        /*
               //Enable indicator on JRT version 2.1 for symbian devices by uncommenting the following code. 
             //JRT versions 2.2 and newer already contain the indicator in the virtual keyboard.

        if (this.textEditor instanceof com.nokia.mid.ui.S60TextEditor) {
            // If the indicator is available, reduce size of the editor
            com.nokia.mid.ui.S60TextEditor s60Editor = (com.nokia.mid.ui.S60TextEditor) this.textEditor;
            editorHeight -= s60Editor.getIndicatorSize()[1] + this.margin;
        }*/

        this.textEditor.setSize(this.controlWidth - 2 * this.textEditorMargin
                - (this.scrollbar != null ? Scrollbar.width : 0), editorHeight);
        // States need to be re-created to reflect change in editor's size
        this.createStates();
        // This call updates indicator position
        this.setPosition(this.getPositionX(), this.getPositionY());
    }

    /**
     * Sets TextEditorListener. Events from TextEditor are forwarded to given
     * listener.
     */
    public void setTextEditorListener(TextEditorListener listener) {
        this.listener = listener;
    }

    /**
     * Enables or disables control.
     */
    public void setEnabled(boolean enabled) {
        if (this.enabled == enabled) {
            return;
        }

        this.enabled = enabled;
        this.updateState();
        this.repaint();
    }

    public boolean isEnabled() {
        return this.enabled;
    }

    /**
     * Sets focus of the CanvasTextBox. Focus is forwarded to TextEditor, and
     * when control loses focus, it hides indicator (where it is supported).
     */
    public void setFocused(boolean focused) {
        if (this.focused == focused) {
            //return;
        }

        this.focused = focused;
        this.updateState();
        if (this.textEditor.hasFocus() != this.focused) {
            this.textEditor.setFocus(this.focused);
        }

        /*               
             //Enable indicator on JRT version 2.1 for symbian devices by uncommenting the following code. 
             //JRT versions 2.2 and newer already contain the indicator in the virtual keyboard.

        if (this.textEditor instanceof com.nokia.mid.ui.S60TextEditor) {
            com.nokia.mid.ui.S60TextEditor s60Editor = (com.nokia.mid.ui.S60TextEditor) this.textEditor;
            s60Editor.setIndicatorVisibility(this.focused && this.showIndicator);
        }*/
        this.repaint();
    }

    public boolean isFocused() {
        return this.focused;
    }

    /**
     * This is very basic pointer event handling. It expects pointer pressed
     * events.
     *
     * CanvasGraphicsItem does not receive any pointer events, they are
     * delivered to parent Canvas, so this method needs to be called from
     * Canvas.pointerPressed() override.
     */
    public void handlePointerPressed(int x, int y) {
        if (this.isVisible() && this.enabled) {
            if (this.hitTest(x, y)) {
                this.setFocused(true);
                if (this.scrollbar != null) {
                    this.scrollbar.handlePointerPressed(x, y);
                    this.repaint();
                }
                if (this.controls != null) {
                    this.controls.handlePointerPressed(x - this.getPositionX(), y - this.getPositionY());
                    this.repaint();
                }
            } else {
                this.setFocused(false);
            }
        }
    }

    /**
     * This is very basic pointer event handling for pointer released events.
     */
    public void handlePointerReleased(int x, int y) {
        if (this.isVisible() && this.controls != null) {
            this.controls.handlePointerReleased(x - this.getPositionX(), y - this.getPositionY());
            this.repaint();
        }
    }

    /**
     * Checks whether given point belongs to the control. Coordinates are
     * relative to parent Canvas.
     */
    public boolean hitTest(int x, int y) {
        return x >= this.getPositionX()
                && x < (this.getPositionX() + this.getWidth())
                && y >= this.getPositionY()
                && y < (this.getPositionY() + this.getHeight());
    }

    /**
     * Before exiting MIDlet, it is necessary to set parents of both TextEditor
     * and the base CanvasGraphics item to null.
     */
    public void dispose() {
        this.textEditor.setParent(null);
        this.setParent(null);
    }

    /**
     * Paints the label, currentState and scrollbar.
     */
    public void paint(Graphics gfx) {

        gfx.setColor(this.currentState.labelColor);
        gfx.drawString(this.label, this.margin, this.margin, Graphics.TOP
                | Graphics.LEFT);

        int textEditorY = this.labelFont.getHeight() + 2 * this.margin;
        this.currentState.paint(gfx,
                (this.controlWidth - this.currentState.width) / 2, textEditorY);

        if (this.scrollbar != null) {
            this.scrollbar.paint(gfx, this.textEditor.getWidth()
                    + this.textEditorMargin, textEditorY
                    + this.textEditorBorderMargin);
        }
        if (this.controls != null && this.isFocused()) {
            this.controls.paint(gfx, (this.controlWidth - this.currentState.width) / 2 + this.currentState.width, textEditorY);
        }
    }

    /**
     * Handles some of the TextEditor events to support scrollbar and forwards
     * events to external listener (if there is any).
     */
    public void inputAction(TextEditor source, int type) {
        if (source != this.textEditor) {
            return;
        }

        if ((type & (TextEditorListener.ACTION_SCROLLBAR_CHANGED | TextEditorListener.ACTION_CARET_MOVE)) != 0) {
            if (this.scrollbar != null) {
                this.repaint();
            }
        }

        if (this.listener != null) {
            this.listener.inputAction(source, type);
        }
    }

    /**
     * Creates TextEditor instance and sets its properties.
     */
    private void initializeTextEditor(Canvas parent, int type) {
        this.textEditor = TextEditor.createTextEditor("", this.textLimit, type,
                this.controlWidth - 2 * this.textEditorMargin,
                (this.multiline ? 2 : 1) * (labelFont.getHeight()));

        this.textEditor.setParent(parent);
        if (this.multiline) {
            this.textEditor.setMultiline(true);

            /*
                   //Enable indicator on JRT version 2.1 for symbian devices by uncommenting the following code. 
                 //JRT versions 2.2 and newer already contain the indicator in the virtual keyboard.

            if (this.textEditor instanceof com.nokia.mid.ui.S60TextEditor) {
                this.textEditor.setSize(this.textEditor.getWidth() - Scrollbar.width, this.textEditor.getHeight());
                //this.scrollbar = new Scrollbar(this.textEditor, 0xaaaaaa, 0x101010);
            }*/

            if(BlogWriter.isS60Platform())
                this.scrollbar = new Scrollbar(this.textEditor, 0xaaaaaa, 0x101010);
        }

        if(!BlogWriter.isS60Platform())
        {
            this.controls = new Controls(this.textEditor, 0x101010, 0xaaaaaa, 0xffffff);
        }
        this.textEditor.setTextEditorListener(this);
        this.setZPosition(1);
        this.textEditor.setZPosition(2);
    }

    /**
     * Sets CanvasTextBox initial size.
     */
    private void setAutomaticSize() {
        // Calculate height so all part of the CanvasTextBox fit
        int height = labelFont.getHeight() + this.normalState.getHeight() + 3
                * this.margin;

        /*
               //Enable indicator on JRT version 2.1 for symbian devices by uncommenting the following code. 
             //JRT versions 2.2 and newer already contain the indicator in the virtual keyboard.

        if (this.textEditor instanceof com.nokia.mid.ui.S60TextEditor) {
            com.nokia.mid.ui.S60TextEditor s60Editor = (com.nokia.mid.ui.S60TextEditor) this.textEditor;
            height += s60Editor.getIndicatorSize()[1] + this.margin;
        }*/
        super.setSize(this.controlWidth + 2 * this.margin, height);
    }

    /**
     * Updates currentState based on state flags.
     */
    private void updateState() {
        if (this.enabled) {
            if (this.focused) {
                this.currentState = this.focusedState;
            } else {
                this.currentState = this.normalState;
            }
        } else {
            this.currentState = this.dimmedState;
        }
        this.textEditor.setForegroundColor(this.currentState.textColor);
    }

    /**
     * Creates CanvasTextBox states.
     */
    private void createStates() {
        int width = this.editorWidth() + 2 * this.textEditorBorderMargin;
        int height = this.textEditor.getHeight() + 2
                * this.textEditorBorderMargin;
        this.normalState = new TextBoxState(width, height, 0xffffff, 0xff000000,
                0xe0e0e0);
        this.focusedState = new TextBoxState(width, height, 0xffffff, 0xff000000,
                0xffffff);
        this.dimmedState = new TextBoxState(width, height, 0xc5c5c5, 0xff313131,
                0xa0a0a0);
        this.updateState();
    }

    /**
     * Calculates space needed for TextEditor. Scrollbar width is taken into
     * account.
     */
    private int editorWidth() {
        return this.textEditor.getWidth()
                + (this.scrollbar != null ? Scrollbar.width : 0);
    }

    /**
     * Encapsulates CanvasTextBox visual appearance.
     *
     * TextBoxState allows to specify label, text and background colors.
     *
     * When the size of the owning CanvasTextEditor changes, TextBoxStates need
     * to be recreated since their width and height can only be set in the
     * constructor.
     */
    class TextBoxState {

        public int backgroundColor;
        public int labelColor;
        public int textColor;
        private int width;
        private int height;
        private final int cornersDiameter = 10;
        private final int borderColor = 0x000000;

        public TextBoxState(int width, int height, int labelColor,
                int textColor, int backgroundColor) {
            this.width = width;
            this.height = height;
            this.labelColor = labelColor;
            this.textColor = textColor;
            this.backgroundColor = backgroundColor;
        }

        public int getHeight() {
            return this.height;
        }

        public int getWidth() {
            return this.width;
        }

        /**
         * Draws background and border. This method should be called from
         * CanvasTextBox paint method.
         */
        public void paint(Graphics gfx, int x, int y) {
            gfx.setColor(this.backgroundColor);
            gfx.fillRoundRect(x, y, this.width, this.height,
                    this.cornersDiameter, this.cornersDiameter);
            gfx.setColor(this.borderColor);
            gfx.drawRoundRect(x, y, this.width, this.height,
                    this.cornersDiameter, this.cornersDiameter);
        }
    }
}