LatinKeyboard.java revision ef5dfc480c7a3e3e34a20b7aacc731942e7a0578
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.keyboard;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.content.res.Resources.Theme;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Paint;
27import android.graphics.Paint.Align;
28import android.graphics.PorterDuff;
29import android.graphics.Rect;
30import android.graphics.drawable.BitmapDrawable;
31import android.graphics.drawable.Drawable;
32import android.text.TextUtils;
33
34import com.android.inputmethod.latin.R;
35import com.android.inputmethod.latin.SubtypeSwitcher;
36
37import java.lang.ref.SoftReference;
38import java.util.Arrays;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Locale;
42
43// TODO: We should remove this class
44public class LatinKeyboard extends Keyboard {
45    private static final int SPACE_LED_LENGTH_PERCENT = 80;
46
47    private final Resources mRes;
48    private final Theme mTheme;
49    private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
50
51    /* Space key and its icons, drawables and colors. */
52    private final Key mSpaceKey;
53    private final Drawable mSpaceIcon;
54    private final boolean mAutoCorrectionSpacebarLedEnabled;
55    private final Drawable mAutoCorrectionSpacebarLedIcon;
56    private final int mSpacebarTextColor;
57    private final int mSpacebarTextShadowColor;
58    private float mSpacebarTextFadeFactor = 0.0f;
59    private final HashMap<Integer, SoftReference<BitmapDrawable>> mSpaceDrawableCache =
60            new HashMap<Integer, SoftReference<BitmapDrawable>>();
61
62    /* Shortcut key and its icons if available */
63    private final Key mShortcutKey;
64    private final Drawable mEnabledShortcutIcon;
65    private final Drawable mDisabledShortcutIcon;
66
67    // Height in space key the language name will be drawn. (proportional to space key height)
68    public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
69    // If the full language name needs to be smaller than this value to be drawn on space key,
70    // its short language name will be used instead.
71    private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
72
73    private static final String SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "small";
74    private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium";
75
76    public LatinKeyboard(Context context, KeyboardId id, int width) {
77        super(context, id.getXmlId(), id, width);
78        mRes = context.getResources();
79        mTheme = context.getTheme();
80
81        final List<Key> keys = getKeys();
82        int spaceKeyIndex = -1;
83        int shortcutKeyIndex = -1;
84        final int keyCount = keys.size();
85        for (int index = 0; index < keyCount; index++) {
86            // For now, assuming there are up to one space key and one shortcut key respectively.
87            switch (keys.get(index).mCode) {
88            case CODE_SPACE:
89                spaceKeyIndex = index;
90                break;
91            case CODE_SHORTCUT:
92                shortcutKeyIndex = index;
93                break;
94            }
95        }
96
97        // The index of space key is available only after Keyboard constructor has finished.
98        mSpaceKey = (spaceKeyIndex >= 0) ? keys.get(spaceKeyIndex) : null;
99        mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
100
101        mShortcutKey = (shortcutKeyIndex >= 0) ? keys.get(shortcutKeyIndex) : null;
102        mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
103
104        final TypedArray a = context.obtainStyledAttributes(
105                null, R.styleable.LatinKeyboard, R.attr.latinKeyboardStyle, R.style.LatinKeyboard);
106        mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
107                R.styleable.LatinKeyboard_autoCorrectionSpacebarLedEnabled, false);
108        mAutoCorrectionSpacebarLedIcon = a.getDrawable(
109                R.styleable.LatinKeyboard_autoCorrectionSpacebarLedIcon);
110        mDisabledShortcutIcon = a.getDrawable(R.styleable.LatinKeyboard_disabledShortcutIcon);
111        mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboard_spacebarTextColor, 0);
112        mSpacebarTextShadowColor = a.getColor(
113                R.styleable.LatinKeyboard_spacebarTextShadowColor, 0);
114        a.recycle();
115    }
116
117    public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) {
118        mSpacebarTextFadeFactor = fadeFactor;
119        updateSpacebarForLocale(false);
120        if (view != null)
121            view.invalidateKey(mSpaceKey);
122    }
123
124    private static int getSpacebarTextColor(int color, float fadeFactor) {
125        final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor),
126                Color.red(color), Color.green(color), Color.blue(color));
127        return newColor;
128    }
129
130    public void updateShortcutKey(boolean available, LatinKeyboardView view) {
131        if (mShortcutKey == null)
132            return;
133        mShortcutKey.setEnabled(available);
134        mShortcutKey.setIcon(available ? mEnabledShortcutIcon : mDisabledShortcutIcon);
135        if (view != null)
136            view.invalidateKey(mShortcutKey);
137    }
138
139    public boolean needsAutoCorrectionSpacebarLed() {
140        return mAutoCorrectionSpacebarLedEnabled;
141    }
142
143    /**
144     * @return a key which should be invalidated.
145     */
146    public Key onAutoCorrectionStateChanged(boolean isAutoCorrection) {
147        updateSpacebarForLocale(isAutoCorrection);
148        return mSpaceKey;
149    }
150
151    @Override
152    public CharSequence adjustLabelCase(CharSequence label) {
153        if (isAlphaKeyboard() && isShiftedOrShiftLocked() && !TextUtils.isEmpty(label)
154                && label.length() < 3 && Character.isLowerCase(label.charAt(0))) {
155            return label.toString().toUpperCase(mId.mLocale);
156        }
157        return label;
158    }
159
160    private void updateSpacebarForLocale(boolean isAutoCorrection) {
161        if (mSpaceKey == null)
162            return;
163        // If application locales are explicitly selected.
164        if (mSubtypeSwitcher.needsToDisplayLanguage()) {
165            mSpaceKey.setIcon(getSpaceDrawable(
166                    mSubtypeSwitcher.getInputLocale(), isAutoCorrection));
167        } else if (isAutoCorrection) {
168            mSpaceKey.setIcon(getSpaceDrawable(null, true));
169        } else {
170            mSpaceKey.setIcon(mSpaceIcon);
171        }
172    }
173
174    // Compute width of text with specified text size using paint.
175    private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) {
176        paint.setTextSize(textSize);
177        paint.getTextBounds(text, 0, text.length(), bounds);
178        return bounds.width();
179    }
180
181    // Layout local language name and left and right arrow on spacebar.
182    private static String layoutSpacebar(Paint paint, Locale locale, int width,
183            float origTextSize) {
184        final Rect bounds = new Rect();
185
186        // Estimate appropriate language name text size to fit in maxTextWidth.
187        String language = SubtypeSwitcher.getFullDisplayName(locale, true);
188        int textWidth = getTextWidth(paint, language, origTextSize, bounds);
189        // Assuming text width and text size are proportional to each other.
190        float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
191        // allow variable text size
192        textWidth = getTextWidth(paint, language, textSize, bounds);
193        // If text size goes too small or text does not fit, use middle or short name
194        final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
195                || (textWidth > width);
196
197        final boolean useShortName;
198        if (useMiddleName) {
199            language = SubtypeSwitcher.getMiddleDisplayLanguage(locale);
200            textWidth = getTextWidth(paint, language, origTextSize, bounds);
201            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
202            useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
203                    || (textWidth > width);
204        } else {
205            useShortName = false;
206        }
207
208        if (useShortName) {
209            language = SubtypeSwitcher.getShortDisplayLanguage(locale);
210            textWidth = getTextWidth(paint, language, origTextSize, bounds);
211            textSize = origTextSize * Math.min(width / textWidth, 1.0f);
212        }
213        paint.setTextSize(textSize);
214
215        return language;
216    }
217
218    private BitmapDrawable getSpaceDrawable(Locale locale, boolean isAutoCorrection) {
219        final Integer hashCode = Arrays.hashCode(
220                new Object[] { locale, isAutoCorrection, mSpacebarTextFadeFactor });
221        final SoftReference<BitmapDrawable> ref = mSpaceDrawableCache.get(hashCode);
222        BitmapDrawable drawable = (ref == null) ? null : ref.get();
223        if (drawable == null) {
224            drawable = new BitmapDrawable(mRes, drawSpacebar(
225                    locale, isAutoCorrection, mSpacebarTextFadeFactor));
226            mSpaceDrawableCache.put(hashCode, new SoftReference<BitmapDrawable>(drawable));
227        }
228        return drawable;
229    }
230
231    private Bitmap drawSpacebar(Locale inputLocale, boolean isAutoCorrection,
232            float textFadeFactor) {
233        final int width = mSpaceKey.mWidth;
234        final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : mSpaceKey.mHeight;
235        final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
236        final Canvas canvas = new Canvas(buffer);
237        final Resources res = mRes;
238        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
239
240        // If application locales are explicitly selected.
241        if (inputLocale != null) {
242            final Paint paint = new Paint();
243            paint.setAntiAlias(true);
244            paint.setTextAlign(Align.CENTER);
245
246            final String textSizeOfLanguageOnSpacebar = res.getString(
247                    R.string.config_text_size_of_language_on_spacebar,
248                    SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR);
249            final int textStyle;
250            final int defaultTextSize;
251            if (MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR.equals(textSizeOfLanguageOnSpacebar)) {
252                textStyle = android.R.style.TextAppearance_Medium;
253                defaultTextSize = 18;
254            } else {
255                textStyle = android.R.style.TextAppearance_Small;
256                defaultTextSize = 14;
257            }
258
259            final String language = layoutSpacebar(paint, inputLocale, width, getTextSizeFromTheme(
260                    mTheme, textStyle, defaultTextSize));
261
262            // Draw language text with shadow
263            // In case there is no space icon, we will place the language text at the center of
264            // spacebar.
265            final float descent = paint.descent();
266            final float textHeight = -paint.ascent() + descent;
267            final float baseline = (mSpaceIcon != null) ? height * SPACEBAR_LANGUAGE_BASELINE
268                    : height / 2 + textHeight / 2;
269            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, textFadeFactor));
270            canvas.drawText(language, width / 2, baseline - descent - 1, paint);
271            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, textFadeFactor));
272            canvas.drawText(language, width / 2, baseline - descent, paint);
273        }
274
275        // Draw the spacebar icon at the bottom
276        if (isAutoCorrection) {
277            final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
278            final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
279            int x = (width - iconWidth) / 2;
280            int y = height - iconHeight;
281            mAutoCorrectionSpacebarLedIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
282            mAutoCorrectionSpacebarLedIcon.draw(canvas);
283        } else if (mSpaceIcon != null) {
284            final int iconWidth = mSpaceIcon.getIntrinsicWidth();
285            final int iconHeight = mSpaceIcon.getIntrinsicHeight();
286            int x = (width - iconWidth) / 2;
287            int y = height - iconHeight;
288            mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
289            mSpaceIcon.draw(canvas);
290        }
291        return buffer;
292    }
293
294    @Override
295    public int[] getNearestKeys(int x, int y) {
296        // Avoid dead pixels at edges of the keyboard
297        return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
298                Math.max(0, Math.min(y, getHeight() - 1)));
299    }
300
301    public static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
302        TypedArray array = theme.obtainStyledAttributes(
303                style, new int[] { android.R.attr.textSize });
304        int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue);
305        return textSize;
306    }
307}
308