387 lines
13 KiB
Java
387 lines
13 KiB
Java
|
/*
|
||
|
* Copyright (C) 2010 Google Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||
|
* the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||
|
* License for the specific language governing permissions and limitations under
|
||
|
* the License.
|
||
|
*/
|
||
|
|
||
|
package com.android.inputmethod.keyboard;
|
||
|
|
||
|
import com.android.inputmethod.latin.R;
|
||
|
|
||
|
import org.xmlpull.v1.XmlPullParserException;
|
||
|
|
||
|
import android.content.Context;
|
||
|
import android.content.res.Resources;
|
||
|
import android.content.res.XmlResourceParser;
|
||
|
import android.graphics.drawable.Drawable;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.List;
|
||
|
import java.util.Map;
|
||
|
|
||
|
/**
|
||
|
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
|
||
|
* consists of rows of keys.
|
||
|
* <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
|
||
|
* <pre>
|
||
|
* <Keyboard
|
||
|
* latin:keyWidth="%10p"
|
||
|
* latin:keyHeight="50px"
|
||
|
* latin:horizontalGap="2px"
|
||
|
* latin:verticalGap="2px" >
|
||
|
* <Row latin:keyWidth="32px" >
|
||
|
* <Key latin:keyLabel="A" />
|
||
|
* ...
|
||
|
* </Row>
|
||
|
* ...
|
||
|
* </Keyboard>
|
||
|
* </pre>
|
||
|
*/
|
||
|
public class Keyboard {
|
||
|
|
||
|
static final String TAG = "Keyboard";
|
||
|
|
||
|
public static final int EDGE_LEFT = 0x01;
|
||
|
public static final int EDGE_RIGHT = 0x02;
|
||
|
public static final int EDGE_TOP = 0x04;
|
||
|
public static final int EDGE_BOTTOM = 0x08;
|
||
|
|
||
|
public static final int KEYCODE_SHIFT = -1;
|
||
|
public static final int KEYCODE_MODE_CHANGE = -2;
|
||
|
public static final int KEYCODE_CANCEL = -3;
|
||
|
public static final int KEYCODE_DONE = -4;
|
||
|
public static final int KEYCODE_DELETE = -5;
|
||
|
public static final int KEYCODE_ALT = -6;
|
||
|
|
||
|
/** Horizontal gap default for all rows */
|
||
|
int mDefaultHorizontalGap;
|
||
|
|
||
|
/** Default key width */
|
||
|
int mDefaultWidth;
|
||
|
|
||
|
/** Default key height */
|
||
|
int mDefaultHeight;
|
||
|
|
||
|
/** Default gap between rows */
|
||
|
int mDefaultVerticalGap;
|
||
|
|
||
|
/** Is the keyboard in the shifted state */
|
||
|
private boolean mShifted;
|
||
|
|
||
|
/** List of shift keys in this keyboard */
|
||
|
private final List<Key> mShiftKeys = new ArrayList<Key>();
|
||
|
|
||
|
/** List of shift keys and its shifted state icon */
|
||
|
private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
|
||
|
|
||
|
/** Total height of the keyboard, including the padding and keys */
|
||
|
private int mTotalHeight;
|
||
|
|
||
|
/**
|
||
|
* Total width of the keyboard, including left side gaps and keys, but not any gaps on the
|
||
|
* right side.
|
||
|
*/
|
||
|
private int mTotalWidth;
|
||
|
|
||
|
/** List of keys in this keyboard */
|
||
|
private final List<Key> mKeys = new ArrayList<Key>();
|
||
|
|
||
|
/** Width of the screen available to fit the keyboard */
|
||
|
final int mDisplayWidth;
|
||
|
|
||
|
/** Height of the screen */
|
||
|
final int mDisplayHeight;
|
||
|
|
||
|
protected final KeyboardId mId;
|
||
|
|
||
|
// Variables for pre-computing nearest keys.
|
||
|
|
||
|
public final int GRID_WIDTH;
|
||
|
public final int GRID_HEIGHT;
|
||
|
private final int GRID_SIZE;
|
||
|
private int mCellWidth;
|
||
|
private int mCellHeight;
|
||
|
private int[][] mGridNeighbors;
|
||
|
private int mProximityThreshold;
|
||
|
private static int[] EMPTY_INT_ARRAY = new int[0];
|
||
|
/** Number of key widths from current touch point to search for nearest keys. */
|
||
|
private static float SEARCH_DISTANCE = 1.2f;
|
||
|
|
||
|
/**
|
||
|
* Creates a keyboard from the given xml key layout file.
|
||
|
* @param context the application or service context
|
||
|
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
|
||
|
*/
|
||
|
public Keyboard(Context context, int xmlLayoutResId) {
|
||
|
this(context, xmlLayoutResId, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a keyboard from the given keyboard identifier.
|
||
|
* @param context the application or service context
|
||
|
* @param id keyboard identifier
|
||
|
*/
|
||
|
public Keyboard(Context context, KeyboardId id) {
|
||
|
this(context, id.getXmlId(), id);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a keyboard from the given xml key layout file.
|
||
|
* @param context the application or service context
|
||
|
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
|
||
|
* @param id keyboard identifier
|
||
|
*/
|
||
|
private Keyboard(Context context, int xmlLayoutResId, KeyboardId id) {
|
||
|
this(context, xmlLayoutResId, id,
|
||
|
context.getResources().getDisplayMetrics().widthPixels,
|
||
|
context.getResources().getDisplayMetrics().heightPixels);
|
||
|
}
|
||
|
|
||
|
private Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width,
|
||
|
int height) {
|
||
|
Resources res = context.getResources();
|
||
|
GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
|
||
|
GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
|
||
|
GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
|
||
|
|
||
|
mDisplayWidth = width;
|
||
|
mDisplayHeight = height;
|
||
|
|
||
|
mDefaultHorizontalGap = 0;
|
||
|
setKeyWidth(mDisplayWidth / 10);
|
||
|
mDefaultVerticalGap = 0;
|
||
|
mDefaultHeight = mDefaultWidth;
|
||
|
mId = id;
|
||
|
loadKeyboard(context, xmlLayoutResId);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>Creates a blank keyboard from the given resource file and populates it with the specified
|
||
|
* characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
|
||
|
* </p>
|
||
|
* <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
|
||
|
* possible in each row.</p>
|
||
|
* @param context the application or service context
|
||
|
* @param layoutTemplateResId the layout template file, containing no keys.
|
||
|
* @param characters the list of characters to display on the keyboard. One key will be created
|
||
|
* for each character.
|
||
|
* @param columns the number of columns of keys to display. If this number is greater than the
|
||
|
* number of keys that can fit in a row, it will be ignored. If this number is -1, the
|
||
|
* keyboard will fit as many keys as possible in each row.
|
||
|
*/
|
||
|
public Keyboard(Context context, int layoutTemplateResId,
|
||
|
CharSequence characters, int columns, int horizontalPadding) {
|
||
|
this(context, layoutTemplateResId);
|
||
|
int x = 0;
|
||
|
int y = 0;
|
||
|
int column = 0;
|
||
|
mTotalWidth = 0;
|
||
|
|
||
|
Row row = new Row(this);
|
||
|
row.defaultHeight = mDefaultHeight;
|
||
|
row.defaultWidth = mDefaultWidth;
|
||
|
row.defaultHorizontalGap = mDefaultHorizontalGap;
|
||
|
row.verticalGap = mDefaultVerticalGap;
|
||
|
row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
|
||
|
final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
|
||
|
for (int i = 0; i < characters.length(); i++) {
|
||
|
char c = characters.charAt(i);
|
||
|
if (column >= maxColumns
|
||
|
|| x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
|
||
|
x = 0;
|
||
|
y += mDefaultVerticalGap + mDefaultHeight;
|
||
|
column = 0;
|
||
|
}
|
||
|
final Key key = new Key(row);
|
||
|
// Horizontal gap is divided equally to both sides of the key.
|
||
|
key.x = x + key.gap / 2;
|
||
|
key.y = y;
|
||
|
key.label = String.valueOf(c);
|
||
|
key.codes = new int[] { c };
|
||
|
column++;
|
||
|
x += key.width + key.gap;
|
||
|
mKeys.add(key);
|
||
|
if (x > mTotalWidth) {
|
||
|
mTotalWidth = x;
|
||
|
}
|
||
|
}
|
||
|
mTotalHeight = y + mDefaultHeight;
|
||
|
}
|
||
|
|
||
|
public KeyboardId getKeyboardId() {
|
||
|
return mId;
|
||
|
}
|
||
|
|
||
|
public List<Key> getKeys() {
|
||
|
return mKeys;
|
||
|
}
|
||
|
|
||
|
protected int getHorizontalGap() {
|
||
|
return mDefaultHorizontalGap;
|
||
|
}
|
||
|
|
||
|
protected void setHorizontalGap(int gap) {
|
||
|
mDefaultHorizontalGap = gap;
|
||
|
}
|
||
|
|
||
|
protected int getVerticalGap() {
|
||
|
return mDefaultVerticalGap;
|
||
|
}
|
||
|
|
||
|
protected void setVerticalGap(int gap) {
|
||
|
mDefaultVerticalGap = gap;
|
||
|
}
|
||
|
|
||
|
protected int getKeyHeight() {
|
||
|
return mDefaultHeight;
|
||
|
}
|
||
|
|
||
|
protected void setKeyHeight(int height) {
|
||
|
mDefaultHeight = height;
|
||
|
}
|
||
|
|
||
|
protected int getKeyWidth() {
|
||
|
return mDefaultWidth;
|
||
|
}
|
||
|
|
||
|
protected void setKeyWidth(int width) {
|
||
|
mDefaultWidth = width;
|
||
|
final int threshold = (int) (width * SEARCH_DISTANCE);
|
||
|
mProximityThreshold = threshold * threshold;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the total height of the keyboard
|
||
|
* @return the total height of the keyboard
|
||
|
*/
|
||
|
public int getHeight() {
|
||
|
return mTotalHeight;
|
||
|
}
|
||
|
|
||
|
public int getMinWidth() {
|
||
|
return mTotalWidth;
|
||
|
}
|
||
|
|
||
|
public int getKeyboardHeight() {
|
||
|
return mDisplayHeight;
|
||
|
}
|
||
|
|
||
|
public int getKeyboardWidth() {
|
||
|
return mDisplayWidth;
|
||
|
}
|
||
|
|
||
|
public boolean setShifted(boolean shiftState) {
|
||
|
for (final Key key : mShiftKeys) {
|
||
|
key.on = shiftState;
|
||
|
}
|
||
|
if (mShifted != shiftState) {
|
||
|
mShifted = shiftState;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean isShiftedOrShiftLocked() {
|
||
|
return mShifted;
|
||
|
}
|
||
|
|
||
|
public List<Key> getShiftKeys() {
|
||
|
return mShiftKeys;
|
||
|
}
|
||
|
|
||
|
public Map<Key, Drawable> getShiftedIcons() {
|
||
|
return mShiftedIcons;
|
||
|
}
|
||
|
|
||
|
private void computeNearestNeighbors() {
|
||
|
// Round-up so we don't have any pixels outside the grid
|
||
|
mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
|
||
|
mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
|
||
|
mGridNeighbors = new int[GRID_SIZE][];
|
||
|
final int[] indices = new int[mKeys.size()];
|
||
|
final int gridWidth = GRID_WIDTH * mCellWidth;
|
||
|
final int gridHeight = GRID_HEIGHT * mCellHeight;
|
||
|
final int threshold = mProximityThreshold;
|
||
|
for (int x = 0; x < gridWidth; x += mCellWidth) {
|
||
|
for (int y = 0; y < gridHeight; y += mCellHeight) {
|
||
|
final int centerX = x + mCellWidth / 2;
|
||
|
final int centerY = y + mCellHeight / 2;
|
||
|
int count = 0;
|
||
|
for (int i = 0; i < mKeys.size(); i++) {
|
||
|
final Key key = mKeys.get(i);
|
||
|
if (key.squaredDistanceToEdge(centerX, centerY) < threshold)
|
||
|
indices[count++] = i;
|
||
|
}
|
||
|
final int[] cell = new int[count];
|
||
|
System.arraycopy(indices, 0, cell, 0, count);
|
||
|
mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the indices of the keys that are closest to the given point.
|
||
|
* @param x the x-coordinate of the point
|
||
|
* @param y the y-coordinate of the point
|
||
|
* @return the array of integer indices for the nearest keys to the given point. If the given
|
||
|
* point is out of range, then an array of size zero is returned.
|
||
|
*/
|
||
|
public int[] getNearestKeys(int x, int y) {
|
||
|
if (mGridNeighbors == null) computeNearestNeighbors();
|
||
|
if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
|
||
|
int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
|
||
|
if (index < GRID_SIZE) {
|
||
|
return mGridNeighbors[index];
|
||
|
}
|
||
|
}
|
||
|
return EMPTY_INT_ARRAY;
|
||
|
}
|
||
|
|
||
|
// TODO should be private
|
||
|
protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
|
||
|
return new Row(res, this, parser);
|
||
|
}
|
||
|
|
||
|
// TODO should be private
|
||
|
protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
|
||
|
XmlResourceParser parser, KeyStyles keyStyles) {
|
||
|
return new Key(res, parent, x, y, parser, keyStyles);
|
||
|
}
|
||
|
|
||
|
private void loadKeyboard(Context context, int xmlLayoutResId) {
|
||
|
try {
|
||
|
final Resources res = context.getResources();
|
||
|
KeyboardParser parser = new KeyboardParser(this, res);
|
||
|
parser.parseKeyboard(res.getXml(xmlLayoutResId));
|
||
|
// mTotalWidth is the width of this keyboard which is maximum width of row.
|
||
|
mTotalWidth = parser.getMaxRowWidth();
|
||
|
mTotalHeight = parser.getTotalHeight();
|
||
|
} catch (XmlPullParserException e) {
|
||
|
Log.w(TAG, "keyboard XML parse error: " + e);
|
||
|
throw new IllegalArgumentException(e);
|
||
|
} catch (IOException e) {
|
||
|
Log.w(TAG, "keyboard XML parse error: " + e);
|
||
|
throw new RuntimeException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected static void setDefaultBounds(Drawable drawable) {
|
||
|
if (drawable != null)
|
||
|
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
|
||
|
drawable.getIntrinsicHeight());
|
||
|
}
|
||
|
}
|