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