/*
*
*
* Copyright 1990-2007 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 com.sun.midp.chameleon.input;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import com.sun.midp.i18n.Resource;
import com.sun.midp.i18n.ResourceConstants;
import com.sun.midp.lcdui.EventConstants;
/**
* An InputMode instance which allows to select the particular symbol
* from the table of predefined symbols.
*/
public class SymbolInputMode implements InputMode {
/** A holder for the keyCode which was last processed */
protected int lastKey = -1;
/** The InputModeMediator for the current input session */
protected InputModeMediator mediator;
/** Symbol table */
protected final SymbolTable st = new SymbolTable();
/**
* The number of symbol_table is designed to be 29 for 5x6 matrix,
* starting the selection at 12. But if you have more, the total
* must be under 36 for 6x6 matrix.
*/
protected final static char[] symbolTableChars;
static {
symbolTableChars =
Resource.getString(ResourceConstants.LCDUI_TF_SYMBOLS_TABLE).
toCharArray();
}
/**
* This method is called to determine if this InputMode supports
* the given text input constraints. The semantics of the constraints
* value are defined in the javax.microedition.lcdui.TextField API.
* If this InputMode returns false, this InputMode must not be used
* to process key input for the selected text component.
*
* @param constraints text input constraints. The semantics of the
* constraints value are defined in the TextField API.
*
* @return true if this InputMode supports the given text component
* constraints, as defined in the MIDP TextField API
*/
public boolean supportsConstraints(int constraints) {
switch (constraints & TextField.CONSTRAINT_MASK) {
case TextField.NUMERIC:
case TextField.DECIMAL:
case TextField.PHONENUMBER:
return false;
default:
return true;
}
}
/**
* Returns the display name which will represent this InputMode to
* the user, such as in a selection list or the softbutton bar.
*
* @return the locale-appropriate name to represent this InputMode
* to the user
*/
public String getName() {
return Resource.getString(ResourceConstants.LCDUI_TF_SYMBOLS);
}
/**
* Returns the command name which will represent this InputMode to
* the user
*
* @return the locale-appropriate command name to represent this InputMode
* to the user
*/
public String getCommandName() {
return getName();
}
/**
* This method will be called before any input keys are passed
* to this InputMode to allow the InputMode to perform any needed
* initialization. A reference to the InputModeMediator which is
* currently managing the relationship between this InputMode and
* the input session is passed in. This reference can be used
* by this InputMode to commit text input as well as end the input
* session with this InputMode. The reference is only valid until
* this InputMode's endInput() method is called.
*
* @param constraints text input constraints. The semantics of the
* constraints value are defined in the TextField API.
*
* @param mediator the InputModeMediator which is negotiating the
* relationship between this InputMode and the input session
*
* @param inputSubset current input subset
*/
public void beginInput(InputModeMediator mediator, String inputSubset,
int constraints) {
validateState(false);
this.mediator = mediator;
}
/**
* Symbol Input mode is represented by Symbol table implemented as canvas
* @return SymbolTable
*/
public Displayable getDisplayable() {
return st;
}
/**
* Process the given key code as input.
*
* This method will return true if the key was processed successfully,
* false otherwise.
*
* @param keyCode the keycode of the key which was input
* @param longPress return true if it's long key press otherwise false
* @return true if the key was processed by this InputMode, false
* otherwise.
*/
public int processKey(int keyCode, boolean longPress) {
return KEYCODE_NONE;
}
/**
* return the pending char
* used to bypass the asynchronous commit mechanism
* e.g. to immediately commit a char before moving the cursor
* @return return the pending char
*/
public char getPendingChar() {
return lastKey == KEYCODE_NONE ? 0 : (char)lastKey;
}
/**
* Return the next possible match for the key input processed thus
* far by this InputMode. A call to this method should be preceeded
* by a check of hasMoreMatches(). If the InputMode has more available
* matches for the given input, this method will return them one by one.
*
* @return a String representing the next available match to the key
* input thus far
*/
public String getNextMatch() {
return null;
}
/**
* True, if after processing a key, there is more than one possible
* match to the input. If this method returns true, the getNextMatch()
* method can be called to return the value.
*
* @return true if after processing a key, there is more than the one
* possible match to the given input
*/
public boolean hasMoreMatches() {
return false;
}
/**
* Gets the possible string matches
*
* @return returns the set of options.
*/
public String[] getMatchList() {
return new String[0];
}
/**
* Mark the end of this InputMode's processing. The only possible call
* to this InputMode after a call to endInput() is a call to beginInput()
* to begin a new input session.
*/
public void endInput() {
validateState(true);
mediator = null;
}
/**
* Returns true if input mode is using its own displayable, false ifinput
* mode does not require the speial displayable for its representation.
* For Symbol mode is represented by Symbol table canvas, so it returns true
* @return true if input mode is using its own displayable, otherwise false
*/
public boolean hasDisplayable() {
return true;
}
/**
* This method will validate the state of this InputMode. If this
* is a check for an "active" operation, the TextInputMediator must
* be non-null or else this method will throw an IllegalStateException.
* If this is a check for an "inactive" operation, then the
* TextInputMediator should be null.
* @param activeOperation true if any operation is active otherwise false.
*/
protected void validateState(boolean activeOperation) {
if (activeOperation && mediator == null) {
throw new IllegalStateException(
"Illegal operation on an input session already in progress");
} else if (!activeOperation && mediator != null) {
throw new IllegalStateException(
"Illegal operation on an input session which is not in progress");
}
}
/**
* A special Canvas to display a symbol table.
*/
protected class SymbolTable extends Canvas implements CommandListener {
/** The margin size */
private static final int MARGIN = 1;
/** The margin size */
private static final int DMARGIN = 2;
/** Select commant to accept the symbol */
private final Command okCmd = new Command(
Resource.getString(ResourceConstants.OK), Command.OK, 1);
/** Back commant to reject the symbol */
private final Command backCmd = new Command(
Resource.getString(ResourceConstants.BACK), Command.BACK, 0);
/** Cell size */
private int cellSize;
/** Height margin */
private int hmargin;
/** Width margin */
private int wmargin;
/** Margin for the cursor */
private int margin = 1;
/** Window x position */
private int wx;
/** Window y position */
private int wy;
/** Window width */
private int ww;
/** Window height */
private int wh;
/** Number of columns */
private int cols;
/** Number of rows */
private int rows;
/** Current cursor position */
private int pos;
/** New cursor position */
private int newpos;
/** Font */
private Font font;
/** x cursor coordinate */
int x_cursor_coord;
/** y cursor coordinate */
int y_cursor_coord;
/** default location to start the cursor */
protected int defaultSymbolCursorPos = 14;
/** Default constructor for SymbolTable */
protected SymbolTable() {
init();
}
/**
* Symbol accept is run in separate thread
*/
private class Accept implements Runnable {
/**
* run for accept
*/
public void run() {
lastKey = symbolTableChars[pos];
completeInputMode(true);
}
}
/**
* Symbol reject is run in separate thread
*/
private class Reject implements Runnable {
/**
* run for reject
*/
public void run() {
completeInputMode(false);
}
}
/**
* Instance of accept object
*/
private Runnable accept = new Accept();
/**
* Instance of reject object
*/
private Runnable reject = new Reject();
/**
* Initialize the symbol table
*/
void init() {
calculateProportions();
addCommand(backCmd);
addCommand(okCmd);
setCommandListener(this);
}
/**
* Calculate numbers of colomns and rows of symbol table.
* Calculate cell size of symbol table.
*/
private void calculateProportions() {
{ // choose the table sizes
rows = cols = (int) Math.sqrt(symbolTableChars.length);
if (rows*cols < symbolTableChars.length) {
rows++;
}
if (rows*cols < symbolTableChars.length) {
cols++;
}
}
int w = getWidth() / cols;
int h = getHeight() / rows;
cellSize = (w > h) ? h : w;
int cw = 0, ch = 0;
int temp_cols = getWidth() / cellSize;
if ( temp_cols > cols) {
cols = temp_cols;
rows = (int)Math.ceil((double)symbolTableChars.length / cols);
}
int[] fs = { Font.SIZE_LARGE, Font.SIZE_MEDIUM, Font.SIZE_SMALL };
for (int i = 0; i < fs.length; i++) {
font = Font.getFont(Font.FACE_SYSTEM,
Font.STYLE_BOLD,
fs[i]);
cw = font.charWidth('M');
ch = font.getHeight();
if (cw <= cellSize && ch <= cellSize) {
break;
}
}
ww = cols * cellSize;
wh = rows * cellSize;
hmargin = (cellSize - ch) / 2;
wmargin = cellSize / 2;
wx = (getWidth() - ww) / 2;
wy = (getHeight() - wh) / 2;
}
/**
* Notify this symbol table its being shown on the screen.
* Overrides Canvas.showNotify.
*/
protected void showNotify() {
pos = newpos = defaultSymbolCursorPos;
}
/**
* Called when the drawable area of the <code>Canvas</code> has
* been changed. This
* method has augmented semantics compared to {@link
* Displayable#sizeChanged(int,int) Displayable.sizeChanged}.
*
* @param w the new width in pixels of the drawable area of the
* <code>Canvas</code>
* @param h the new height in pixels of the drawable area of
* the <code>Canvas</code>
*/
protected void sizeChanged(int w, int h) {
calculateProportions();
}
/**
* Paint this symbol table.
* Overrides Canvas.paint.
*
* @param g The Graphics object to paint to
*/
protected void paint(Graphics g) {
paintPanel(g);
}
/**
* Paint the symbol table panel
*
* @param g The Graphics object to paint to
*/
void paintPanel(Graphics g) {
int clipX = g.getClipX();
int clipY = g.getClipY();
int clipW = g.getClipWidth();
int clipH = g.getClipHeight();
Font old_font = g.getFont();
g.setFont(font);
g.setGrayScale(255);
g.fillRect(clipX, clipY, clipW, clipH);
int sr = (clipY - wy) / cellSize;
if (sr < 0) sr = 0;
int sc = (clipX - wx) / cellSize;
if (sc < 0) sc = 0;
int er = (clipH + clipY + cellSize - wy) / cellSize;
if (er > rows - 1) er = rows - 1;
int ec = (clipW + clipX + cellSize - wx) / cellSize;
if (ec > cols - 1) ec = cols - 1;
g.setGrayScale(0);
g.drawRect(wx, wy, ww, wh);
for (int r = sr; r <= er; r++) {
for (int c = sc; c <= ec; c++) {
int i = r * cols + c;
if (i >= symbolTableChars.length) {
break;
}
if (i == newpos) {
g.setGrayScale(0);
setCursorCoord(newpos);
g.fillRect(x_cursor_coord, y_cursor_coord,
cellSize - margin, cellSize - margin);
drawChar(g, symbolTableChars[i], r, c, true);
g.setGrayScale(255);
pos = newpos;
} else {
drawChar(g, symbolTableChars[i], r, c, false);
}
}
}
g.setFont(old_font);
}
/**
* Draw a character
*
* @param g The Graphics object to paint to
* @param c The character to draw
* @param row The row the character is in
* @param col The column the character is in
* @param reverse A flag to draw the character in inverse
*/
void drawChar(Graphics g, char c, int row, int col, boolean reverse) {
int y = wy + row * cellSize + hmargin;
int x = wx + col * cellSize + wmargin;
Font old_font = g.getFont();
g.setFont(font);
g.setGrayScale(reverse ? 255 : 0);
g.drawChar(c, x, y, Graphics.HCENTER | Graphics.TOP);
g.setFont(old_font);
}
/**
* Move focus to the symbol
* @param x x coordinate of the pointer
* @param y y coordinate of the pointer
*/
protected void pointerPressed(int x, int y) {
int pressedId;
pressedId = getIdAtPointerPosition(x, y);
// move focus to the clicked position
if (pressedId < symbolTableChars.length && pressedId >= 0) {
newpos = pressedId;
repaint();
}
}
/**
* Select the symbol by one click
* @param x x coordinate of the pointer
* @param y y coordinate of the pointer
*/
protected void pointerReleased(int x, int y) {
int pressedId;
// select character
pressedId = getIdAtPointerPosition(x, y);
if (pos == pressedId) {
new Thread(accept).start();
}
}
/**
* Helper function to determine the cell index at the x,y position
*
* @param x, y pointer coordinates in this popup
* layer's space (0,0 means left-top corner)
*
* @return cell id at the pointer position
*/
private int getIdAtPointerPosition(int x, int y) {
int ret = -1;
int col = (x - wx) / cellSize;
int row = (y - wy) / cellSize;
if (col >= 0 && col < cols && row >= 0 && row < rows) {
ret = cols * row + col;
}
return ret;
}
/**
* Handle a key press event on this symbol table.
* Overrides Canvas.keyPressed.
*
* @param keyCode The key that was pressed
*/
protected void keyPressed(int keyCode) {
validateState(true);
if (mediator != null && mediator.isClearKey(keyCode)) {
new Thread(reject).start();
} else {
switch (getGameAction(keyCode)) {
case Canvas.RIGHT:
if ((pos + 1) < symbolTableChars.length) {
newpos = pos + 1;
setCursorCoord(pos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
setCursorCoord(newpos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
}
break;
case Canvas.LEFT:
if (pos > 0) {
newpos = pos - 1;
setCursorCoord(pos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
setCursorCoord(newpos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
}
break;
case Canvas.UP: {
int p = pos - cols;
if (p >= 0) {
newpos = p;
setCursorCoord(pos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
setCursorCoord(newpos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
}
break;
}
case Canvas.DOWN: {
int p = pos + cols;
if (p < symbolTableChars.length) {
newpos = p;
setCursorCoord(pos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
setCursorCoord(newpos);
repaint(x_cursor_coord,y_cursor_coord,cellSize - margin,cellSize - margin);
}
break;
}
case Canvas.FIRE:
new Thread(accept).start();
break;
}
}
}
/**
* Set bounds of repaint area
* @param curr_pos - position of cursor
*/
private void setCursorCoord(int curr_pos) {
int row = curr_pos / cols;
int col = curr_pos % cols;
y_cursor_coord = wy + row * cellSize;
x_cursor_coord = wx + col * cellSize;
}
/**
* This method is used to immediately commit the given
* string and then call the TextInputMediator's inputModeCompleted()
* method
* @param c command
* @param d displayable
*/
public void commandAction(Command c, Displayable d) {
validateState(true);
if (c == backCmd) {
new Thread(reject).start();
} else if (c == okCmd) {
new Thread(accept).start();
}
}
}
/**
* Complete current input mode
* @param commit true if the symbol has to be committed otherwise false
*/
protected void completeInputMode(boolean commit) {
if (commit) {
commitPendingChar();
}
mediator.inputModeCompleted();
}
/**
* Commit pending char
* @return true if the char has been committed otherwise false
*/
protected boolean commitPendingChar() {
boolean committed = false;
if (lastKey != KEYCODE_NONE) {
committed = true;
mediator.commit(String.valueOf((char)lastKey));
}
lastKey = -1;
return committed;
}
/**
* Check if the char is the symbol from the symbol table
* @param c char
* @return true if this char exists in the symbol table otherwise false.
*/
public static boolean isSymbol(char c) {
for (int i = 0; i < symbolTableChars.length; i++) {
if (symbolTableChars[i] == c) {
return true;
}
}
return false;
}
/** this mode is not set as default. So the map is initialoized by false */
private static final boolean[][] isMap =
new boolean[TextInputSession.INPUT_SUBSETS.length]
[TextInputSession.MAX_CONSTRAINTS];
/**
* Returns the map specifying this input mode is proper one for the
* particular pair of input subset and constraint. The form of the map is
*
* |ANY|EMAILADDR|NUMERIC|PHONENUMBER|URL|DECIMAL|
* ---------------------------------------------------------------------
* IS_FULLWIDTH_DIGITS |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_FULLWIDTH_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_HALFWIDTH_KATAKANA |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_HANJA |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_KANJI |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_LATIN_DIGITS |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_SIMPLIFIED_HANZI |t|f| t|f | t|f | t|f |t|f| t|f |
* IS_TRADITIONAL_HANZI |t|f| t|f | t|f | t|f |t|f| t|f |
* MIDP_UPPERCASE_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
* MIDP_LOWERCASE_LATIN |t|f| t|f | t|f | t|f |t|f| t|f |
* NULL |t|f| t|f | t|f | t|f |t|f| t|f |
*
* @return input subset x constraint map
*/
public boolean[][] getIsConstraintsMap() {
return isMap;
}
}
|