summaryrefslogtreecommitdiff
path: root/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
diff options
context:
space:
mode:
authorTadashi G. Takaoka <takaoka@google.com>2010-12-02 03:07:43 -0800
committerAndroid (Google) Code Review <android-gerrit@google.com>2010-12-02 03:07:43 -0800
commit26dae3f4e8ffd0f25b78c58598752cd393419bcc (patch)
treefd7280e7e0c0c879958a5c7d8b9376942d447e9b /java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
parent18c28f431eadc1b451ca25d14fd683db4b234838 (diff)
parent5a309f57155fb95667c2ccdda730eaf175de8876 (diff)
Merge "Move some inner static class to top class in new package"
Diffstat (limited to 'java/src/com/android/inputmethod/keyboard/LatinKeyboard.java')
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboard.java543
1 files changed, 543 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
new file mode 100644
index 000000000..29f749a46
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.LatinIME;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class LatinKeyboard extends Keyboard {
+
+ private static final boolean DEBUG_PREFERRED_LETTER = false;
+ private static final String TAG = "LatinKeyboard";
+
+ public static final int KEYCODE_OPTIONS = -100;
+ public static final int KEYCODE_OPTIONS_LONGPRESS = -101;
+ // TODO: remove this once LatinIME stops referring to this.
+ public static final int KEYCODE_VOICE = -102;
+ public static final int KEYCODE_NEXT_LANGUAGE = -104;
+ public static final int KEYCODE_PREV_LANGUAGE = -105;
+ public static final int KEYCODE_CAPSLOCK = -106;
+
+ static final int OPACITY_FULLY_OPAQUE = 255;
+ private static final int SPACE_LED_LENGTH_PERCENT = 80;
+
+ private Drawable mShiftLockPreviewIcon;
+ private final HashMap<Key, Drawable> mNormalShiftIcons = new HashMap<Key, Drawable>();
+ private Drawable mSpaceIcon;
+ private Drawable mSpaceAutoCompletionIndicator;
+ private Drawable mSpacePreviewIcon;
+ private final Drawable mButtonArrowLeftIcon;
+ private final Drawable mButtonArrowRightIcon;
+ private final int mSpaceBarTextShadowColor;
+ private Key mSpaceKey;
+ private int mSpaceKeyIndex = -1;
+ private int mSpaceDragStartX;
+ private int mSpaceDragLastDiff;
+ private final Resources mRes;
+ private final Context mContext;
+ private boolean mCurrentlyInSpace;
+ private SlidingLocaleDrawable mSlidingLocaleIcon;
+ private int[] mPrefLetterFrequencies;
+ private int mPrefLetter;
+ private int mPrefLetterX;
+ private int mPrefLetterY;
+ private int mPrefDistance;
+
+ private LatinKeyboardShiftState mShiftState = new LatinKeyboardShiftState();
+
+ private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f;
+ private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f;
+ private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f;
+ // Minimum width of space key preview (proportional to keyboard width)
+ private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f;
+ // Height in space key the language name will be drawn. (proportional to space key height)
+ public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
+ // If the full language name needs to be smaller than this value to be drawn on space key,
+ // its short language name will be used instead.
+ private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
+
+ private static int sSpacebarVerticalCorrection;
+
+ public LatinKeyboard(Context context, KeyboardId id) {
+ super(context, id);
+ final Resources res = context.getResources();
+ mContext = context;
+ mRes = res;
+ if (id.mColorScheme == KeyboardView.COLOR_SCHEME_BLACK) {
+ mSpaceBarTextShadowColor = res.getColor(
+ R.color.latinkeyboard_bar_language_shadow_black);
+ } else { // default color scheme is KeyboardView.COLOR_SCHEME_WHITE
+ mSpaceBarTextShadowColor = res.getColor(
+ R.color.latinkeyboard_bar_language_shadow_white);
+ }
+ mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
+ setDefaultBounds(mShiftLockPreviewIcon);
+ mSpaceAutoCompletionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
+ mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left);
+ mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right);
+ sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
+ R.dimen.spacebar_vertical_correction);
+ mSpaceKeyIndex = indexOf(LatinIME.KEYCODE_SPACE);
+ }
+
+ @Override
+ protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
+ XmlResourceParser parser, KeyStyles keyStyles) {
+ Key key = new LatinKey(res, parent, x, y, parser, keyStyles);
+ switch (key.codes[0]) {
+ case LatinIME.KEYCODE_SPACE:
+ mSpaceKey = key;
+ mSpaceIcon = key.icon;
+ mSpacePreviewIcon = key.iconPreview;
+ break;
+ }
+
+ return key;
+ }
+
+ public void enableShiftLock() {
+ for (final Key key : getShiftKeys()) {
+ if (key instanceof LatinKey) {
+ ((LatinKey)key).enableShiftLock();
+ }
+ mNormalShiftIcons.put(key, key.icon);
+ }
+ }
+
+ public boolean setShiftLocked(boolean newShiftLockState) {
+ final Map<Key, Drawable> shiftedIcons = getShiftedIcons();
+ for (final Key key : getShiftKeys()) {
+ key.on = newShiftLockState;
+ key.icon = newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key);
+ }
+ mShiftState.setShiftLocked(newShiftLockState);
+ return true;
+ }
+
+ public boolean isShiftLocked() {
+ return mShiftState.isShiftLocked();
+ }
+
+ @Override
+ public boolean setShifted(boolean newShiftState) {
+ if (getShiftKeys().size() == 0)
+ return super.setShifted(newShiftState);
+
+ final Map<Key, Drawable> shiftedIcons = getShiftedIcons();
+ for (final Key key : getShiftKeys()) {
+ if (!newShiftState && !mShiftState.isShiftLocked()) {
+ key.icon = mNormalShiftIcons.get(key);
+ } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) {
+ key.icon = shiftedIcons.get(key);
+ }
+ }
+ return mShiftState.setShifted(newShiftState);
+ }
+
+ @Override
+ public boolean isShiftedOrShiftLocked() {
+ if (getShiftKeys().size() > 0) {
+ return mShiftState.isShiftedOrShiftLocked();
+ } else {
+ return super.isShiftedOrShiftLocked();
+ }
+ }
+
+ public void setAutomaticTemporaryUpperCase() {
+ setShifted(true);
+ mShiftState.setAutomaticTemporaryUpperCase();
+ }
+
+ public boolean isAutomaticTemporaryUpperCase() {
+ return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase();
+ }
+
+ public boolean isManualTemporaryUpperCase() {
+ return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase();
+ }
+
+ public LatinKeyboardShiftState getKeyboardShiftState() {
+ return mShiftState;
+ }
+
+ public boolean isAlphaKeyboard() {
+ return mId.getXmlId() == R.xml.kbd_qwerty;
+ }
+
+ public boolean isPhoneKeyboard() {
+ return mId.mMode == KeyboardId.MODE_PHONE;
+ }
+
+ public boolean isNumberKeyboard() {
+ return mId.mMode == KeyboardId.MODE_NUMBER;
+ }
+
+ /**
+ * @return a key which should be invalidated.
+ */
+ public Key onAutoCompletionStateChanged(boolean isAutoCompletion) {
+ updateSpaceBarForLocale(isAutoCompletion);
+ return mSpaceKey;
+ }
+
+ private void updateSpaceBarForLocale(boolean isAutoCompletion) {
+ final Resources res = mRes;
+ // If application locales are explicitly selected.
+ if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) {
+ mSpaceKey.icon = new BitmapDrawable(res,
+ drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion));
+ } else {
+ // sym_keyboard_space_led can be shared with Black and White symbol themes.
+ if (isAutoCompletion) {
+ mSpaceKey.icon = new BitmapDrawable(res,
+ drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion));
+ } else {
+ mSpaceKey.icon = mSpaceIcon;
+ }
+ }
+ }
+
+ // Compute width of text with specified text size using paint.
+ private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) {
+ paint.setTextSize(textSize);
+ paint.getTextBounds(text, 0, text.length(), bounds);
+ return bounds.width();
+ }
+
+ // Layout local language name and left and right arrow on space bar.
+ private static String layoutSpaceBar(Paint paint, Locale locale, Drawable lArrow,
+ Drawable rArrow, int width, int height, float origTextSize,
+ boolean allowVariableTextSize) {
+ final float arrowWidth = lArrow.getIntrinsicWidth();
+ final float arrowHeight = lArrow.getIntrinsicHeight();
+ final float maxTextWidth = width - (arrowWidth + arrowWidth);
+ final Rect bounds = new Rect();
+
+ // Estimate appropriate language name text size to fit in maxTextWidth.
+ String language = SubtypeSwitcher.getDisplayLanguage(locale);
+ int textWidth = getTextWidth(paint, language, origTextSize, bounds);
+ // Assuming text width and text size are proportional to each other.
+ float textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f);
+
+ final boolean useShortName;
+ if (allowVariableTextSize) {
+ textWidth = getTextWidth(paint, language, textSize, bounds);
+ // If text size goes too small or text does not fit, use short name
+ useShortName = textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME
+ || textWidth > maxTextWidth;
+ } else {
+ useShortName = textWidth > maxTextWidth;
+ textSize = origTextSize;
+ }
+ if (useShortName) {
+ language = SubtypeSwitcher.getShortDisplayLanguage(locale);
+ textWidth = getTextWidth(paint, language, origTextSize, bounds);
+ textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f);
+ }
+ paint.setTextSize(textSize);
+
+ // Place left and right arrow just before and after language text.
+ final float baseline = height * SPACEBAR_LANGUAGE_BASELINE;
+ final int top = (int)(baseline - arrowHeight);
+ final float remains = (width - textWidth) / 2;
+ lArrow.setBounds((int)(remains - arrowWidth), top, (int)remains, (int)baseline);
+ rArrow.setBounds((int)(remains + textWidth), top, (int)(remains + textWidth + arrowWidth),
+ (int)baseline);
+
+ return language;
+ }
+
+ @SuppressWarnings("unused")
+ private Bitmap drawSpaceBar(int opacity, boolean isAutoCompletion) {
+ final int width = mSpaceKey.width;
+ final int height = mSpaceIcon.getIntrinsicHeight();
+ final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(buffer);
+ final Resources res = mRes;
+ canvas.drawColor(res.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
+
+ SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
+ // If application locales are explicitly selected.
+ if (subtypeSwitcher.needsToDisplayLanguage()) {
+ final Paint paint = new Paint();
+ paint.setAlpha(opacity);
+ paint.setAntiAlias(true);
+ paint.setTextAlign(Align.CENTER);
+
+ final boolean allowVariableTextSize = true;
+ final String language = layoutSpaceBar(paint, subtypeSwitcher.getInputLocale(),
+ mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height,
+ getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14),
+ allowVariableTextSize);
+
+ // Draw language text with shadow
+ final float baseline = height * SPACEBAR_LANGUAGE_BASELINE;
+ final float descent = paint.descent();
+ paint.setColor(mSpaceBarTextShadowColor);
+ canvas.drawText(language, width / 2, baseline - descent - 1, paint);
+ paint.setColor(res.getColor(R.color.latinkeyboard_bar_language_text));
+ canvas.drawText(language, width / 2, baseline - descent, paint);
+
+ // Put arrows that are already layed out on either side of the text
+ if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER
+ && subtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) {
+ mButtonArrowLeftIcon.draw(canvas);
+ mButtonArrowRightIcon.draw(canvas);
+ }
+ }
+
+ // Draw the spacebar icon at the bottom
+ if (isAutoCompletion) {
+ final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
+ final int iconHeight = mSpaceAutoCompletionIndicator.getIntrinsicHeight();
+ int x = (width - iconWidth) / 2;
+ int y = height - iconHeight;
+ mSpaceAutoCompletionIndicator.setBounds(x, y, x + iconWidth, y + iconHeight);
+ mSpaceAutoCompletionIndicator.draw(canvas);
+ } else {
+ final int iconWidth = mSpaceIcon.getIntrinsicWidth();
+ final int iconHeight = mSpaceIcon.getIntrinsicHeight();
+ int x = (width - iconWidth) / 2;
+ int y = height - iconHeight;
+ mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
+ mSpaceIcon.draw(canvas);
+ }
+ return buffer;
+ }
+
+ private void updateLocaleDrag(int diff) {
+ if (mSlidingLocaleIcon == null) {
+ final int width = Math.max(mSpaceKey.width,
+ (int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO));
+ final int height = mSpacePreviewIcon.getIntrinsicHeight();
+ mSlidingLocaleIcon =
+ new SlidingLocaleDrawable(mContext, mSpacePreviewIcon, width, height);
+ mSlidingLocaleIcon.setBounds(0, 0, width, height);
+ mSpaceKey.iconPreview = mSlidingLocaleIcon;
+ }
+ mSlidingLocaleIcon.setDiff(diff);
+ if (Math.abs(diff) == Integer.MAX_VALUE) {
+ mSpaceKey.iconPreview = mSpacePreviewIcon;
+ } else {
+ mSpaceKey.iconPreview = mSlidingLocaleIcon;
+ }
+ mSpaceKey.iconPreview.invalidateSelf();
+ }
+
+ public int getLanguageChangeDirection() {
+ if (mSpaceKey == null || SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() <= 1
+ || Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD) {
+ return 0; // No change
+ }
+ return mSpaceDragLastDiff > 0 ? 1 : -1;
+ }
+
+ boolean isCurrentlyInSpace() {
+ return mCurrentlyInSpace;
+ }
+
+ public void setPreferredLetters(int[] frequencies) {
+ mPrefLetterFrequencies = frequencies;
+ mPrefLetter = 0;
+ }
+
+ public void keyReleased() {
+ mCurrentlyInSpace = false;
+ mSpaceDragLastDiff = 0;
+ mPrefLetter = 0;
+ mPrefLetterX = 0;
+ mPrefLetterY = 0;
+ mPrefDistance = Integer.MAX_VALUE;
+ if (mSpaceKey != null) {
+ updateLocaleDrag(Integer.MAX_VALUE);
+ }
+ }
+
+ /**
+ * Does the magic of locking the touch gesture into the spacebar when
+ * switching input languages.
+ */
+ @SuppressWarnings("unused")
+ public boolean isInside(LatinKey key, int x, int y) {
+ final int code = key.codes[0];
+ if (code == KEYCODE_SHIFT || code == KEYCODE_DELETE) {
+ y -= key.height / 10;
+ if (code == KEYCODE_SHIFT) x += key.width / 6;
+ if (code == KEYCODE_DELETE) x -= key.width / 6;
+ } else if (code == LatinIME.KEYCODE_SPACE) {
+ y += LatinKeyboard.sSpacebarVerticalCorrection;
+ if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER
+ && SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() > 1) {
+ if (mCurrentlyInSpace) {
+ int diff = x - mSpaceDragStartX;
+ if (Math.abs(diff - mSpaceDragLastDiff) > 0) {
+ updateLocaleDrag(diff);
+ }
+ mSpaceDragLastDiff = diff;
+ return true;
+ } else {
+ boolean insideSpace = key.isInsideSuper(x, y);
+ if (insideSpace) {
+ mCurrentlyInSpace = true;
+ mSpaceDragStartX = x;
+ updateLocaleDrag(0);
+ }
+ return insideSpace;
+ }
+ }
+ } else if (mPrefLetterFrequencies != null) {
+ // New coordinate? Reset
+ if (mPrefLetterX != x || mPrefLetterY != y) {
+ mPrefLetter = 0;
+ mPrefDistance = Integer.MAX_VALUE;
+ }
+ // Handle preferred next letter
+ final int[] pref = mPrefLetterFrequencies;
+ if (mPrefLetter > 0) {
+ if (DEBUG_PREFERRED_LETTER) {
+ if (mPrefLetter == code && !key.isInsideSuper(x, y)) {
+ Log.d(TAG, "CORRECTED !!!!!!");
+ }
+ }
+ return mPrefLetter == code;
+ } else {
+ final boolean inside = key.isInsideSuper(x, y);
+ int[] nearby = getNearestKeys(x, y);
+ List<Key> nearbyKeys = getKeys();
+ if (inside) {
+ // If it's a preferred letter
+ if (inPrefList(code, pref)) {
+ // Check if its frequency is much lower than a nearby key
+ mPrefLetter = code;
+ mPrefLetterX = x;
+ mPrefLetterY = y;
+ for (int i = 0; i < nearby.length; i++) {
+ Key k = nearbyKeys.get(nearby[i]);
+ if (k != key && inPrefList(k.codes[0], pref)) {
+ final int dist = distanceFrom(k, x, y);
+ if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) &&
+ (pref[k.codes[0]] > pref[mPrefLetter] * 3)) {
+ mPrefLetter = k.codes[0];
+ mPrefDistance = dist;
+ if (DEBUG_PREFERRED_LETTER) {
+ Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!");
+ }
+ break;
+ }
+ }
+ }
+
+ return mPrefLetter == code;
+ }
+ }
+
+ // Get the surrounding keys and intersect with the preferred list
+ // For all in the intersection
+ // if distance from touch point is within a reasonable distance
+ // make this the pref letter
+ // If no pref letter
+ // return inside;
+ // else return thiskey == prefletter;
+
+ for (int i = 0; i < nearby.length; i++) {
+ Key k = nearbyKeys.get(nearby[i]);
+ if (inPrefList(k.codes[0], pref)) {
+ final int dist = distanceFrom(k, x, y);
+ if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB)
+ && dist < mPrefDistance) {
+ mPrefLetter = k.codes[0];
+ mPrefLetterX = x;
+ mPrefLetterY = y;
+ mPrefDistance = dist;
+ }
+ }
+ }
+ // Didn't find any
+ if (mPrefLetter == 0) {
+ return inside;
+ } else {
+ return mPrefLetter == code;
+ }
+ }
+ }
+
+ // Lock into the spacebar
+ if (mCurrentlyInSpace) return false;
+
+ return key.isInsideSuper(x, y);
+ }
+
+ private boolean inPrefList(int code, int[] pref) {
+ if (code < pref.length && code >= 0) return pref[code] > 0;
+ return false;
+ }
+
+ private int distanceFrom(Key k, int x, int y) {
+ if (y > k.y && y < k.y + k.height) {
+ return Math.abs(k.x + k.width / 2 - x);
+ } else {
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ @Override
+ public int[] getNearestKeys(int x, int y) {
+ if (mCurrentlyInSpace) {
+ return new int[] { mSpaceKeyIndex };
+ } else {
+ // Avoid dead pixels at edges of the keyboard
+ return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
+ Math.max(0, Math.min(y, getHeight() - 1)));
+ }
+ }
+
+ private int indexOf(int code) {
+ List<Key> keys = getKeys();
+ int count = keys.size();
+ for (int i = 0; i < count; i++) {
+ if (keys.get(i).codes[0] == code) return i;
+ }
+ return -1;
+ }
+
+ private int getTextSizeFromTheme(int style, int defValue) {
+ TypedArray array = mContext.getTheme().obtainStyledAttributes(
+ style, new int[] { android.R.attr.textSize });
+ int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue);
+ return textSize;
+ }
+}