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