104c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka/* 28632bff2d5a8e1160989008dea6eff4b94b065ddTadashi G. Takaoka * Copyright (C) 2011 The Android Open Source Project 304c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * 48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); 58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License. 68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at 704c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * 88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0 904c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * 1004c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software 118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS, 128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and 148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License. 1504c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka */ 1604c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 1704c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaokapackage com.android.inputmethod.keyboard; 1804c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 19a729377395967f7652d93992cbcf50cd2ff522d1Tadashi G. Takaokaimport android.content.Context; 2032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaokaimport android.graphics.Paint; 2132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 2215f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaokaimport com.android.inputmethod.annotations.UsedForTesting; 2335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyboardBuilder; 2435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyboardParams; 2535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.MoreKeySpec; 2632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaokaimport com.android.inputmethod.latin.R; 274beeb9253a06482299e0c67467531d30436a02fcJean Chalardimport com.android.inputmethod.latin.common.StringUtils; 28ccf4a310279b13bbf0b6aac76a0878178c1dfb7dTadashi G. Takaokaimport com.android.inputmethod.latin.utils.TypefaceUtils; 2904c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 309d5d01a54319797db17fa8ac4b612f8836c83b9aTadashi G. Takaokaimport javax.annotation.Nonnull; 319d5d01a54319797db17fa8ac4b612f8836c83b9aTadashi G. Takaoka 32a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class MoreKeysKeyboard extends Keyboard { 338da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka private final int mDefaultKeyCoordX; 3404c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 3535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka MoreKeysKeyboard(final MoreKeysKeyboardParams params) { 368da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka super(params); 378da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; 3804c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka } 3904c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 4004c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka public int getDefaultCoordX() { 4104c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka return mDefaultKeyCoordX; 4204c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka } 4332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 4415f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaoka @UsedForTesting 4535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka static class MoreKeysKeyboardParams extends KeyboardParams { 46a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka public boolean mIsMoreKeysFixedOrder; 4735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka /* package */int mTopRowAdjustment; 4835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mNumRows; 4935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mNumColumns; 5035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mTopKeys; 5135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mLeftKeys; 5235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mRightKeys; // includes default key. 5335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mDividerWidth; 5435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mColumnWidth; 5535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 5635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public MoreKeysKeyboardParams() { 5735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka super(); 5835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 59e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka 6035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka /** 6135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * Set keyboard parameters of more keys keyboard. 6235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * 6335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param numKeys number of keys in this more keys keyboard. 64a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * @param numColumn number of columns of this more keys keyboard. 6535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param keyWidth more keys keyboard key width in pixel, including horizontal gap. 6635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param rowHeight more keys keyboard row height in pixel, including vertical gap. 6735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param coordXInParent coordinate x of the key preview in parent keyboard. 6835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param parentKeyboardWidth parent keyboard width in pixel. 69a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * @param isMoreKeysFixedColumn true if more keys keyboard should have 70a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * <code>numColumn</code> columns. Otherwise more keys keyboard should have 71a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * <code>numColumn</code> columns at most. 72a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * @param isMoreKeysFixedOrder true if the order of more keys is determined by the order in 73a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * the more keys' specification. Otherwise the order of more keys is automatically 74a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * determined. 7535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param dividerWidth width of divider, zero for no dividers. 7635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka */ 77a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka public void setParameters(final int numKeys, final int numColumn, final int keyWidth, 7835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int rowHeight, final int coordXInParent, final int parentKeyboardWidth, 79a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka final boolean isMoreKeysFixedColumn, final boolean isMoreKeysFixedOrder, 80a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka final int dividerWidth) { 81a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka mIsMoreKeysFixedOrder = isMoreKeysFixedOrder; 82a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka if (parentKeyboardWidth / keyWidth < Math.min(numKeys, numColumn)) { 83b0e76724edcde33dbfa17b56c3f5858705cd06eeTadashi G. Takaoka throw new IllegalArgumentException("Keyboard is too small to hold more keys: " 84a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka + parentKeyboardWidth + " " + keyWidth + " " + numKeys + " " + numColumn); 8532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 8635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mDefaultKeyWidth = keyWidth; 8735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mDefaultRowHeight = rowHeight; 8835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 89a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka final int numRows = (numKeys + numColumn - 1) / numColumn; 9035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mNumRows = numRows; 91a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka final int numColumns = isMoreKeysFixedColumn ? Math.min(numKeys, numColumn) 92a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka : getOptimizedColumns(numKeys, numColumn); 9335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mNumColumns = numColumns; 9435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int topKeys = numKeys % numColumns; 9535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mTopKeys = topKeys == 0 ? numColumns : topKeys; 9635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 9735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numLeftKeys = (numColumns - 1) / 2; 9835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numRightKeys = numColumns - numLeftKeys; // including default key. 9935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Maximum number of keys we can layout both side of the parent key 10035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int maxLeftKeys = coordXInParent / keyWidth; 10135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; 10235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int leftKeys, rightKeys; 10335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (numLeftKeys > maxLeftKeys) { 10435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys = maxLeftKeys; 10535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys = numColumns - leftKeys; 10635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else if (numRightKeys > maxRightKeys + 1) { 10735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys = maxRightKeys + 1; // include default key 10835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys = numColumns - rightKeys; 10935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else { 11035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys = numLeftKeys; 11135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys = numRightKeys; 11232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 11335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // If the left keys fill the left side of the parent key, entire more keys keyboard 11435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // should be shifted to the right unless the parent key is on the left edge. 11535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (maxLeftKeys == leftKeys && leftKeys > 0) { 11635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys--; 11735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys++; 118aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 11935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // If the right keys fill the right side of the parent key, entire more keys 12035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // should be shifted to the left unless the parent key is on the right edge. 12135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { 12235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys++; 12335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys--; 124aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 12535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mLeftKeys = leftKeys; 12635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mRightKeys = rightKeys; 12735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 12835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Adjustment of the top row. 129a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka mTopRowAdjustment = isMoreKeysFixedOrder ? getFixedOrderTopRowAdjustment() 13035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka : getAutoOrderTopRowAdjustment(); 13135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mDividerWidth = dividerWidth; 13235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mColumnWidth = mDefaultKeyWidth + mDividerWidth; 13335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; 13435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Need to subtract the bottom row's gutter only. 13535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap 13635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka + mTopPadding + mBottomPadding; 13735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 138aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 13935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getFixedOrderTopRowAdjustment() { 14035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns 14135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka || mLeftKeys == 0 || mRightKeys == 1) { 14235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return 0; 143aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 14435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return -1; 14535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 146aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 14735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getAutoOrderTopRowAdjustment() { 14835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 14935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka || mLeftKeys == 0 || mRightKeys == 1) { 15035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return 0; 151aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 15235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return -1; 15335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 154aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 15535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Return key position according to column count (0 is default). 15635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka /* package */int getColumnPos(final int n) { 157a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka return mIsMoreKeysFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); 15835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 159aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 16035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getFixedOrderColumnPos(final int n) { 16135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int col = n % mNumColumns; 16235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int row = n / mNumColumns; 16335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (!isTopRow(row)) { 16435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return col - mLeftKeys; 16535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 16635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int rightSideKeys = mTopKeys / 2; 16735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int leftSideKeys = mTopKeys - (rightSideKeys + 1); 16835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int pos = col - leftSideKeys; 16935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numLeftKeys = mLeftKeys + mTopRowAdjustment; 17035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numRightKeys = mRightKeys - 1; 17135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { 17232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka return pos; 17335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else if (numRightKeys < rightSideKeys) { 17435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return pos - (rightSideKeys - numRightKeys); 17535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else { // numLeftKeys < leftSideKeys 17635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return pos + (leftSideKeys - numLeftKeys); 17732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 17835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 17932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 18035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getAutomaticColumnPos(final int n) { 18135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int col = n % mNumColumns; 18235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int row = n / mNumColumns; 18335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int leftKeys = mLeftKeys; 18435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (isTopRow(row)) { 18535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys += mTopRowAdjustment; 18635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 18735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (col == 0) { 18835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // default position. 18935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return 0; 19032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 19132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 19235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int pos = 0; 19335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int right = 1; // include default position key. 19435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int left = 0; 19535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int i = 0; 19635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka while (true) { 19735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Assign right key if available. 19835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (right < mRightKeys) { 19935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka pos = right; 20035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka right++; 20135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka i++; 20232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 20335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (i >= col) 20435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka break; 20535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Assign left key if available. 20635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (left < leftKeys) { 20735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka left++; 20835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka pos = -left; 20935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka i++; 21035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 21135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (i >= col) 21235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka break; 21332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 21435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return pos; 21535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 21632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 21735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { 21835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int remainings = numKeys % numColumns; 21935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return remainings == 0 ? 0 : numColumns - remainings; 22035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 22132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 22235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getOptimizedColumns(final int numKeys, final int maxColumns) { 22335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int numColumns = Math.min(numKeys, maxColumns); 22435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { 22535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka numColumns--; 22632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 22735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return numColumns; 22835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 22932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 23035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int getDefaultKeyCoordX() { 2312fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa return mLeftKeys * mColumnWidth + mLeftPadding; 23235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 23332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 23435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int getX(final int n, final int row) { 23535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); 23635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (isTopRow(row)) { 23735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return x + mTopRowAdjustment * (mColumnWidth / 2); 23832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 23935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return x; 24035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 24132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 24235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int getY(final int row) { 24335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; 24432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 24532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 24635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public void markAsEdgeKey(final Key key, final int row) { 24735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (row == 0) 24835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka key.markAsTopEdge(this); 24935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (isTopRow(row)) 25035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka key.markAsBottomEdge(this); 25135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 25235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 25335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private boolean isTopRow(final int rowCount) { 25435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return mNumRows > 1 && rowCount == mNumRows - 1; 25535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 25635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 25735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 25835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> { 25935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private final Key mParentKey; 26035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 26135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static final float LABEL_PADDING_RATIO = 0.2f; 26235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static final float DIVIDER_RATIO = 0.2f; 26335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 2647ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka /** 2657ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka * The builder of MoreKeysKeyboard. 266a729377395967f7652d93992cbcf50cd2ff522d1Tadashi G. Takaoka * @param context the context of {@link MoreKeysKeyboardView}. 26703288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka * @param key the {@link Key} that invokes more keys keyboard. 26803288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka * @param keyboard the {@link Keyboard} that contains the parentKey. 26984405d2a6815a99992849e821e073835f2d892e3Tadashi G. Takaoka * @param isSingleMoreKeyWithPreview true if the <code>key</code> has just a single 27084405d2a6815a99992849e821e073835f2d892e3Tadashi G. Takaoka * "more key" and its key popup preview is enabled. 271a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * @param keyPreviewVisibleWidth the width of visible part of key popup preview. 272a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka * @param keyPreviewVisibleHeight the height of visible part of key popup preview 27384405d2a6815a99992849e821e073835f2d892e3Tadashi G. Takaoka * @param paintToMeasure the {@link Paint} object to measure a "more key" width 2747ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka */ 27503288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka public Builder(final Context context, final Key key, final Keyboard keyboard, 27684405d2a6815a99992849e821e073835f2d892e3Tadashi G. Takaoka final boolean isSingleMoreKeyWithPreview, final int keyPreviewVisibleWidth, 27703288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka final int keyPreviewVisibleHeight, final Paint paintToMeasure) { 278a729377395967f7652d93992cbcf50cd2ff522d1Tadashi G. Takaoka super(context, new MoreKeysKeyboardParams()); 27903288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka load(keyboard.mMoreKeysTemplate, keyboard.mId); 28032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 2812affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka // TODO: More keys keyboard's vertical gap is currently calculated heuristically. 282ea0c567f86bd19015d53fc038c4579df776cfec3Tadashi G. Takaoka // Should revise the algorithm. 28303288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka mParams.mVerticalGap = keyboard.mVerticalGap / 2; 28484405d2a6815a99992849e821e073835f2d892e3Tadashi G. Takaoka // This {@link MoreKeysKeyboard} is invoked from the <code>key</code>. 28503288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka mParentKey = key; 28603288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka 28703288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka final int keyWidth, rowHeight; 28884405d2a6815a99992849e821e073835f2d892e3Tadashi G. Takaoka if (isSingleMoreKeyWithPreview) { 2897ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // Use pre-computed width and height if this more keys keyboard has only one key to 2907ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // mitigate visual flicker between key preview and more keys keyboard. 2917ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // Caveats for the visual assets: To achieve this effect, both the key preview 2927ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // backgrounds and the more keys keyboard panel background have the exact same 2937ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // left/right/top paddings. The bottom paddings of both backgrounds don't need to 2947ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // be considered because the vertical positions of both backgrounds were already 2957ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // adjusted with their bottom paddings deducted. 29603288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka keyWidth = keyPreviewVisibleWidth; 29703288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka rowHeight = keyPreviewVisibleHeight + mParams.mVerticalGap; 298a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka } else { 299b0e76724edcde33dbfa17b56c3f5858705cd06eeTadashi G. Takaoka final float padding = context.getResources().getDimension( 3002fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa R.dimen.config_more_keys_keyboard_key_horizontal_padding) 30103288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka + (key.hasLabelsInMoreKeys() 302b0e76724edcde33dbfa17b56c3f5858705cd06eeTadashi G. Takaoka ? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f); 30303288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka keyWidth = getMaxKeyWidth(key, mParams.mDefaultKeyWidth, padding, paintToMeasure); 30403288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka rowHeight = keyboard.mMostCommonKeyHeight; 305a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka } 3065ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int dividerWidth; 30703288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka if (key.needsDividersInMoreKeys()) { 30803288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka dividerWidth = (int)(keyWidth * DIVIDER_RATIO); 3095ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka } else { 3105ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka dividerWidth = 0; 3115ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka } 31203288ef47fd93758b5665e19fe9b892ece6e586fTadashi G. Takaoka final MoreKeySpec[] moreKeys = key.getMoreKeys(); 313486c4894ce0917fc7b18eaee4bfd031051f2b05bTadashi G. Takaoka mParams.setParameters(moreKeys.length, key.getMoreKeysColumnNumber(), keyWidth, 314486c4894ce0917fc7b18eaee4bfd031051f2b05bTadashi G. Takaoka rowHeight, key.getX() + key.getWidth() / 2, keyboard.mId.mWidth, 315a9fc8622fe6024a3740895db354829f574ddfa75Tadashi G. Takaoka key.isMoreKeysFixedColumn(), key.isMoreKeysFixedOrder(), dividerWidth); 31632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 31732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 318b0e76724edcde33dbfa17b56c3f5858705cd06eeTadashi G. Takaoka private static int getMaxKeyWidth(final Key parentKey, final int minKeyWidth, 319b0e76724edcde33dbfa17b56c3f5858705cd06eeTadashi G. Takaoka final float padding, final Paint paint) { 3202315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka int maxWidth = minKeyWidth; 3217dc60f9db729e93cb591492574a436418c553ebfTadashi G. Takaoka for (final MoreKeySpec spec : parentKey.getMoreKeys()) { 322ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka final String label = spec.mLabel; 323bd7b160cfb05ee543e3cb6ddc7bd231b3f3aba0bTadashi G. Takaoka // If the label is single letter, minKeyWidth is enough to hold the label. 324cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka if (label != null && StringUtils.codePointCount(label) > 1) { 32508ae0d5ca03ed455827e82222df249d1cafb5d71Tadashi G. Takaoka maxWidth = Math.max(maxWidth, 3262fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa (int)(TypefaceUtils.getStringWidth(label, paint) + padding)); 32732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 32832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 329be34d973349909196dc3427a5653f4e119092ea7Tadashi G. Takaoka return maxWidth; 33032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 33132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 33232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka @Override 3339d5d01a54319797db17fa8ac4b612f8836c83b9aTadashi G. Takaoka @Nonnull 3342affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka public MoreKeysKeyboard build() { 3352affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka final MoreKeysKeyboardParams params = mParams; 336ab0d0d8a021a9b0f179281ac9e18604ad331cc43Tadashi G. Takaoka final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags(); 3377dc60f9db729e93cb591492574a436418c553ebfTadashi G. Takaoka final MoreKeySpec[] moreKeys = mParentKey.getMoreKeys(); 33842fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka for (int n = 0; n < moreKeys.length; n++) { 339ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka final MoreKeySpec moreKeySpec = moreKeys[n]; 34032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka final int row = n / params.mNumColumns; 3415ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int x = params.getX(n, row); 3425ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int y = params.getY(row); 343f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka final Key key = moreKeySpec.buildKey(x, y, moreKeyFlags, params); 3442fc4248700023853980b0006c12425079e3f9257Tadashi G. Takaoka params.markAsEdgeKey(key, row); 34532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka params.onAddKey(key); 3465ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka 3475ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int pos = params.getColumnPos(n); 3485ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka // The "pos" value represents the offset from the default position. Negative means 3495ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka // left of the default position. 3505ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka if (params.mDividerWidth > 0 && pos != 0) { 3515ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int dividerX = (pos > 0) ? x - params.mDividerWidth 3525ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka : x + params.mDefaultKeyWidth; 353727e818e95ef68460ebafb2eb3b11c23a2d8fcd8Tadashi G. Takaoka final Key divider = new MoreKeyDivider( 354727e818e95ef68460ebafb2eb3b11c23a2d8fcd8Tadashi G. Takaoka params, dividerX, y, params.mDividerWidth, params.mDefaultRowHeight); 3555ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka params.onAddKey(divider); 3565ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka } 35732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 3582affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka return new MoreKeysKeyboard(params); 35932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 36032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 36135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 362727e818e95ef68460ebafb2eb3b11c23a2d8fcd8Tadashi G. Takaoka // Used as a divider maker. A divider is drawn by {@link MoreKeysKeyboardView}. 363727e818e95ef68460ebafb2eb3b11c23a2d8fcd8Tadashi G. Takaoka public static class MoreKeyDivider extends Key.Spacer { 364727e818e95ef68460ebafb2eb3b11c23a2d8fcd8Tadashi G. Takaoka public MoreKeyDivider(final KeyboardParams params, final int x, final int y, 365727e818e95ef68460ebafb2eb3b11c23a2d8fcd8Tadashi G. Takaoka final int width, final int height) { 366727e818e95ef68460ebafb2eb3b11c23a2d8fcd8Tadashi G. Takaoka super(params, x, y, width, height); 36735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 36835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 36904c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka} 370