• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 
17 package com.android.inputmethod.keyboard;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Paint;
22 import android.graphics.drawable.Drawable;
23 
24 import com.android.inputmethod.annotations.UsedForTesting;
25 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
26 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
27 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
28 import com.android.inputmethod.keyboard.internal.KeyboardParams;
29 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
30 import com.android.inputmethod.latin.R;
31 import com.android.inputmethod.latin.StringUtils;
32 
33 public final class MoreKeysKeyboard extends Keyboard {
34     private final int mDefaultKeyCoordX;
35 
MoreKeysKeyboard(final MoreKeysKeyboardParams params)36     MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
37         super(params);
38         mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
39     }
40 
getDefaultCoordX()41     public int getDefaultCoordX() {
42         return mDefaultKeyCoordX;
43     }
44 
45     @UsedForTesting
46     static class MoreKeysKeyboardParams extends KeyboardParams {
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 
MoreKeysKeyboardParams()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          */
setParameters(final int numKeys, final int maxColumns, final int keyWidth, final int rowHeight, final int coordXInParent, final int parentKeyboardWidth, final boolean isFixedColumnOrder, final int dividerWidth)73         public void setParameters(final int numKeys, final int maxColumns, final int keyWidth,
74                 final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
75                 final boolean isFixedColumnOrder, final int dividerWidth) {
76             mIsFixedOrder = isFixedColumnOrder;
77             if (parentKeyboardWidth / keyWidth < Math.min(numKeys, maxColumns)) {
78                 throw new IllegalArgumentException(
79                         "Keyboard is too small to hold more keys keyboard: "
80                                 + parentKeyboardWidth + " " + keyWidth + " "
81                                 + numKeys + " " + maxColumns);
82             }
83             mDefaultKeyWidth = keyWidth;
84             mDefaultRowHeight = rowHeight;
85 
86             final int numRows = (numKeys + maxColumns - 1) / maxColumns;
87             mNumRows = numRows;
88             final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
89                     : getOptimizedColumns(numKeys, maxColumns);
90             mNumColumns = numColumns;
91             final int topKeys = numKeys % numColumns;
92             mTopKeys = topKeys == 0 ? numColumns : topKeys;
93 
94             final int numLeftKeys = (numColumns - 1) / 2;
95             final int numRightKeys = numColumns - numLeftKeys; // including default key.
96             // Maximum number of keys we can layout both side of the parent key
97             final int maxLeftKeys = coordXInParent / keyWidth;
98             final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
99             int leftKeys, rightKeys;
100             if (numLeftKeys > maxLeftKeys) {
101                 leftKeys = maxLeftKeys;
102                 rightKeys = numColumns - leftKeys;
103             } else if (numRightKeys > maxRightKeys + 1) {
104                 rightKeys = maxRightKeys + 1; // include default key
105                 leftKeys = numColumns - rightKeys;
106             } else {
107                 leftKeys = numLeftKeys;
108                 rightKeys = numRightKeys;
109             }
110             // If the left keys fill the left side of the parent key, entire more keys keyboard
111             // should be shifted to the right unless the parent key is on the left edge.
112             if (maxLeftKeys == leftKeys && leftKeys > 0) {
113                 leftKeys--;
114                 rightKeys++;
115             }
116             // If the right keys fill the right side of the parent key, entire more keys
117             // should be shifted to the left unless the parent key is on the right edge.
118             if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
119                 leftKeys++;
120                 rightKeys--;
121             }
122             mLeftKeys = leftKeys;
123             mRightKeys = rightKeys;
124 
125             // Adjustment of the top row.
126             mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
127                     : getAutoOrderTopRowAdjustment();
128             mDividerWidth = dividerWidth;
129             mColumnWidth = mDefaultKeyWidth + mDividerWidth;
130             mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
131             // Need to subtract the bottom row's gutter only.
132             mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
133                     + mTopPadding + mBottomPadding;
134         }
135 
getFixedOrderTopRowAdjustment()136         private int getFixedOrderTopRowAdjustment() {
137             if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
138                     || mLeftKeys == 0  || mRightKeys == 1) {
139                 return 0;
140             }
141             return -1;
142         }
143 
getAutoOrderTopRowAdjustment()144         private int getAutoOrderTopRowAdjustment() {
145             if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
146                     || mLeftKeys == 0 || mRightKeys == 1) {
147                 return 0;
148             }
149             return -1;
150         }
151 
152         // Return key position according to column count (0 is default).
getColumnPos(final int n)153         /* package */int getColumnPos(final int n) {
154             return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
155         }
156 
getFixedOrderColumnPos(final int n)157         private int getFixedOrderColumnPos(final int n) {
158             final int col = n % mNumColumns;
159             final int row = n / mNumColumns;
160             if (!isTopRow(row)) {
161                 return col - mLeftKeys;
162             }
163             final int rightSideKeys = mTopKeys / 2;
164             final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
165             final int pos = col - leftSideKeys;
166             final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
167             final int numRightKeys = mRightKeys - 1;
168             if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
169                 return pos;
170             } else if (numRightKeys < rightSideKeys) {
171                 return pos - (rightSideKeys - numRightKeys);
172             } else { // numLeftKeys < leftSideKeys
173                 return pos + (leftSideKeys - numLeftKeys);
174             }
175         }
176 
getAutomaticColumnPos(final int n)177         private int getAutomaticColumnPos(final int n) {
178             final int col = n % mNumColumns;
179             final int row = n / mNumColumns;
180             int leftKeys = mLeftKeys;
181             if (isTopRow(row)) {
182                 leftKeys += mTopRowAdjustment;
183             }
184             if (col == 0) {
185                 // default position.
186                 return 0;
187             }
188 
189             int pos = 0;
190             int right = 1; // include default position key.
191             int left = 0;
192             int i = 0;
193             while (true) {
194                 // Assign right key if available.
195                 if (right < mRightKeys) {
196                     pos = right;
197                     right++;
198                     i++;
199                 }
200                 if (i >= col)
201                     break;
202                 // Assign left key if available.
203                 if (left < leftKeys) {
204                     left++;
205                     pos = -left;
206                     i++;
207                 }
208                 if (i >= col)
209                     break;
210             }
211             return pos;
212         }
213 
getTopRowEmptySlots(final int numKeys, final int numColumns)214         private static int getTopRowEmptySlots(final int numKeys, final int numColumns) {
215             final int remainings = numKeys % numColumns;
216             return remainings == 0 ? 0 : numColumns - remainings;
217         }
218 
getOptimizedColumns(final int numKeys, final int maxColumns)219         private int getOptimizedColumns(final int numKeys, final int maxColumns) {
220             int numColumns = Math.min(numKeys, maxColumns);
221             while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
222                 numColumns--;
223             }
224             return numColumns;
225         }
226 
getDefaultKeyCoordX()227         public int getDefaultKeyCoordX() {
228             return mLeftKeys * mColumnWidth;
229         }
230 
getX(final int n, final int row)231         public int getX(final int n, final int row) {
232             final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
233             if (isTopRow(row)) {
234                 return x + mTopRowAdjustment * (mColumnWidth / 2);
235             }
236             return x;
237         }
238 
getY(final int row)239         public int getY(final int row) {
240             return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
241         }
242 
markAsEdgeKey(final Key key, final int row)243         public void markAsEdgeKey(final Key key, final int row) {
244             if (row == 0)
245                 key.markAsTopEdge(this);
246             if (isTopRow(row))
247                 key.markAsBottomEdge(this);
248         }
249 
isTopRow(final int rowCount)250         private boolean isTopRow(final int rowCount) {
251             return mNumRows > 1 && rowCount == mNumRows - 1;
252         }
253     }
254 
255     public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> {
256         private final Key mParentKey;
257         private final Drawable mDivider;
258 
259         private static final float LABEL_PADDING_RATIO = 0.2f;
260         private static final float DIVIDER_RATIO = 0.2f;
261 
262         /**
263          * The builder of MoreKeysKeyboard.
264          * @param context the context of {@link MoreKeysKeyboardView}.
265          * @param parentKey the {@link Key} that invokes more keys keyboard.
266          * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
267          * @param keyPreviewDrawParams the parameter to place key preview.
268          */
Builder(final Context context, final Key parentKey, final MainKeyboardView parentKeyboardView, final KeyPreviewDrawParams keyPreviewDrawParams)269         public Builder(final Context context, final Key parentKey,
270                 final MainKeyboardView parentKeyboardView,
271                 final KeyPreviewDrawParams keyPreviewDrawParams) {
272             super(context, new MoreKeysKeyboardParams());
273             final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
274             load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
275 
276             // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
277             // Should revise the algorithm.
278             mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
279             mParentKey = parentKey;
280 
281             final int width, height;
282             final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
283                     && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1;
284             if (singleMoreKeyWithPreview) {
285                 // Use pre-computed width and height if this more keys keyboard has only one key to
286                 // mitigate visual flicker between key preview and more keys keyboard.
287                 // Caveats for the visual assets: To achieve this effect, both the key preview
288                 // backgrounds and the more keys keyboard panel background have the exact same
289                 // left/right/top paddings. The bottom paddings of both backgrounds don't need to
290                 // be considered because the vertical positions of both backgrounds were already
291                 // adjusted with their bottom paddings deducted.
292                 width = keyPreviewDrawParams.mPreviewVisibleWidth;
293                 height = keyPreviewDrawParams.mPreviewVisibleHeight + mParams.mVerticalGap;
294                 // TODO: Remove this check.
295                 if (width == 0) {
296                     throw new IllegalArgumentException(
297                             "Zero width key detected: " + parentKey + " in " + parentKeyboard.mId);
298                 }
299             } else {
300                 width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth,
301                         context.getResources());
302                 height = parentKeyboard.mMostCommonKeyHeight;
303                 // TODO: Remove this check.
304                 if (width == 0) {
305                     throw new IllegalArgumentException(
306                             "Zero width calculated: " + parentKey
307                             + " moreKeys=" + java.util.Arrays.toString(parentKey.mMoreKeys)
308                             + " in " + parentKeyboard.mId);
309                 }
310             }
311             final int dividerWidth;
312             if (parentKey.needsDividersInMoreKeys()) {
313                 mDivider = mResources.getDrawable(R.drawable.more_keys_divider);
314                 dividerWidth = (int)(width * DIVIDER_RATIO);
315             } else {
316                 mDivider = null;
317                 dividerWidth = 0;
318             }
319             mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
320                     width, height, parentKey.mX + parentKey.mWidth / 2,
321                     parentKeyboardView.getMeasuredWidth(), parentKey.isFixedColumnOrderMoreKeys(),
322                     dividerWidth);
323         }
324 
getMaxKeyWidth(final KeyboardView view, final Key parentKey, final int minKeyWidth, final Resources res)325         private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
326                 final int minKeyWidth, final Resources res) {
327             final float padding =
328                     res.getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
329                     + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0.0f);
330             final Paint paint = view.newLabelPaint(parentKey);
331             int maxWidth = minKeyWidth;
332             for (final MoreKeySpec spec : parentKey.mMoreKeys) {
333                 final String label = spec.mLabel;
334                 // If the label is single letter, minKeyWidth is enough to hold the label.
335                 if (label != null && StringUtils.codePointCount(label) > 1) {
336                     maxWidth = Math.max(maxWidth,
337                             (int)(TypefaceUtils.getLabelWidth(label, paint) + padding));
338                 }
339             }
340             return maxWidth;
341         }
342 
343         @Override
build()344         public MoreKeysKeyboard build() {
345             final MoreKeysKeyboardParams params = mParams;
346             final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags();
347             final MoreKeySpec[] moreKeys = mParentKey.mMoreKeys;
348             for (int n = 0; n < moreKeys.length; n++) {
349                 final MoreKeySpec moreKeySpec = moreKeys[n];
350                 final int row = n / params.mNumColumns;
351                 final int x = params.getX(n, row);
352                 final int y = params.getY(row);
353                 final Key key = new Key(params, moreKeySpec, x, y,
354                         params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags);
355                 params.markAsEdgeKey(key, row);
356                 params.onAddKey(key);
357 
358                 final int pos = params.getColumnPos(n);
359                 // The "pos" value represents the offset from the default position. Negative means
360                 // left of the default position.
361                 if (params.mDividerWidth > 0 && pos != 0) {
362                     final int dividerX = (pos > 0) ? x - params.mDividerWidth
363                             : x + params.mDefaultKeyWidth;
364                     final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y);
365                     params.onAddKey(divider);
366                 }
367             }
368             return new MoreKeysKeyboard(params);
369         }
370     }
371 
372     private static class MoreKeyDivider extends Key.Spacer {
373         private final Drawable mIcon;
374 
MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon, final int x, final int y)375         public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon,
376                 final int x, final int y) {
377             super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
378             mIcon = icon;
379         }
380 
381         @Override
getIcon(final KeyboardIconsSet iconSet, final int alpha)382         public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
383             // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
384             // constructor.
385             // TODO: Drawable itself should have an alpha value.
386             mIcon.setAlpha(128);
387             return mIcon;
388         }
389     }
390 }
391