diff options
| author | Tadashi G. Takaoka <takaoka@google.com> | 2010-12-02 03:07:43 -0800 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2010-12-02 03:07:43 -0800 |
| commit | 26dae3f4e8ffd0f25b78c58598752cd393419bcc (patch) | |
| tree | fd7280e7e0c0c879958a5c7d8b9376942d447e9b /java/src/com/android/inputmethod/keyboard/LatinKeyboard.java | |
| parent | 18c28f431eadc1b451ca25d14fd683db4b234838 (diff) | |
| parent | 5a309f57155fb95667c2ccdda730eaf175de8876 (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.java | 543 |
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; + } +} |
