MoreKeysKeyboard.java revision aeeed758480b0fac848f4556884d978f3004555b
1/*
2 * Copyright (C) 2011 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.graphics.Paint;
20
21import com.android.inputmethod.keyboard.internal.KeySpecParser;
22import com.android.inputmethod.latin.R;
23
24public class MoreKeysKeyboard extends Keyboard {
25    private final int mDefaultKeyCoordX;
26
27    MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) {
28        super(params);
29        mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
30    }
31
32    public int getDefaultCoordX() {
33        return mDefaultKeyCoordX;
34    }
35
36    public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
37        private final String[] mMoreKeys;
38
39        public static class MoreKeysKeyboardParams extends Keyboard.Params {
40            public boolean mIsFixedOrder;
41            /* package */int mTopRowAdjustment;
42            public int mNumRows;
43            public int mNumColumns;
44            public int mTopKeys;
45            public int mLeftKeys;
46            public int mRightKeys; // includes default key.
47
48            public MoreKeysKeyboardParams() {
49                super();
50            }
51
52            /* package for test */MoreKeysKeyboardParams(int numKeys, int maxColumns, int keyWidth,
53                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
54                super();
55                setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent,
56                        parentKeyboardWidth);
57            }
58
59            /**
60             * Set keyboard parameters of more keys keyboard.
61             *
62             * @param numKeys number of keys in this more keys keyboard.
63             * @param maxColumnsAndFlags number of maximum columns of this more keys keyboard.
64             * This might have {@link Key#MORE_KEYS_FIXED_COLUMN_ORDER} flag.
65             * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
66             * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
67             * @param coordXInParent coordinate x of the key preview in parent keyboard.
68             * @param parentKeyboardWidth parent keyboard width in pixel.
69             */
70            public void setParameters(int numKeys, int maxColumnsAndFlags, int keyWidth,
71                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
72                mIsFixedOrder = (maxColumnsAndFlags & Key.MORE_KEYS_FIXED_COLUMN_ORDER) != 0;
73                final int maxColumns = maxColumnsAndFlags & ~Key.MORE_KEYS_FIXED_COLUMN_ORDER;
74                if (parentKeyboardWidth / keyWidth < maxColumns) {
75                    throw new IllegalArgumentException(
76                            "Keyboard is too small to hold more keys keyboard: "
77                                    + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
78                }
79                mDefaultKeyWidth = keyWidth;
80                mDefaultRowHeight = rowHeight;
81
82                final int numRows = (numKeys + maxColumns - 1) / maxColumns;
83                mNumRows = numRows;
84                final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
85                        : getOptimizedColumns(numKeys, maxColumns);
86                mNumColumns = numColumns;
87                final int topKeys = numKeys % numColumns;
88                mTopKeys = topKeys == 0 ? numColumns : topKeys;
89
90                final int numLeftKeys = (numColumns - 1) / 2;
91                final int numRightKeys = numColumns - numLeftKeys; // including default key.
92                // Maximum number of keys we can layout both side of the parent key
93                final int maxLeftKeys = coordXInParent / keyWidth;
94                final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
95                int leftKeys, rightKeys;
96                if (numLeftKeys > maxLeftKeys) {
97                    leftKeys = maxLeftKeys;
98                    rightKeys = numColumns - leftKeys;
99                } else if (numRightKeys > maxRightKeys + 1) {
100                    rightKeys = maxRightKeys + 1; // include default key
101                    leftKeys = numColumns - rightKeys;
102                } else {
103                    leftKeys = numLeftKeys;
104                    rightKeys = numRightKeys;
105                }
106                // If the left keys fill the left side of the parent key, entire more keys keyboard
107                // should be shifted to the right unless the parent key is on the left edge.
108                if (maxLeftKeys == leftKeys && leftKeys > 0) {
109                    leftKeys--;
110                    rightKeys++;
111                }
112                // If the right keys fill the right side of the parent key, entire more keys
113                // should be shifted to the left unless the parent key is on the right edge.
114                if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
115                    leftKeys++;
116                    rightKeys--;
117                }
118                mLeftKeys = leftKeys;
119                mRightKeys = rightKeys;
120
121                // Adjustment of the top row.
122                mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
123                        : getAutoOrderTopRowAdjustment();
124                mBaseWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
125                // Need to subtract the bottom row's gutter only.
126                mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
127                        + mTopPadding + mBottomPadding;
128            }
129
130            private int getFixedOrderTopRowAdjustment() {
131                if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
132                        || mLeftKeys == 0  || mRightKeys == 1) {
133                    return 0;
134                }
135                return -1;
136            }
137
138            private int getAutoOrderTopRowAdjustment() {
139                if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
140                        || mLeftKeys == 0 || mRightKeys == 1) {
141                    return 0;
142                }
143                return -1;
144            }
145
146            // Return key position according to column count (0 is default).
147            /* package */int getColumnPos(int n) {
148                return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
149            }
150
151            private int getFixedOrderColumnPos(int n) {
152                final int col = n % mNumColumns;
153                final int row = n / mNumColumns;
154                if (!isTopRow(row)) {
155                    return col - mLeftKeys;
156                }
157                final int rightSideKeys = mTopKeys / 2;
158                final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
159                final int pos = col - leftSideKeys;
160                final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
161                final int numRightKeys = mRightKeys - 1;
162                if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
163                    return pos;
164                } else if (numRightKeys < rightSideKeys) {
165                    return pos - (rightSideKeys - numRightKeys);
166                } else { // numLeftKeys < leftSideKeys
167                    return pos + (leftSideKeys - numLeftKeys);
168                }
169            }
170
171            private int getAutomaticColumnPos(int n) {
172                final int col = n % mNumColumns;
173                final int row = n / mNumColumns;
174                int leftKeys = mLeftKeys;
175                if (isTopRow(row)) {
176                    leftKeys += mTopRowAdjustment;
177                }
178                if (col == 0) {
179                    // default position.
180                    return 0;
181                }
182
183                int pos = 0;
184                int right = 1; // include default position key.
185                int left = 0;
186                int i = 0;
187                while (true) {
188                    // Assign right key if available.
189                    if (right < mRightKeys) {
190                        pos = right;
191                        right++;
192                        i++;
193                    }
194                    if (i >= col)
195                        break;
196                    // Assign left key if available.
197                    if (left < leftKeys) {
198                        left++;
199                        pos = -left;
200                        i++;
201                    }
202                    if (i >= col)
203                        break;
204                }
205                return pos;
206            }
207
208            private static int getTopRowEmptySlots(int numKeys, int numColumns) {
209                final int remainings = numKeys % numColumns;
210                return remainings == 0 ? 0 : numColumns - remainings;
211            }
212
213            private int getOptimizedColumns(int numKeys, int maxColumns) {
214                int numColumns = Math.min(numKeys, maxColumns);
215                while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
216                    numColumns--;
217                }
218                return numColumns;
219            }
220
221            public int getDefaultKeyCoordX() {
222                return mLeftKeys * mDefaultKeyWidth;
223            }
224
225            public int getX(int n, int row) {
226                final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
227                if (isTopRow(row)) {
228                    return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
229                }
230                return x;
231            }
232
233            public int getY(int row) {
234                return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
235            }
236
237            public void markAsEdgeKey(Key key, int row) {
238                if (row == 0)
239                    key.markAsTopEdge(this);
240                if (isTopRow(row))
241                    key.markAsBottomEdge(this);
242            }
243
244            private boolean isTopRow(int rowCount) {
245                return mNumRows > 1 && rowCount == mNumRows - 1;
246            }
247        }
248
249        public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
250            super(view.getContext(), new MoreKeysKeyboardParams());
251            load(xmlId, parentKeyboard.mId);
252
253            // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
254            // Should revise the algorithm.
255            mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
256            mMoreKeys = parentKey.mMoreKeys;
257
258            final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth;
259            final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight;
260            final int width, height;
261            // Use pre-computed width and height if these values are available and more keys
262            // keyboard has only one key to mitigate visual flicker between key preview and more
263            // keys keyboard.
264            if (view.isKeyPreviewPopupEnabled() && mMoreKeys.length == 1 && previewWidth > 0
265                    && previewHeight > 0) {
266                width = previewWidth;
267                height = previewHeight + mParams.mVerticalGap;
268            } else {
269                width = getMaxKeyWidth(view, parentKey.mMoreKeys, mParams.mDefaultKeyWidth);
270                height = parentKeyboard.mMostCommonKeyHeight;
271            }
272            mParams.setParameters(mMoreKeys.length, parentKey.mMaxMoreKeysColumn, width, height,
273                    parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth());
274        }
275
276        private static int getMaxKeyWidth(KeyboardView view, String[] moreKeys, int minKeyWidth) {
277            final int padding = (int) view.getContext().getResources()
278                    .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding);
279            Paint paint = null;
280            int maxWidth = minKeyWidth;
281            for (String moreKeySpec : moreKeys) {
282                final String label = KeySpecParser.getLabel(moreKeySpec);
283                // If the label is single letter, minKeyWidth is enough to hold the label.
284                if (label != null && label.length() > 1) {
285                    if (paint == null) {
286                        paint = new Paint();
287                        paint.setAntiAlias(true);
288                    }
289                    final int width = (int)view.getDefaultLabelWidth(label, paint) + padding;
290                    if (maxWidth < width) {
291                        maxWidth = width;
292                    }
293                }
294            }
295            return maxWidth;
296        }
297
298        @Override
299        public MoreKeysKeyboard build() {
300            final MoreKeysKeyboardParams params = mParams;
301            for (int n = 0; n < mMoreKeys.length; n++) {
302                final String moreKeySpec = mMoreKeys[n];
303                final int row = n / params.mNumColumns;
304                final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row),
305                        params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight);
306                params.markAsEdgeKey(key, row);
307                params.onAddKey(key);
308            }
309            return new MoreKeysKeyboard(params);
310        }
311    }
312}
313