MoreKeysKeyboard.java revision 72913f97edb74e877f78b25418a568a4b0f5ae5c
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;
20import android.graphics.drawable.Drawable;
21
22import com.android.inputmethod.keyboard.internal.KeySpecParser;
23import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
24import com.android.inputmethod.latin.R;
25import com.android.inputmethod.latin.StringUtils;
26
27public class MoreKeysKeyboard extends Keyboard {
28    private final int mDefaultKeyCoordX;
29
30    MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) {
31        super(params);
32        mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
33    }
34
35    public int getDefaultCoordX() {
36        return mDefaultKeyCoordX;
37    }
38
39    public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
40        private final Key mParentKey;
41        private final Drawable mDivider;
42
43        private static final float LABEL_PADDING_RATIO = 0.2f;
44        private static final float DIVIDER_RATIO = 0.2f;
45
46        public static class MoreKeysKeyboardParams extends Keyboard.Params {
47            public boolean mIsFixedOrder;
48            /* package */int mTopRowAdjustment;
49            public int mNumRows;
50            public int mNumColumns;
51            public int mTopKeys;
52            public int mLeftKeys;
53            public int mRightKeys; // includes default key.
54            public int mDividerWidth;
55            public int mColumnWidth;
56
57            public MoreKeysKeyboardParams() {
58                super();
59            }
60
61            /**
62             * Set keyboard parameters of more keys keyboard.
63             *
64             * @param numKeys number of keys in this more keys keyboard.
65             * @param maxColumns number of maximum columns of this more keys keyboard.
66             * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
67             * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
68             * @param coordXInParent coordinate x of the key preview in parent keyboard.
69             * @param parentKeyboardWidth parent keyboard width in pixel.
70             * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
71             * @param dividerWidth width of divider, zero for no dividers.
72             */
73            public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
74                    int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder,
75                    int dividerWidth) {
76                mIsFixedOrder = isFixedColumnOrder;
77                if (parentKeyboardWidth / keyWidth < maxColumns) {
78                    throw new IllegalArgumentException(
79                            "Keyboard is too small to hold more keys keyboard: "
80                                    + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
81                }
82                mDefaultKeyWidth = keyWidth;
83                mDefaultRowHeight = rowHeight;
84
85                final int numRows = (numKeys + maxColumns - 1) / maxColumns;
86                mNumRows = numRows;
87                final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
88                        : getOptimizedColumns(numKeys, maxColumns);
89                mNumColumns = numColumns;
90                final int topKeys = numKeys % numColumns;
91                mTopKeys = topKeys == 0 ? numColumns : topKeys;
92
93                final int numLeftKeys = (numColumns - 1) / 2;
94                final int numRightKeys = numColumns - numLeftKeys; // including default key.
95                // Maximum number of keys we can layout both side of the parent key
96                final int maxLeftKeys = coordXInParent / keyWidth;
97                final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
98                int leftKeys, rightKeys;
99                if (numLeftKeys > maxLeftKeys) {
100                    leftKeys = maxLeftKeys;
101                    rightKeys = numColumns - leftKeys;
102                } else if (numRightKeys > maxRightKeys + 1) {
103                    rightKeys = maxRightKeys + 1; // include default key
104                    leftKeys = numColumns - rightKeys;
105                } else {
106                    leftKeys = numLeftKeys;
107                    rightKeys = numRightKeys;
108                }
109                // If the left keys fill the left side of the parent key, entire more keys keyboard
110                // should be shifted to the right unless the parent key is on the left edge.
111                if (maxLeftKeys == leftKeys && leftKeys > 0) {
112                    leftKeys--;
113                    rightKeys++;
114                }
115                // If the right keys fill the right side of the parent key, entire more keys
116                // should be shifted to the left unless the parent key is on the right edge.
117                if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
118                    leftKeys++;
119                    rightKeys--;
120                }
121                mLeftKeys = leftKeys;
122                mRightKeys = rightKeys;
123
124                // Adjustment of the top row.
125                mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
126                        : getAutoOrderTopRowAdjustment();
127                mDividerWidth = dividerWidth;
128                mColumnWidth = mDefaultKeyWidth + mDividerWidth;
129                mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
130                // Need to subtract the bottom row's gutter only.
131                mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
132                        + mTopPadding + mBottomPadding;
133            }
134
135            private int getFixedOrderTopRowAdjustment() {
136                if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
137                        || mLeftKeys == 0  || mRightKeys == 1) {
138                    return 0;
139                }
140                return -1;
141            }
142
143            private int getAutoOrderTopRowAdjustment() {
144                if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
145                        || mLeftKeys == 0 || mRightKeys == 1) {
146                    return 0;
147                }
148                return -1;
149            }
150
151            // Return key position according to column count (0 is default).
152            /* package */int getColumnPos(int n) {
153                return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
154            }
155
156            private int getFixedOrderColumnPos(int n) {
157                final int col = n % mNumColumns;
158                final int row = n / mNumColumns;
159                if (!isTopRow(row)) {
160                    return col - mLeftKeys;
161                }
162                final int rightSideKeys = mTopKeys / 2;
163                final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
164                final int pos = col - leftSideKeys;
165                final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
166                final int numRightKeys = mRightKeys - 1;
167                if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
168                    return pos;
169                } else if (numRightKeys < rightSideKeys) {
170                    return pos - (rightSideKeys - numRightKeys);
171                } else { // numLeftKeys < leftSideKeys
172                    return pos + (leftSideKeys - numLeftKeys);
173                }
174            }
175
176            private int getAutomaticColumnPos(int n) {
177                final int col = n % mNumColumns;
178                final int row = n / mNumColumns;
179                int leftKeys = mLeftKeys;
180                if (isTopRow(row)) {
181                    leftKeys += mTopRowAdjustment;
182                }
183                if (col == 0) {
184                    // default position.
185                    return 0;
186                }
187
188                int pos = 0;
189                int right = 1; // include default position key.
190                int left = 0;
191                int i = 0;
192                while (true) {
193                    // Assign right key if available.
194                    if (right < mRightKeys) {
195                        pos = right;
196                        right++;
197                        i++;
198                    }
199                    if (i >= col)
200                        break;
201                    // Assign left key if available.
202                    if (left < leftKeys) {
203                        left++;
204                        pos = -left;
205                        i++;
206                    }
207                    if (i >= col)
208                        break;
209                }
210                return pos;
211            }
212
213            private static int getTopRowEmptySlots(int numKeys, int numColumns) {
214                final int remainings = numKeys % numColumns;
215                return remainings == 0 ? 0 : numColumns - remainings;
216            }
217
218            private int getOptimizedColumns(int numKeys, int maxColumns) {
219                int numColumns = Math.min(numKeys, maxColumns);
220                while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
221                    numColumns--;
222                }
223                return numColumns;
224            }
225
226            public int getDefaultKeyCoordX() {
227                return mLeftKeys * mColumnWidth;
228            }
229
230            public int getX(int n, int row) {
231                final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
232                if (isTopRow(row)) {
233                    return x + mTopRowAdjustment * (mColumnWidth / 2);
234                }
235                return x;
236            }
237
238            public int getY(int row) {
239                return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
240            }
241
242            public void markAsEdgeKey(Key key, int row) {
243                if (row == 0)
244                    key.markAsTopEdge(this);
245                if (isTopRow(row))
246                    key.markAsBottomEdge(this);
247            }
248
249            private boolean isTopRow(int rowCount) {
250                return mNumRows > 1 && rowCount == mNumRows - 1;
251            }
252        }
253
254        public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
255            super(view.getContext(), new MoreKeysKeyboardParams());
256            load(xmlId, parentKeyboard.mId);
257
258            // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
259            // Should revise the algorithm.
260            mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
261            mParentKey = parentKey;
262
263            final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth;
264            final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight;
265            final int width, height;
266            // Use pre-computed width and height if these values are available and more keys
267            // keyboard has only one key to mitigate visual flicker between key preview and more
268            // keys keyboard.
269            final boolean validKeyPreview = view.isKeyPreviewPopupEnabled()
270                    && !parentKey.noKeyPreview() && (previewWidth > 0) && (previewHeight > 0);
271            final boolean singleMoreKeyWithPreview = validKeyPreview
272                    && parentKey.mMoreKeys.length == 1;
273            if (singleMoreKeyWithPreview) {
274                width = previewWidth;
275                height = previewHeight + mParams.mVerticalGap;
276            } else {
277                width = getMaxKeyWidth(view, parentKey, mParams.mDefaultKeyWidth);
278                height = parentKeyboard.mMostCommonKeyHeight;
279            }
280            final int dividerWidth;
281            if (parentKey.needsDividersInMoreKeys()) {
282                mDivider = mResources.getDrawable(R.drawable.more_keys_divider);
283                // TODO: Drawable itself should have an alpha value.
284                mDivider.setAlpha(128);
285                dividerWidth = (int)(width * DIVIDER_RATIO);
286            } else {
287                mDivider = null;
288                dividerWidth = 0;
289            }
290            mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
291                    width, height, parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth(),
292                    parentKey.isFixedColumnOrderMoreKeys(), dividerWidth);
293        }
294
295        private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) {
296            final int padding = (int)(view.getResources()
297                    .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
298                    + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
299            final Paint paint = view.newDefaultLabelPaint();
300            paint.setTextSize(parentKey.hasLabelsInMoreKeys()
301                    ? view.mKeyDrawParams.mKeyLabelSize
302                    : view.mKeyDrawParams.mKeyLetterSize);
303            int maxWidth = minKeyWidth;
304            for (String moreKeySpec : parentKey.mMoreKeys) {
305                final String label = KeySpecParser.getLabel(moreKeySpec);
306                // If the label is single letter, minKeyWidth is enough to hold the label.
307                if (label != null && StringUtils.codePointCount(label) > 1) {
308                    final int width = (int)view.getLabelWidth(label, paint) + padding;
309                    if (maxWidth < width) {
310                        maxWidth = width;
311                    }
312                }
313            }
314            return maxWidth;
315        }
316
317        private static class MoreKeyDivider extends Key.Spacer {
318            private final Drawable mIcon;
319
320            public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) {
321                super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
322                mIcon = icon;
323            }
324
325            @Override
326            public Drawable getIcon(KeyboardIconsSet iconSet) {
327                // KeyboardIconsSet is unused. Use the icon that has been passed to the constructor.
328                return mIcon;
329            }
330        }
331
332        @Override
333        public MoreKeysKeyboard build() {
334            final MoreKeysKeyboardParams params = mParams;
335            // moreKeyFlags == 0 means that the rendered text size will be determined by its
336            // label's code point count.
337            final int moreKeyFlags = mParentKey.hasLabelsInMoreKeys() ? 0
338                    : Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
339            final String[] moreKeys = mParentKey.mMoreKeys;
340            for (int n = 0; n < moreKeys.length; n++) {
341                final String moreKeySpec = moreKeys[n];
342                final int row = n / params.mNumColumns;
343                final int x = params.getX(n, row);
344                final int y = params.getY(row);
345                final Key key = new Key(mResources, params, moreKeySpec, x, y,
346                        params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags);
347                params.markAsEdgeKey(key, row);
348                params.onAddKey(key);
349
350                final int pos = params.getColumnPos(n);
351                // The "pos" value represents the offset from the default position. Negative means
352                // left of the default position.
353                if (params.mDividerWidth > 0 && pos != 0) {
354                    final int dividerX = (pos > 0) ? x - params.mDividerWidth
355                            : x + params.mDefaultKeyWidth;
356                    final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y);
357                    params.onAddKey(divider);
358                }
359            }
360            return new MoreKeysKeyboard(params);
361        }
362    }
363}
364