CanvasTextBox.java

/*
 * Copyright © 2013 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.
 */

package com.nokia.example;

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;
        private final int editorYPadding = 10;

	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;

		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
                                + ((BlogWriter.isAshaPlatform()) ? editorYPadding : 0);
		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;
                if(BlogWriter.isAshaPlatform())
                    h += 2 * editorYPadding - 4;
		int editorHeight = (h - labelFont.getHeight() - 3 * this.margin - 2 * this.textEditorBorderMargin) + (BlogWriter.isAshaPlatform() ? margin : 0);
		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());
	}
	
	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) {
        if (BlogWriter.isAshaPlatform()) {
            gfx.setColor(0x8D8C8C);
            gfx.fillRect(0, 0, getWidth(), getHeight());
        }

		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-1);
		}
	}

	/**
	 * 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;
        if (BlogWriter.isAshaPlatform()) {
            height = this.textEditor.getHeight() - 2;// + 2 * this.textEditorBorderMargin;
        } else {
            height = this.textEditor.getHeight() + 2 * this.textEditorBorderMargin;
        }

        this.normalState = new TextBoxState(width, height, 0x000000, 0xff000000,
                0xe0e0e0);
        this.focusedState = new TextBoxState(width, height, 0x000000, 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);
		}
	}
}