summaryrefslogtreecommitdiff
path: root/java/src/com/android/inputmethod/keyboard/Keyboard.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/keyboard/Keyboard.java')
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java386
1 files changed, 386 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
new file mode 100644
index 000000000..b75415579
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -0,0 +1,386 @@
+/*
+ * 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>
+ * &lt;Keyboard
+ * latin:keyWidth="%10p"
+ * latin:keyHeight="50px"
+ * latin:horizontalGap="2px"
+ * latin:verticalGap="2px" &gt;
+ * &lt;Row latin:keyWidth="32px" &gt;
+ * &lt;Key latin:keyLabel="A" /&gt;
+ * ...
+ * &lt;/Row&gt;
+ * ...
+ * &lt;/Keyboard&gt;
+ * </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());
+ }
+}