vavi-apps-appleii-bdj/src/test/java/vavi/apps/appleii/VirtualKeyboard.java

784 lines
26 KiB
Java

/*
* $LastChangedDate: 2005-11-21 02:11:20 +0900 (ì›? 21 11 2005) $
*
* Copyright 1990-2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package vavi.apps.appleii;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
/**
* This is a popup layer that handles a sub-popup within the text tfContext
*
* @author Amir Uval
*/
class VirtualKeyboard {
/** indicates whether the virtual keyboard is enabled */
public static boolean USE_VIRTUAL_KEYBOARD = true;
/** indicates whether the virtual keypad is enabled */
public static final boolean USE_VIRTUAL_KEYPAD = false;
/** indicates whether the virtual keyboard is opened automatically */
public static boolean USE_VIRTUAL_KEYBOARD_OPEN_AUTO = false;
/** instance of the virtual keyboard listener */
VirtualKeyboardListener vkl;
// keyboard dimensions
int kbX;
int kbY;
int kbWidth;
int kbHeight;
int fontW; // width of 'M'
int fontH; // height of 'M'
int buttonW; // width of keyboard
int buttonH; // height of keyboard
int fontWCenter; // placement of text inside button
int fontHTop; // placement of text inside button
int maxRows; // horizontal keyboard rows
int maxColumns; // vertical keyboard columns
int fullColumns; // number of columns that are completely full with keys
int currentChar = 0;
int currentKeyboard = 1; // abc
int textfieldHeight = 0; // height of text field area, including adornments
int candidateFieldHeight = 0; // height of candidate input field
char itemIndexWhenPressed;
char PRESS_OUT_OF_BOUNDS = 0;
/** array of all available keys n the keyboard */
char[][] keys;
boolean inMetaKeys = false; // traversal mode
boolean inShift = false;
int currentMeta = 0;
Image[] metaKeys = null;
boolean textKbd = false;
Font f;
static final int PRESSED = 0;
static final int RELEASED = 1;
/**
* Virtual Keyboard constructor.
*
* @param keys array of available keys for the keyboard
* @param vkl the virtual keyboard listener
* @param displayTextArea flag to indicate whether to display the text area
*/
public VirtualKeyboard(char[][] keys,
VirtualKeyboardListener vkl,
boolean displayTextArea,
int neededColumns,
int neededRows) throws VirtualKeyboardException {
textKbd = displayTextArea;
if (textKbd) {
PADDING = 1;
} else {
PADDING = 2;
}
currentKeyboard = 0;
this.vkl = vkl;
kbX = PADDING;
kbY = PADDING;
kbWidth = vkl.getAvailableWidth() - 2 * PADDING;
kbHeight = vkl.getAvailableHeight() - 2 * PADDING;
// f = Font.getFont(Font.MONOSPACED, // or SYSTEM
// Font.PLAIN,
// 36);
fontW = 36;// f.charWidth('M');
fontH = 36;// f.getHeight();
if (textKbd) {
textfieldHeight = fontH + 8 * PADDING;
buttonW = fontW + 8;
buttonH = fontH + 8;
fontHTop = (buttonH - fontH) / 2;
fontWCenter = buttonW / 2;
} else {
buttonW = fontW * 3;
buttonH = fontH * 3;
fontHTop = (buttonH - fontH) / 2;
fontWCenter = buttonW / 2;
}
candidateFieldHeight = 0;
maxRows = (kbHeight - PADDING) / (buttonH + PADDING);
if (textKbd) {
if (neededColumns == 0) {
maxColumns = (kbWidth - PADDING) / (buttonW + PADDING);
} else {
maxColumns = neededColumns;
}
kbWidth = maxColumns * (buttonW + PADDING) + PADDING + 1;
kbX = (vkl.getAvailableWidth() - kbWidth) / 2;
} else {
maxColumns = 7; // verify
kbWidth = maxColumns * (buttonW + PADDING) + PADDING + 1;
kbX = 0;
}
if (neededRows == 0) {
int tmpMax = 0; // will hold the longest keyboard.
for (int i = 0; i < keys.length; i++) {
if (tmpMax < keys[i].length)
tmpMax = keys[i].length;
}
neededRows = (tmpMax + maxColumns - 1) / maxColumns;
}
if (neededRows > maxRows) {
System.err.println("Keys list is too long for this size of screen.");
System.err.println("Please split your keyboard array to multiple arrays.");
// System.exit(0);
throw new VirtualKeyboardException("Keys list is too long for this size of screen.");
}
maxRows = neededRows;
int neededHeight = 0;
// do not require to account for meta keys for a canvas keyboard-hk
if (textKbd) {
neededHeight = maxRows * (buttonH + PADDING) + 4 * PADDING + // between the keys and the meta keys
IMAGE_SIZE + META_PADDING * 4 + textfieldHeight + candidateFieldHeight;
kbY = kbHeight - neededHeight - 4 * PADDING;
kbHeight = neededHeight;
} else {
neededHeight = maxRows * (buttonH + PADDING) + 3 * PADDING + textfieldHeight;
kbY = vkl.getAvailableHeight() - neededHeight;
kbHeight = neededHeight;
}
this.keys = keys;
if (textKbd)
currentKeyboard = 1; // lower case
else
currentKeyboard = 0;
fullColumns = keys[currentKeyboard].length / maxColumns;
// need not be displayed in the canvas mode
if (displayTextArea) { // hk
metaKeys = new Image[7];
metaKeys[OK_META_KEY] = createImage("/ok.png");
metaKeys[CANCEL_META_KEY] = createImage("/cancel.png");
metaKeys[BACKSPACE_META_KEY] = createImage("/backspace.png");
metaKeys[SHIFT_META_KEY] = createImage("/shift.png");
metaKeys[CAPS_META_KEY] = createImage("/caps.png");
metaKeys[MODE_META_KEY] = createImage("/mode.png");
// metaKeys[CNINPUT_META_KEY] = createImage("cn.png");
}
}
private Image createImage(String name) {
Image image = Toolkit.getDefaultToolkit().getImage(getClass().getResource(name));
return image;
}
/**
* Checks if the virtual keyboard is enabled.
*
* @return <code>true</code> if the virtual keyboard is enabled,
* <code>false</code> otherwise.
*/
static boolean isKeyboardEnabled() {
return USE_VIRTUAL_KEYBOARD;
}
/**
* Checks if the virtual keyboard is enabled.
*
* @return <code>true</code> if the virtual keyboard is enabled,
* <code>false</code> otherwise.
*/
static boolean isKeypadEnabled() {
return USE_VIRTUAL_KEYPAD;
}
/**
* Checks if the virtual keyboard is opened automatically.
*
* @return <code>true</code> if the virtual keyboard is opened
* automatically, <code>false</code> otherwise.
*/
static boolean isAutoOpen() {
return USE_VIRTUAL_KEYBOARD_OPEN_AUTO;
}
/**
* traverse the virtual keyboard according to key pressed.
*
* @param type type of keypress
* @param keyCode key code of key pressed
*/
void traverse(int type, int keyCode) {
System.err.println("VirtualK: keyCode=" + keyCode);
// Soft button means dismiss to the virtual keyboard
if (type == RELEASED && keyCode == 461) {
vkl.virtualKeyEntered(type, (char) 0);
return;
}
if (!inMetaKeys) {
if (type == RELEASED &&
keyCode != KeyEvent.VK_ENTER) {
// in this case we don't want to traverse on key release
} else {
switch (keyCode) {
case KeyEvent.VK_RIGHT:
currentChar++;
if (currentChar > keys[currentKeyboard].length - 1) {
currentChar = 0;
}
break;
case KeyEvent.VK_LEFT:
currentChar--;
if (currentChar < 0) {
currentChar = keys[currentKeyboard].length - 1;
}
break;
case KeyEvent.VK_UP:
currentChar = (currentChar - maxColumns);
if (currentChar < 0) {
currentChar = currentChar + (fullColumns + 1) * maxColumns;
if (currentChar > keys[currentKeyboard].length - 1) {
currentChar -= maxColumns;
}
}
break;
case KeyEvent.VK_DOWN:
currentChar = (currentChar + maxColumns);
if (currentChar > keys[currentKeyboard].length - 1) {
currentChar = (currentChar - maxColumns);
inMetaKeys = true;
}
break;
case KeyEvent.VK_ENTER:
// System.out.println("Key Selected - type :" + type + ", "
// + keys[currentKeyboard][currentChar]);
vkl.virtualKeyEntered(type, keys[currentKeyboard][currentChar]);
if (inShift && type == PRESSED) {
// shift is a one-shot upper case
inShift = false;
if (textKbd) {
currentKeyboard = 1;
vkl.virtualMetaKeyEntered(IM_CHANGED_KEY);
} // hk : still need a keyboard displayed
else {
currentKeyboard = 0;
}
}
break;
}
}
} else {
if (type != RELEASED) {
// meta keys
switch (keyCode) {
case KeyEvent.VK_RIGHT:
currentMeta++;
if (currentMeta > metaKeys.length - 1) {
currentMeta = 0;
}
break;
case KeyEvent.VK_LEFT:
currentMeta--;
if (currentMeta < 0) {
currentMeta = metaKeys.length - 1;
}
break;
case KeyEvent.VK_UP:
inMetaKeys = false;
break;
case KeyEvent.VK_DOWN:
inMetaKeys = false;
currentChar = (currentChar + maxColumns);
if (currentChar > keys[currentKeyboard].length - 1) {
currentChar = currentChar % maxColumns;
}
break;
case KeyEvent.VK_ENTER:
switch (currentMeta) {
case SHIFT_META_KEY: // "Shift" - one shot upper case
if (currentKeyboard == 1) { // lower case
currentKeyboard = 2;
vkl.virtualMetaKeyEntered(IM_CHANGED_KEY);
}
inShift = true;
inMetaKeys = false;
break;
case CAPS_META_KEY: // "CapsL" (caps lock)
if (currentKeyboard == 1) { // lower case
currentKeyboard = 2;
} else if (currentKeyboard == 2) { // upper case
currentKeyboard = 1;
}
vkl.virtualMetaKeyEntered(IM_CHANGED_KEY);
break;
case MODE_META_KEY: // "Mode"
currentKeyboard = (currentKeyboard + 1) % keys.length;
vkl.virtualMetaKeyEntered(IM_CHANGED_KEY);
break;
case BACKSPACE_META_KEY: // "backspace"
vkl.virtualMetaKeyEntered(BACKSPACE_META_KEY);
break;
case OK_META_KEY: // "ok"
vkl.virtualMetaKeyEntered(OK_META_KEY);
break;
case CANCEL_META_KEY: // "cancel"
vkl.virtualMetaKeyEntered(CANCEL_META_KEY);
break;
case CNINPUT_META_KEY: // "cn_input"
vkl.virtualMetaKeyEntered(CNINPUT_META_KEY);
break;
}
}
}
}
if (type != RELEASED) {
if (461 == keyCode) {
vkl.virtualMetaKeyEntered(BACKSPACE_META_KEY);
} else {
switch (keyCode) {
// Short cuts by number keys
case 461:
vkl.virtualMetaKeyEntered(CNINPUT_META_KEY);
break;
case KeyEvent.VK_ENTER:
currentKeyboard = (currentKeyboard + 1) % keys.length;
vkl.virtualMetaKeyEntered(IM_CHANGED_KEY);
break;
case KeyEvent.VK_NUMPAD2:
vkl.virtualMetaKeyEntered(CURSOR_UP_META_KEY);
break;
case KeyEvent.VK_NUMPAD4:
vkl.virtualMetaKeyEntered(CURSOR_LEFT_META_KEY);
break;
case KeyEvent.VK_NUMPAD6:
vkl.virtualMetaKeyEntered(CURSOR_RIGHT_META_KEY);
break;
case KeyEvent.VK_NUMPAD8:
vkl.virtualMetaKeyEntered(CURSOR_DOWN_META_KEY);
break;
case KeyEvent.VK_NUMPAD5:
vkl.virtualMetaKeyEntered(CNINPUT_SELECT_META_KEY);
break;
}
}
}
// triggers paint()
vkl.repaintVK();
}
/**
* paint the virtual keyboard on the screen
*
* @param g The graphics context to paint to
*/
protected void paint(Graphics g) {
int actualHeight = kbHeight + candidateFieldHeight;
g.setFont(f);
g.setColor(Color.lightGray);
g.fillRect(0, 0, kbWidth, actualHeight);
drawBorder(g, 0, 0, kbWidth - 1, actualHeight - 1);
if (candidateFieldHeight > 0) {
drawCandidateBar(g);
}
g.translate(0, candidateFieldHeight);
if (textfieldHeight > 0) {
drawTextField(g);
}
g.translate(0, textfieldHeight);
drawKeys(g);
g.translate(0, actualHeight - (IMAGE_SIZE + 4 * PADDING + 2 * META_PADDING) - textfieldHeight - candidateFieldHeight);
if (textKbd) {
drawMetaKeys(g);
}
}
/**
* Draw the text field of the virtual keyboard.
*
* @param g The graphics context to paint to
*/
void drawTextField(Graphics g) {
drawSunkedBorder(g, PADDING, PADDING, kbWidth - 2 * PADDING, textfieldHeight);
g.setClip(0, 0, kbWidth - 2 * PADDING, textfieldHeight);
g.translate(PADDING + 1, 0);
vkl.paintTextOnly(g, kbWidth, textfieldHeight);
g.translate(-PADDING - 1, 0);
g.setClip(0, 0, kbWidth, kbHeight);
}
void drawCandidateBar(Graphics g) {
g.setClip(0, 0, kbWidth - 2 * PADDING, candidateFieldHeight);
g.translate(PADDING + 1, 2 * PADDING);
vkl.paintCandidateBar(g, kbWidth - 3 * PADDING, candidateFieldHeight - 2 * PADDING);
g.translate(-PADDING - 1, -PADDING);
g.setClip(0, 0, kbWidth, kbHeight);
}
/**
* draw keyboard keys
*
* @param g The graphics context to paint to
*/
void drawKeys(Graphics g) {
int tmp;
if (!textKbd) {
currentKeyboard = 0;
}
for (int i = 0; i < maxRows; i++) {
for (int j = 0; j < maxColumns; j++) {
tmp = i * maxColumns + j;
if (tmp >= keys[currentKeyboard].length) {
// no more chars to draw
break;
}
if (currentChar == tmp && inMetaKeys == false) {
drawButton(g, j * (PADDING + buttonW) + PADDING, i * (PADDING + buttonH) + PADDING, buttonW, buttonH);
} else {
drawBeveledButton(g, j * (PADDING + buttonW) + PADDING, i * (PADDING + buttonH) + PADDING, buttonW, buttonH);
}
// g.setColor(DARK_GRAY);
g.setColor(TEXT_COLOR);
g.drawString("" + keys[currentKeyboard][tmp], j * (PADDING + buttonW) + PADDING + fontWCenter, i * (PADDING + buttonH) + PADDING + fontHTop);
}
}
}
/**
* draw keyboard meta keys
*
* @param g The graphics context to paint to
*/
void drawMetaKeys(Graphics g) {
int mkWidth = metaKeys.length * (IMAGE_SIZE + 3 * META_PADDING) + META_PADDING;
int currX = (kbWidth - mkWidth) / 2 + 2 * META_PADDING;
int currY = 0;
if (inMetaKeys) {
drawBorder(g, currX - 2 * META_PADDING, // x1
currY - 2 * META_PADDING, // y1
currX + mkWidth,
currY + IMAGE_SIZE + 2 * META_PADDING);
}
for (int i = 0; i < metaKeys.length; i++) {
if (currX + IMAGE_SIZE > kbWidth) {
currX = PADDING;
currY -= (IMAGE_SIZE + META_PADDING);
}
if (inMetaKeys && i == currentMeta) {
drawButton(g, currX, currY, IMAGE_SIZE + 2 * META_PADDING, IMAGE_SIZE + 2 * META_PADDING);
} else {
drawBeveledButton(g, currX, currY, IMAGE_SIZE + 2 * META_PADDING, IMAGE_SIZE + 2 * META_PADDING);
}
g.drawImage(metaKeys[i], currX + META_PADDING, currY + META_PADDING, null);
currX += (IMAGE_SIZE + 2 * META_PADDING + 2);
if (currX > kbWidth) {
currX = META_PADDING;
currY -= (IMAGE_SIZE + META_PADDING);
}
}
}
/**
* draw a border
*
* @param g The graphics context to paint to
* @param x1 x-coordinate of the button's location
* @param y1 y-coordinate of the button's location
* @param x2 the x-coordinate at the width of the border
* @param y2 the y-coordinate at the height of the border
*/
private void drawBorder(Graphics g, int x1, int y1, int x2, int y2) {
g.setColor(Color.gray);
g.drawLine(x1 + 2, y1 + 2, x1 + 2, y2 - 3); // left
g.drawLine(x1 + 2, y1 + 2, x2 - 2, y1 + 2); // top
g.drawLine(x1 + 2, y2 - 1, x2 - 1, y2 - 1); // bottom
g.drawLine(x2 - 1, y1 + 2, x2 - 1, y2 - 1); // right
g.setColor(Color.white);
g.drawRect(x1 + 1, y1 + 1, x2 - x1 - 3, y2 - y1 - 3);
}
/**
* draw a sunken border
*
* @param g The graphics context to paint to
* @param x1 x-coordinate of the button's location
* @param y1 y-coordinate of the button's location
* @param x2 the x-coordinate at the width of the border
* @param y2 the y-coordinate at the height of the border
*/
private void drawSunkedBorder(Graphics g, int x1, int y1, int x2, int y2) {
g.setColor(Color.white);
g.fillRect(x1 + 2, y1 + 2, x2 - x1 - 2, y2 - y1 - 2);
g.setColor(Color.gray);
g.drawLine(x1 + 2, y1 + 2, x1 + 2, y2 - 2); // left
g.drawLine(x1 + 2, y1 + 2, x2 - 2, y1 + 2); // top
g.setColor(Color.darkGray);
g.drawLine(x1 + 3, y1 + 3, x1 + 3, y2 - 3); // left
g.drawLine(x1 + 3, y1 + 3, x2 - 3, y1 + 3); // top
g.setColor(Color.lightGray);
g.drawLine(x1 + 3, y2 - 2, x2 - 2, y2 - 2); // bottom
g.drawLine(x2 - 2, y1 + 3, x2 - 2, y2 - 2); // right
}
/**
* draw a button
*
* @param g The graphics context to paint to
* @param x x-coordinate of the button's location
* @param y y-coordinate of the button's location
* @param w the width of the button
* @param h the height of the button
*/
private void drawButton(Graphics g, int x, int y, int w, int h) {
g.setColor(Color.gray);
g.drawLine(x + 1, y + h - 1, x + w, y + h - 1); // bottom
g.drawLine(x + w - 1, y + 1, x + w - 1, y + h); // right
g.setColor(Color.darkGray);
g.drawLine(x, y + h, x + w, y + h); // bottom
g.drawLine(x + w, y, x + w, y + h); // right
g.setColor(Color.white);
g.drawLine(x, y, x + w - 1, y);
g.drawLine(x, y, x, y + h - 1);
}
/**
* draw a beveled button
*
* @param g The graphics context to paint to
* @param x x-coordinate of the button's location
* @param y y-coordinate of the button's location
* @param w the width of the button
* @param h the height of the button
*/
private void drawBeveledButton(Graphics g, int x, int y, int w, int h) {
g.setColor(Color.gray);
g.drawLine(x + 1, y + h - 1, x + w, y + h - 1); // bottom
g.drawLine(x + w - 1, y + 1, x + w - 1, y + h); // right
g.setColor(Color.white);
g.drawLine(x, y + h, x + w, y + h); // bottom
g.drawLine(x + w, y, x + w, y + h); // right
g.setColor(Color.gray);
g.drawLine(x, y, x + w - 1, y);
g.drawLine(x, y, x, y + h - 1);
g.setColor(Color.white);
g.drawLine(x + 1, y + 1, x + w - 2, y + 1);
g.drawLine(x + 1, y + 1, x + 1, y + h - 2);
}
/**
* Helper function to determine the itemIndex at the x,y position
*
* @param x,y pointer coordinates in menuLayer's space (0,0 means left-top
* corner) both value can be negative as menuLayer handles the
* pointer event outside its bounds
* @return menuItem's index since 0, or PRESS_OUT_OF_BOUNDS, PRESS_ON_TITLE
*
*/
private boolean isKeyAtPointerPosition(int x, int y) {
int tmpX, tmpY, tmp;
for (int i = 0; i < maxRows; i++) {
for (int j = 0; j < maxColumns; j++) {
tmp = i * maxColumns + j;
if (tmp >= keys[currentKeyboard].length) {
// no more chars to draw
break;
}
tmpX = x - (j * (PADDING + buttonW) + PADDING);
tmpY = y - (i * (PADDING + buttonH) + PADDING) - textfieldHeight;
if ((tmpX >= 0) && (tmpY >= 0) && (tmpX < buttonW) && (tmpY < buttonH)) {
currentChar = tmp;
inMetaKeys = false;
return true;
}
}
}
if (metaKeys == null) {
return false;
}
// Check for meta chars
int mkWidth = metaKeys.length * (IMAGE_SIZE + 3 * META_PADDING) + META_PADDING;
int currX = (kbWidth - mkWidth) / 2 + 2 * META_PADDING;
int currY = kbHeight - (IMAGE_SIZE + 6 * META_PADDING);
for (int i = 0; i < metaKeys.length; i++) {
if (currX + IMAGE_SIZE > kbWidth) {
currX = PADDING;
currY -= (IMAGE_SIZE + META_PADDING);
}
tmpX = x - currX;
tmpY = y - currY;
if ((tmpX >= 0) && (tmpY >= 0) && (tmpX < (IMAGE_SIZE + 2 * META_PADDING)) && (tmpY < (IMAGE_SIZE + 2 * META_PADDING))) {
currentMeta = i;
inMetaKeys = true;
return true;
}
currX += (IMAGE_SIZE + 2 * META_PADDING + 2);
if (currX > kbWidth) {
currX = META_PADDING;
currY -= (IMAGE_SIZE + META_PADDING);
}
}
return false;
}
/**
* Handle input from a pen tap. Parameters describe the type of pen event
* and the x,y location in the layer at which the event occurred. Important
* : the x,y location of the pen tap will already be translated into the
* coordinate space of the layer.
*
* @param type the type of pen event
* @param x the x coordinate of the event
* @param y the y coordinate of the event
*/
public boolean pointerInput(int type, int x, int y) {
switch (type) {
case PRESSED:
// dismiss the menu layer if the user pressed outside the menu
if (isKeyAtPointerPosition(x, y)) {
// press on valid key
traverse(type, KeyEvent.VK_ENTER);
vkl.repaintVK();
}
break;
case RELEASED:
if (isKeyAtPointerPosition(x, y)) {
traverse(type, KeyEvent.VK_ENTER);
vkl.repaintVK();
}
break;
}
// return true always as menuLayer will capture all of the pointer
// inputs
return true;
}
// ********* attributes ********* //
private final static Color TEXT_COLOR = Color.black;
/** padding between rows of buttons */
private int PADDING;
/** padding used by the meta keys */
private final static int META_PADDING = 2;
/** size of meta icons */
private final static int IMAGE_SIZE = 13;
// If you want to change the order of the buttons, just
// change the serial numbers here:
final static int OK_META_KEY = 0;
final static int CANCEL_META_KEY = 1;
final static int MODE_META_KEY = 2;
final static int BACKSPACE_META_KEY = 3;
final static int SHIFT_META_KEY = 4;
final static int CAPS_META_KEY = 5;
final static int CNINPUT_META_KEY = 6;
final static int CURSOR_UP_META_KEY = 7;
final static int CURSOR_DOWN_META_KEY = 8;
final static int CURSOR_LEFT_META_KEY = 9;
final static int CURSOR_RIGHT_META_KEY = 10;
final static int CNINPUT_SELECT_META_KEY = 11;
// When input method is changed, process this key to update UI
final static int IM_CHANGED_KEY = 99;
}
class VirtualKeyboardException extends Exception {
/**
* Constructs an <code>IOException</code> with <code>null</code> as its
* error detail message.
*/
public VirtualKeyboardException() {
super();
}
/**
* Constructs an <code>IOException</code> with the specified detail message.
* The error message string <code>s</code> can later be retrieved by the
* <code>{@link java.lang.Throwable#getMessage}</code> method of class
* <code>java.lang.Throwable</code>.
*
* @param s the detail message.
*/
public VirtualKeyboardException(String s) {
super(s);
}
}