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 29 private MiniKeyboard(Builder.MiniKeyboardParams params) { 30 super(params); 31 mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; 32 } 33 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 48 public MiniKeyboardParams() { 49 super(); 50 } 51 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 */ 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). 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 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 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 180 public int getDefaultKeyCoordX() { 181 return mLeftKeys * mDefaultKeyWidth; 182 } 183 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 192 public int getY(int row) { 193 return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; 194 } 195 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 203 private boolean isTopRow(int rowCount) { 204 return rowCount == mNumRows - 1; 205 } 206 } 207 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 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 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