1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.pinyin;
18
19import com.android.inputmethod.pinyin.InputModeSwitcher.ToggleStates;
20
21import android.graphics.Rect;
22import android.graphics.drawable.Drawable;
23import android.view.KeyEvent;
24
25import java.util.ArrayList;
26import java.util.List;
27
28/**
29 * Class used to represent a soft keyboard definition, including the height, the
30 * background image, the image for high light, the keys, etc.
31 */
32public class SoftKeyboard {
33    /** The XML resource id for this soft keyboard. */
34    private int mSkbXmlId;
35
36    /** Do we need to cache this soft keyboard? */
37    private boolean mCacheFlag;
38
39    /**
40     * After user switches to this soft keyboard, if this flag is true, this
41     * soft keyboard will be kept unless explicit switching operation is
42     * performed, otherwise IME will switch back to the previous keyboard layout
43     * whenever user clicks on any none-function key.
44     **/
45    private boolean mStickyFlag;
46
47    /**
48     * The cache id for this soft keyboard. It is used to identify it in the
49     * soft keyboard pool.
50     */
51    private int mCacheId;
52
53    /**
54     * Used to indicate whether this soft keyboard is newly loaded from an XML
55     * file or is just gotten from the soft keyboard pool.
56     */
57    private boolean mNewlyLoadedFlag = true;
58
59    /** The width of the soft keyboard. */
60    private int mSkbCoreWidth;
61
62    /** The height of the soft keyboard. */
63    private int mSkbCoreHeight;
64
65    /** The soft keyboard template for this soft keyboard. */
66    private SkbTemplate mSkbTemplate;
67
68    /** Used to indicate whether this soft keyboard is a QWERTY keyboard. */
69    private boolean mIsQwerty;
70
71    /**
72     * When {@link #mIsQwerty} is true, this member is Used to indicate that the
73     * soft keyboard should be displayed in uppercase.
74     */
75    private boolean mIsQwertyUpperCase;
76
77    /**
78     * The id of the rows which are enabled. Rows with id
79     * {@link KeyRow#ALWAYS_SHOW_ROW_ID} are always enabled.
80     */
81    private int mEnabledRowId;
82
83    /**
84     * Rows in this soft keyboard. Each row has a id. Only matched rows will be
85     * enabled.
86     */
87    private List<KeyRow> mKeyRows;
88
89    /**
90     * Background of the soft keyboard. If it is null, the one in the soft
91     * keyboard template will be used.
92     **/
93    public Drawable mSkbBg;
94
95    /**
96     * Background for key balloon. If it is null, the one in the soft keyboard
97     * template will be used.
98     **/
99    private Drawable mBalloonBg;
100
101    /**
102     * Background for popup mini soft keyboard. If it is null, the one in the
103     * soft keyboard template will be used.
104     **/
105    private Drawable mPopupBg;
106
107    /** The left and right margin of a key. */
108    private float mKeyXMargin = 0;
109
110    /** The top and bottom margin of a key. */
111    private float mKeyYMargin = 0;
112
113    private Rect mTmpRect = new Rect();
114
115    public SoftKeyboard(int skbXmlId, SkbTemplate skbTemplate, int skbWidth,
116            int skbHeight) {
117        mSkbXmlId = skbXmlId;
118        mSkbTemplate = skbTemplate;
119        mSkbCoreWidth = skbWidth;
120        mSkbCoreHeight = skbHeight;
121    }
122
123    public void setFlags(boolean cacheFlag, boolean stickyFlag,
124            boolean isQwerty, boolean isQwertyUpperCase) {
125        mCacheFlag = cacheFlag;
126        mStickyFlag = stickyFlag;
127        mIsQwerty = isQwerty;
128        mIsQwertyUpperCase = isQwertyUpperCase;
129    }
130
131    public boolean getCacheFlag() {
132        return mCacheFlag;
133    }
134
135    public void setCacheId(int cacheId) {
136        mCacheId = cacheId;
137    }
138
139    public boolean getStickyFlag() {
140        return mStickyFlag;
141    }
142
143    public void setSkbBackground(Drawable skbBg) {
144        mSkbBg = skbBg;
145    }
146
147    public void setPopupBackground(Drawable popupBg) {
148        mPopupBg = popupBg;
149    }
150
151    public void setKeyBalloonBackground(Drawable balloonBg) {
152        mBalloonBg = balloonBg;
153    }
154
155    public void setKeyMargins(float xMargin, float yMargin) {
156        mKeyXMargin = xMargin;
157        mKeyYMargin = yMargin;
158    }
159
160    public int getCacheId() {
161        return mCacheId;
162    }
163
164    public void reset() {
165        if (null != mKeyRows) mKeyRows.clear();
166    }
167
168    public void setNewlyLoadedFlag(boolean newlyLoadedFlag) {
169        mNewlyLoadedFlag = newlyLoadedFlag;
170    }
171
172    public boolean getNewlyLoadedFlag() {
173        return mNewlyLoadedFlag;
174    }
175
176    public void beginNewRow(int rowId, float yStartingPos) {
177        if (null == mKeyRows) mKeyRows = new ArrayList<KeyRow>();
178        KeyRow keyRow = new KeyRow();
179        keyRow.mRowId = rowId;
180        keyRow.mTopF = yStartingPos;
181        keyRow.mBottomF = yStartingPos;
182        keyRow.mSoftKeys = new ArrayList<SoftKey>();
183        mKeyRows.add(keyRow);
184    }
185
186    public boolean addSoftKey(SoftKey softKey) {
187        if (mKeyRows.size() == 0) return false;
188        KeyRow keyRow = mKeyRows.get(mKeyRows.size() - 1);
189        if (null == keyRow) return false;
190        List<SoftKey> softKeys = keyRow.mSoftKeys;
191
192        softKey.setSkbCoreSize(mSkbCoreWidth, mSkbCoreHeight);
193        softKeys.add(softKey);
194        if (softKey.mTopF < keyRow.mTopF) {
195            keyRow.mTopF = softKey.mTopF;
196        }
197        if (softKey.mBottomF > keyRow.mBottomF) {
198            keyRow.mBottomF = softKey.mBottomF;
199        }
200        return true;
201    }
202
203    public int getSkbXmlId() {
204        return mSkbXmlId;
205    }
206
207    // Set the size of the soft keyboard core. In other words, the background's
208    // padding are not counted.
209    public void setSkbCoreSize(int skbCoreWidth, int skbCoreHeight) {
210        if (null == mKeyRows
211                || (skbCoreWidth == mSkbCoreWidth && skbCoreHeight == mSkbCoreHeight)) {
212            return;
213        }
214        for (int row = 0; row < mKeyRows.size(); row++) {
215            KeyRow keyRow = mKeyRows.get(row);
216            keyRow.mBottom = (int) (skbCoreHeight * keyRow.mBottomF);
217            keyRow.mTop = (int) (skbCoreHeight * keyRow.mTopF);
218
219            List<SoftKey> softKeys = keyRow.mSoftKeys;
220            for (int i = 0; i < softKeys.size(); i++) {
221                SoftKey softKey = softKeys.get(i);
222                softKey.setSkbCoreSize(skbCoreWidth, skbCoreHeight);
223            }
224        }
225        mSkbCoreWidth = skbCoreWidth;
226        mSkbCoreHeight = skbCoreHeight;
227    }
228
229    public int getSkbCoreWidth() {
230        return mSkbCoreWidth;
231    }
232
233    public int getSkbCoreHeight() {
234        return mSkbCoreHeight;
235    }
236
237    public int getSkbTotalWidth() {
238        Rect padding = getPadding();
239        return mSkbCoreWidth + padding.left + padding.right;
240    }
241
242    public int getSkbTotalHeight() {
243        Rect padding = getPadding();
244        return mSkbCoreHeight + padding.top + padding.bottom;
245    }
246
247    public int getKeyXMargin() {
248        Environment env = Environment.getInstance();
249        return (int) (mKeyXMargin * mSkbCoreWidth * env.getKeyXMarginFactor());
250    }
251
252    public int getKeyYMargin() {
253        Environment env = Environment.getInstance();
254        return (int) (mKeyYMargin * mSkbCoreHeight * env.getKeyYMarginFactor());
255    }
256
257    public Drawable getSkbBackground() {
258        if (null != mSkbBg) return mSkbBg;
259        return mSkbTemplate.getSkbBackground();
260    }
261
262    public Drawable getBalloonBackground() {
263        if (null != mBalloonBg) return mBalloonBg;
264        return mSkbTemplate.getBalloonBackground();
265    }
266
267    public Drawable getPopupBackground() {
268        if (null != mPopupBg) return mPopupBg;
269        return mSkbTemplate.getPopupBackground();
270    }
271
272    public int getRowNum() {
273        if (null != mKeyRows) {
274            return mKeyRows.size();
275        }
276        return 0;
277    }
278
279    public KeyRow getKeyRowForDisplay(int row) {
280        if (null != mKeyRows && mKeyRows.size() > row) {
281            KeyRow keyRow = mKeyRows.get(row);
282            if (KeyRow.ALWAYS_SHOW_ROW_ID == keyRow.mRowId
283                    || keyRow.mRowId == mEnabledRowId) {
284                return keyRow;
285            }
286        }
287        return null;
288    }
289
290    public SoftKey getKey(int row, int location) {
291        if (null != mKeyRows && mKeyRows.size() > row) {
292            List<SoftKey> softKeys = mKeyRows.get(row).mSoftKeys;
293            if (softKeys.size() > location) {
294                return softKeys.get(location);
295            }
296        }
297        return null;
298    }
299
300    public SoftKey mapToKey(int x, int y) {
301        if (null == mKeyRows) {
302            return null;
303        }
304        // If the position is inside the rectangle of a certain key, return that
305        // key.
306        int rowNum = mKeyRows.size();
307        for (int row = 0; row < rowNum; row++) {
308            KeyRow keyRow = mKeyRows.get(row);
309            if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
310                    && keyRow.mRowId != mEnabledRowId) continue;
311            if (keyRow.mTop > y && keyRow.mBottom <= y) continue;
312
313            List<SoftKey> softKeys = keyRow.mSoftKeys;
314            int keyNum = softKeys.size();
315            for (int i = 0; i < keyNum; i++) {
316                SoftKey sKey = softKeys.get(i);
317                if (sKey.mLeft <= x && sKey.mTop <= y && sKey.mRight > x
318                        && sKey.mBottom > y) {
319                    return sKey;
320                }
321            }
322        }
323
324        // If the position is outside the rectangles of all keys, find the
325        // nearest one.
326        SoftKey nearestKey = null;
327        float nearestDis = Float.MAX_VALUE;
328        for (int row = 0; row < rowNum; row++) {
329            KeyRow keyRow = mKeyRows.get(row);
330            if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
331                    && keyRow.mRowId != mEnabledRowId) continue;
332            if (keyRow.mTop > y && keyRow.mBottom <= y) continue;
333
334            List<SoftKey> softKeys = keyRow.mSoftKeys;
335            int keyNum = softKeys.size();
336            for (int i = 0; i < keyNum; i++) {
337                SoftKey sKey = softKeys.get(i);
338                int disx = (sKey.mLeft + sKey.mRight) / 2 - x;
339                int disy = (sKey.mTop + sKey.mBottom) / 2 - y;
340                float dis = disx * disx + disy * disy;
341                if (dis < nearestDis) {
342                    nearestDis = dis;
343                    nearestKey = sKey;
344                }
345            }
346        }
347        return nearestKey;
348    }
349
350    public void switchQwertyMode(int toggle_state_id, boolean upperCase) {
351        if (!mIsQwerty) return;
352
353        int rowNum = mKeyRows.size();
354        for (int row = 0; row < rowNum; row++) {
355            KeyRow keyRow = mKeyRows.get(row);
356            List<SoftKey> softKeys = keyRow.mSoftKeys;
357            int keyNum = softKeys.size();
358            for (int i = 0; i < keyNum; i++) {
359                SoftKey sKey = softKeys.get(i);
360                if (sKey instanceof SoftKeyToggle) {
361                    ((SoftKeyToggle) sKey).enableToggleState(toggle_state_id,
362                            true);
363                }
364                if (sKey.mKeyCode >= KeyEvent.KEYCODE_A
365                        && sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {
366                    sKey.changeCase(upperCase);
367                }
368            }
369        }
370    }
371
372    public void enableToggleState(int toggleStateId, boolean resetIfNotFound) {
373        int rowNum = mKeyRows.size();
374        for (int row = 0; row < rowNum; row++) {
375            KeyRow keyRow = mKeyRows.get(row);
376            List<SoftKey> softKeys = keyRow.mSoftKeys;
377            int keyNum = softKeys.size();
378            for (int i = 0; i < keyNum; i++) {
379                SoftKey sKey = softKeys.get(i);
380                if (sKey instanceof SoftKeyToggle) {
381                    ((SoftKeyToggle) sKey).enableToggleState(toggleStateId,
382                            resetIfNotFound);
383                }
384            }
385        }
386    }
387
388    public void disableToggleState(int toggleStateId, boolean resetIfNotFound) {
389        int rowNum = mKeyRows.size();
390        for (int row = 0; row < rowNum; row++) {
391            KeyRow keyRow = mKeyRows.get(row);
392            List<SoftKey> softKeys = keyRow.mSoftKeys;
393            int keyNum = softKeys.size();
394            for (int i = 0; i < keyNum; i++) {
395                SoftKey sKey = softKeys.get(i);
396                if (sKey instanceof SoftKeyToggle) {
397                    ((SoftKeyToggle) sKey).disableToggleState(toggleStateId,
398                            resetIfNotFound);
399                }
400            }
401        }
402    }
403
404    public void enableToggleStates(ToggleStates toggleStates) {
405        if (null == toggleStates) return;
406
407        enableRow(toggleStates.mRowIdToEnable);
408
409        boolean isQwerty = toggleStates.mQwerty;
410        boolean isQwertyUpperCase = toggleStates.mQwertyUpperCase;
411        boolean needUpdateQwerty = (isQwerty && mIsQwerty && (mIsQwertyUpperCase != isQwertyUpperCase));
412        int states[] = toggleStates.mKeyStates;
413        int statesNum = toggleStates.mKeyStatesNum;
414
415        int rowNum = mKeyRows.size();
416        for (int row = 0; row < rowNum; row++) {
417            KeyRow keyRow = mKeyRows.get(row);
418            if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
419                    && keyRow.mRowId != mEnabledRowId) {
420                continue;
421            }
422            List<SoftKey> softKeys = keyRow.mSoftKeys;
423            int keyNum = softKeys.size();
424            for (int keyPos = 0; keyPos < keyNum; keyPos++) {
425                SoftKey sKey = softKeys.get(keyPos);
426                if (sKey instanceof SoftKeyToggle) {
427                    for (int statePos = 0; statePos < statesNum; statePos++) {
428                        ((SoftKeyToggle) sKey).enableToggleState(
429                                states[statePos], statePos == 0);
430                    }
431                    if (0 == statesNum) {
432                        ((SoftKeyToggle) sKey).disableAllToggleStates();
433                    }
434                }
435                if (needUpdateQwerty) {
436                    if (sKey.mKeyCode >= KeyEvent.KEYCODE_A
437                            && sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {
438                        sKey.changeCase(isQwertyUpperCase);
439                    }
440                }
441            }
442        }
443        mIsQwertyUpperCase = isQwertyUpperCase;
444    }
445
446    private Rect getPadding() {
447        mTmpRect.set(0, 0, 0, 0);
448        Drawable skbBg = getSkbBackground();
449        if (null == skbBg) return mTmpRect;
450        skbBg.getPadding(mTmpRect);
451        return mTmpRect;
452    }
453
454    /**
455     * Enable a row with the give toggle Id. Rows with other toggle ids (except
456     * the id {@link KeyRow#ALWAYS_SHOW_ROW_ID}) will be disabled.
457     *
458     * @param rowId The row id to enable.
459     * @return True if the soft keyboard requires redrawing.
460     */
461    private boolean enableRow(int rowId) {
462        if (KeyRow.ALWAYS_SHOW_ROW_ID == rowId) return false;
463
464        boolean enabled = false;
465        int rowNum = mKeyRows.size();
466        for (int row = rowNum - 1; row >= 0; row--) {
467            if (mKeyRows.get(row).mRowId == rowId) {
468                enabled = true;
469                break;
470            }
471        }
472        if (enabled) {
473            mEnabledRowId = rowId;
474        }
475        return enabled;
476    }
477
478    @Override
479    public String toString() {
480        String str = "------------------SkbInfo----------------------\n";
481        String endStr = "-----------------------------------------------\n";
482        str += "Width: " + String.valueOf(mSkbCoreWidth) + "\n";
483        str += "Height: " + String.valueOf(mSkbCoreHeight) + "\n";
484        str += "KeyRowNum: " + mKeyRows == null ? "0" : String.valueOf(mKeyRows
485                .size())
486                + "\n";
487        if (null == mKeyRows) return str + endStr;
488        int rowNum = mKeyRows.size();
489        for (int row = 0; row < rowNum; row++) {
490            KeyRow keyRow = mKeyRows.get(row);
491            List<SoftKey> softKeys = keyRow.mSoftKeys;
492            int keyNum = softKeys.size();
493            for (int i = 0; i < softKeys.size(); i++) {
494                str += "-key " + String.valueOf(i) + ":"
495                        + softKeys.get(i).toString();
496            }
497        }
498        return str + endStr;
499    }
500
501    public String toShortString() {
502        return super.toString();
503    }
504
505    class KeyRow {
506        static final int ALWAYS_SHOW_ROW_ID = -1;
507        static final int DEFAULT_ROW_ID = 0;
508
509        List<SoftKey> mSoftKeys;
510        /**
511         * If the row id is {@link #ALWAYS_SHOW_ROW_ID}, this row will always be
512         * enabled.
513         */
514        int mRowId;
515        float mTopF;
516        float mBottomF;
517        int mTop;
518        int mBottom;
519    }
520}
521