1/*
2 * Copyright (C) 2010 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.util.Log;
20import android.util.SparseArray;
21
22import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
23import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
24import com.android.inputmethod.keyboard.internal.KeyboardParams;
25import com.android.inputmethod.latin.CollectionUtils;
26
27
28
29/**
30 * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
31 * consists of rows of keys.
32 * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
33 * <pre>
34 * &lt;Keyboard
35 *         latin:keyWidth="%10p"
36 *         latin:keyHeight="50px"
37 *         latin:horizontalGap="2px"
38 *         latin:verticalGap="2px" &gt;
39 *     &lt;Row latin:keyWidth="32px" &gt;
40 *         &lt;Key latin:keyLabel="A" /&gt;
41 *         ...
42 *     &lt;/Row&gt;
43 *     ...
44 * &lt;/Keyboard&gt;
45 * </pre>
46 */
47public class Keyboard {
48    private static final String TAG = Keyboard.class.getSimpleName();
49
50    /** Some common keys code. Must be positive.
51     * These should be aligned with values/keycodes.xml
52     */
53    public static final int CODE_ENTER = '\n';
54    public static final int CODE_TAB = '\t';
55    public static final int CODE_SPACE = ' ';
56    public static final int CODE_PERIOD = '.';
57    public static final int CODE_DASH = '-';
58    public static final int CODE_SINGLE_QUOTE = '\'';
59    public static final int CODE_DOUBLE_QUOTE = '"';
60    public static final int CODE_QUESTION_MARK = '?';
61    public static final int CODE_EXCLAMATION_MARK = '!';
62    // TODO: Check how this should work for right-to-left languages. It seems to stand
63    // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
64    // managed by the font? Or is it a different char?
65    public static final int CODE_CLOSING_PARENTHESIS = ')';
66    public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
67    public static final int CODE_CLOSING_CURLY_BRACKET = '}';
68    public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
69
70    /** Special keys code. Must be negative.
71     * These should be aligned with KeyboardCodesSet.ID_TO_NAME[],
72     * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[]
73     */
74    public static final int CODE_SHIFT = -1;
75    public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
76    public static final int CODE_OUTPUT_TEXT = -3;
77    public static final int CODE_DELETE = -4;
78    public static final int CODE_SETTINGS = -5;
79    public static final int CODE_SHORTCUT = -6;
80    public static final int CODE_ACTION_ENTER = -7;
81    public static final int CODE_ACTION_NEXT = -8;
82    public static final int CODE_ACTION_PREVIOUS = -9;
83    public static final int CODE_LANGUAGE_SWITCH = -10;
84    public static final int CODE_RESEARCH = -11;
85    // Code value representing the code is not specified.
86    public static final int CODE_UNSPECIFIED = -12;
87
88    public final KeyboardId mId;
89    public final int mThemeId;
90
91    /** Total height of the keyboard, including the padding and keys */
92    public final int mOccupiedHeight;
93    /** Total width of the keyboard, including the padding and keys */
94    public final int mOccupiedWidth;
95
96    /** The padding above the keyboard */
97    public final int mTopPadding;
98    /** Default gap between rows */
99    public final int mVerticalGap;
100
101    /** Per keyboard key visual parameters */
102    public final KeyVisualAttributes mKeyVisualAttributes;
103
104    public final int mMostCommonKeyHeight;
105    public final int mMostCommonKeyWidth;
106
107    /** More keys keyboard template */
108    public final int mMoreKeysTemplate;
109
110    /** Maximum column for more keys keyboard */
111    public final int mMaxMoreKeysKeyboardColumn;
112
113    /** Array of keys and icons in this keyboard */
114    public final Key[] mKeys;
115    public final Key[] mShiftKeys;
116    public final Key[] mAltCodeKeysWhileTyping;
117    public final KeyboardIconsSet mIconsSet;
118
119    private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
120
121    private final ProximityInfo mProximityInfo;
122    private final boolean mProximityCharsCorrectionEnabled;
123
124    public Keyboard(final KeyboardParams params) {
125        mId = params.mId;
126        mThemeId = params.mThemeId;
127        mOccupiedHeight = params.mOccupiedHeight;
128        mOccupiedWidth = params.mOccupiedWidth;
129        mMostCommonKeyHeight = params.mMostCommonKeyHeight;
130        mMostCommonKeyWidth = params.mMostCommonKeyWidth;
131        mMoreKeysTemplate = params.mMoreKeysTemplate;
132        mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
133        mKeyVisualAttributes = params.mKeyVisualAttributes;
134        mTopPadding = params.mTopPadding;
135        mVerticalGap = params.mVerticalGap;
136
137        mKeys = params.mKeys.toArray(new Key[params.mKeys.size()]);
138        mShiftKeys = params.mShiftKeys.toArray(new Key[params.mShiftKeys.size()]);
139        mAltCodeKeysWhileTyping = params.mAltCodeKeysWhileTyping.toArray(
140                new Key[params.mAltCodeKeysWhileTyping.size()]);
141        mIconsSet = params.mIconsSet;
142
143        mProximityInfo = new ProximityInfo(params.mId.mLocale.toString(),
144                params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
145                mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection);
146        mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
147    }
148
149    public boolean hasProximityCharsCorrection(final int code) {
150        if (!mProximityCharsCorrectionEnabled) {
151            return false;
152        }
153        // Note: The native code has the main keyboard layout only at this moment.
154        // TODO: Figure out how to handle proximity characters information of all layouts.
155        final boolean canAssumeNativeHasProximityCharsInfoOfAllKeys = (
156                mId.mElementId == KeyboardId.ELEMENT_ALPHABET
157                || mId.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED);
158        return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code);
159    }
160
161    public ProximityInfo getProximityInfo() {
162        return mProximityInfo;
163    }
164
165    public Key getKey(final int code) {
166        if (code == CODE_UNSPECIFIED) {
167            return null;
168        }
169        synchronized (mKeyCache) {
170            final int index = mKeyCache.indexOfKey(code);
171            if (index >= 0) {
172                return mKeyCache.valueAt(index);
173            }
174
175            for (final Key key : mKeys) {
176                if (key.mCode == code) {
177                    mKeyCache.put(code, key);
178                    return key;
179                }
180            }
181            mKeyCache.put(code, null);
182            return null;
183        }
184    }
185
186    public boolean hasKey(final Key aKey) {
187        if (mKeyCache.indexOfValue(aKey) >= 0) {
188            return true;
189        }
190
191        for (final Key key : mKeys) {
192            if (key == aKey) {
193                mKeyCache.put(key.mCode, key);
194                return true;
195            }
196        }
197        return false;
198    }
199
200    public static boolean isLetterCode(final int code) {
201        return code >= CODE_SPACE;
202    }
203
204    @Override
205    public String toString() {
206        return mId.toString();
207    }
208
209    /**
210     * Returns the array of the keys that are closest to the given point.
211     * @param x the x-coordinate of the point
212     * @param y the y-coordinate of the point
213     * @return the array of the nearest keys to the given point. If the given
214     * point is out of range, then an array of size zero is returned.
215     */
216    public Key[] getNearestKeys(final int x, final int y) {
217        // Avoid dead pixels at edges of the keyboard
218        final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
219        final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
220        return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
221    }
222
223    public static String printableCode(final int code) {
224        switch (code) {
225        case CODE_SHIFT: return "shift";
226        case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
227        case CODE_OUTPUT_TEXT: return "text";
228        case CODE_DELETE: return "delete";
229        case CODE_SETTINGS: return "settings";
230        case CODE_SHORTCUT: return "shortcut";
231        case CODE_ACTION_ENTER: return "actionEnter";
232        case CODE_ACTION_NEXT: return "actionNext";
233        case CODE_ACTION_PREVIOUS: return "actionPrevious";
234        case CODE_LANGUAGE_SWITCH: return "languageSwitch";
235        case CODE_UNSPECIFIED: return "unspec";
236        case CODE_TAB: return "tab";
237        case CODE_ENTER: return "enter";
238        default:
239            if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code);
240            if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
241            if (code < 0x100) return String.format("'%c'", code);
242            return String.format("'\\u%04x'", code);
243        }
244    }
245}
246