MoreKeysKeyboard.java revision aeeed758480b0fac848f4556884d978f3004555b
104c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka/*
28632bff2d5a8e1160989008dea6eff4b94b065ddTadashi G. Takaoka * Copyright (C) 2011 The Android Open Source Project
304c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka *
404c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); you may not
504c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * use this file except in compliance with the License. You may obtain a copy of
604c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * the License at
704c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka *
804c96ab966e8a58e5cd401362b49509751ce75d9Tadashi 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
1104c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1204c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1304c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * License for the specific language governing permissions and limitations under
1404c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka * the License.
1504c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka */
1604c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka
1704c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaokapackage com.android.inputmethod.keyboard;
1804c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka
1932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaokaimport android.graphics.Paint;
2032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
21e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeySpecParser;
2232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaokaimport com.android.inputmethod.latin.R;
2304c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka
242affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaokapublic class MoreKeysKeyboard extends Keyboard {
258da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka    private final int mDefaultKeyCoordX;
2604c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka
276213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka    MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) {
288da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        super(params);
298da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka        mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
3004c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka    }
3104c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka
3204c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka    public int getDefaultCoordX() {
3304c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka        return mDefaultKeyCoordX;
3404c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka    }
3532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
362affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka    public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
37bd7b160cfb05ee543e3cb6ddc7bd231b3f3aba0bTadashi G. Takaoka        private final String[] mMoreKeys;
3832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
392affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka        public static class MoreKeysKeyboardParams extends Keyboard.Params {
40aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            public boolean mIsFixedOrder;
4132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            /* package */int mTopRowAdjustment;
4232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            public int mNumRows;
4332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            public int mNumColumns;
44aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            public int mTopKeys;
4532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            public int mLeftKeys;
4632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            public int mRightKeys; // includes default key.
4732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
482affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            public MoreKeysKeyboardParams() {
4932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                super();
5032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
5132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
522affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            /* package for test */MoreKeysKeyboardParams(int numKeys, int maxColumns, int keyWidth,
5332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
5432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                super();
5532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent,
5632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        parentKeyboardWidth);
5732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
5832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
5932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            /**
602affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka             * Set keyboard parameters of more keys keyboard.
6132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka             *
622affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka             * @param numKeys number of keys in this more keys keyboard.
63aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka             * @param maxColumnsAndFlags number of maximum columns of this more keys keyboard.
64aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka             * This might have {@link Key#MORE_KEYS_FIXED_COLUMN_ORDER} flag.
652affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka             * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
662affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka             * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
672affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka             * @param coordXInParent coordinate x of the key preview in parent keyboard.
6832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka             * @param parentKeyboardWidth parent keyboard width in pixel.
6932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka             */
70aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            public void setParameters(int numKeys, int maxColumnsAndFlags, int keyWidth,
71aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
72aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                mIsFixedOrder = (maxColumnsAndFlags & Key.MORE_KEYS_FIXED_COLUMN_ORDER) != 0;
73aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int maxColumns = maxColumnsAndFlags & ~Key.MORE_KEYS_FIXED_COLUMN_ORDER;
7432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                if (parentKeyboardWidth / keyWidth < maxColumns) {
7532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    throw new IllegalArgumentException(
762affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka                            "Keyboard is too small to hold more keys keyboard: "
772affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka                                    + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
7832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
7932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                mDefaultKeyWidth = keyWidth;
8032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                mDefaultRowHeight = rowHeight;
8132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
8232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                final int numRows = (numKeys + maxColumns - 1) / maxColumns;
8332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                mNumRows = numRows;
84aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
85aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                        : getOptimizedColumns(numKeys, maxColumns);
8632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                mNumColumns = numColumns;
87aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int topKeys = numKeys % numColumns;
88aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                mTopKeys = topKeys == 0 ? numColumns : topKeys;
8932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
9032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                final int numLeftKeys = (numColumns - 1) / 2;
9132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                final int numRightKeys = numColumns - numLeftKeys; // including default key.
926213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                // Maximum number of keys we can layout both side of the parent key
9332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                final int maxLeftKeys = coordXInParent / keyWidth;
946213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
9532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                int leftKeys, rightKeys;
9632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                if (numLeftKeys > maxLeftKeys) {
9732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    leftKeys = maxLeftKeys;
986213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                    rightKeys = numColumns - leftKeys;
996213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                } else if (numRightKeys > maxRightKeys + 1) {
1006213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                    rightKeys = maxRightKeys + 1; // include default key
1016213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                    leftKeys = numColumns - rightKeys;
10232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                } else {
10332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    leftKeys = numLeftKeys;
10432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    rightKeys = numRightKeys;
10532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
1066213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                // If the left keys fill the left side of the parent key, entire more keys keyboard
1076213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                // should be shifted to the right unless the parent key is on the left edge.
1086213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                if (maxLeftKeys == leftKeys && leftKeys > 0) {
10932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    leftKeys--;
11032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    rightKeys++;
11132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
1126213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                // If the right keys fill the right side of the parent key, entire more keys
1136213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                // should be shifted to the left unless the parent key is on the right edge.
1146213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
11532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    leftKeys++;
11632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    rightKeys--;
11732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
11832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                mLeftKeys = leftKeys;
11932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                mRightKeys = rightKeys;
12032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
121aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                // Adjustment of the top row.
122aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
123aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                        : getAutoOrderTopRowAdjustment();
1248fbfac4ffb7079e8e71fd4e3ddc04e362239ebb3Tadashi G. Takaoka                mBaseWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
125ea0c567f86bd19015d53fc038c4579df776cfec3Tadashi G. Takaoka                // Need to subtract the bottom row's gutter only.
1268fbfac4ffb7079e8e71fd4e3ddc04e362239ebb3Tadashi G. Takaoka                mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
127ea0c567f86bd19015d53fc038c4579df776cfec3Tadashi G. Takaoka                        + mTopPadding + mBottomPadding;
12832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
12932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
130aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            private int getFixedOrderTopRowAdjustment() {
131aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
132aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                        || mLeftKeys == 0  || mRightKeys == 1) {
133aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    return 0;
134aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                }
135aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                return -1;
136aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            }
137aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
138aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            private int getAutoOrderTopRowAdjustment() {
139aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
140aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                        || mLeftKeys == 0 || mRightKeys == 1) {
141aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    return 0;
142aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                }
143aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                return -1;
144aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            }
145aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
14632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            // Return key position according to column count (0 is default).
14732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            /* package */int getColumnPos(int n) {
148aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
149aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            }
150aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
151aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            private int getFixedOrderColumnPos(int n) {
152aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int col = n % mNumColumns;
153aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int row = n / mNumColumns;
154aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                if (!isTopRow(row)) {
155aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    return col - mLeftKeys;
156aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                }
157aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int rightSideKeys = mTopKeys / 2;
158aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
159aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int pos = col - leftSideKeys;
160aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
161aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int numRightKeys = mRightKeys - 1;
162aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
163aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    return pos;
164aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                } else if (numRightKeys < rightSideKeys) {
165aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    return pos - (rightSideKeys - numRightKeys);
166aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                } else { // numLeftKeys < leftSideKeys
167aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    return pos + (leftSideKeys - numLeftKeys);
168aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                }
169aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            }
170aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
171aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            private int getAutomaticColumnPos(int n) {
17232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                final int col = n % mNumColumns;
173aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int row = n / mNumColumns;
174aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                int leftKeys = mLeftKeys;
175aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                if (isTopRow(row)) {
176aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    leftKeys += mTopRowAdjustment;
177aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                }
17832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                if (col == 0) {
17932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    // default position.
18032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    return 0;
18132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
182aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
18332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                int pos = 0;
18432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                int right = 1; // include default position key.
18532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                int left = 0;
18632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                int i = 0;
18732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                while (true) {
18832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    // Assign right key if available.
18932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    if (right < mRightKeys) {
19032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        pos = right;
19132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        right++;
19232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        i++;
19332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    }
19432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    if (i >= col)
19532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        break;
19632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    // Assign left key if available.
197aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    if (left < leftKeys) {
19832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        left++;
19932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        pos = -left;
20032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        i++;
20132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    }
20232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    if (i >= col)
20332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        break;
20432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
20532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                return pos;
20632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
20732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
20832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            private static int getTopRowEmptySlots(int numKeys, int numColumns) {
209aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                final int remainings = numKeys % numColumns;
210aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                return remainings == 0 ? 0 : numColumns - remainings;
21132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
21232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
21332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            private int getOptimizedColumns(int numKeys, int maxColumns) {
21432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                int numColumns = Math.min(numKeys, maxColumns);
21532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
21632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    numColumns--;
21732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
21832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                return numColumns;
21932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
22032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
22132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            public int getDefaultKeyCoordX() {
22232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                return mLeftKeys * mDefaultKeyWidth;
22332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
22432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
22532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            public int getX(int n, int row) {
22632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
22732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                if (isTopRow(row)) {
22832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
22932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
23032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                return x;
23132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
23232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
23332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            public int getY(int row) {
23432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
23532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
23632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
2372fc4248700023853980b0006c12425079e3f9257Tadashi G. Takaoka            public void markAsEdgeKey(Key key, int row) {
23832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                if (row == 0)
2392fc4248700023853980b0006c12425079e3f9257Tadashi G. Takaoka                    key.markAsTopEdge(this);
24032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                if (isTopRow(row))
2412fc4248700023853980b0006c12425079e3f9257Tadashi G. Takaoka                    key.markAsBottomEdge(this);
24232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
24332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
24432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            private boolean isTopRow(int rowCount) {
245aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                return mNumRows > 1 && rowCount == mNumRows - 1;
24632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
24732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka        }
24832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
24932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka        public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
2502affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            super(view.getContext(), new MoreKeysKeyboardParams());
251645128af712961456a42cbcc34c0cdf5f0b40a83Tadashi G. Takaoka            load(xmlId, parentKeyboard.mId);
25232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
2532affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
254ea0c567f86bd19015d53fc038c4579df776cfec3Tadashi G. Takaoka            // Should revise the algorithm.
255ea0c567f86bd19015d53fc038c4579df776cfec3Tadashi G. Takaoka            mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
2569d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            mMoreKeys = parentKey.mMoreKeys;
25732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
258a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka            final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth;
259a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka            final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight;
260a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka            final int width, height;
2612affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            // Use pre-computed width and height if these values are available and more keys
2622affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            // keyboard has only one key to mitigate visual flicker between key preview and more
2632affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            // keys keyboard.
264a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka            if (view.isKeyPreviewPopupEnabled() && mMoreKeys.length == 1 && previewWidth > 0
265a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka                    && previewHeight > 0) {
266a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka                width = previewWidth;
267a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka                height = previewHeight + mParams.mVerticalGap;
268a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka            } else {
269a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka                width = getMaxKeyWidth(view, parentKey.mMoreKeys, mParams.mDefaultKeyWidth);
270a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka                height = parentKeyboard.mMostCommonKeyHeight;
271a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka            }
272a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka            mParams.setParameters(mMoreKeys.length, parentKey.mMaxMoreKeysColumn, width, height,
2736213983fa147d010ce30314f43b0a1800d2528baTadashi G. Takaoka                    parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth());
27432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka        }
27532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
276bd7b160cfb05ee543e3cb6ddc7bd231b3f3aba0bTadashi G. Takaoka        private static int getMaxKeyWidth(KeyboardView view, String[] moreKeys, int minKeyWidth) {
277be34d973349909196dc3427a5653f4e119092ea7Tadashi G. Takaoka            final int padding = (int) view.getContext().getResources()
2782affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka                    .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding);
27932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            Paint paint = null;
2802315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka            int maxWidth = minKeyWidth;
281bd7b160cfb05ee543e3cb6ddc7bd231b3f3aba0bTadashi G. Takaoka            for (String moreKeySpec : moreKeys) {
282e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaoka                final String label = KeySpecParser.getLabel(moreKeySpec);
283bd7b160cfb05ee543e3cb6ddc7bd231b3f3aba0bTadashi G. Takaoka                // If the label is single letter, minKeyWidth is enough to hold the label.
28432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                if (label != null && label.length() > 1) {
28532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    if (paint == null) {
28632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        paint = new Paint();
28732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                        paint.setAntiAlias(true);
28832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                    }
289be34d973349909196dc3427a5653f4e119092ea7Tadashi G. Takaoka                    final int width = (int)view.getDefaultLabelWidth(label, paint) + padding;
2902315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka                    if (maxWidth < width) {
2912315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka                        maxWidth = width;
2922315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka                    }
29332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                }
29432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
295be34d973349909196dc3427a5653f4e119092ea7Tadashi G. Takaoka            return maxWidth;
29632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka        }
29732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka
29832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka        @Override
2992affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka        public MoreKeysKeyboard build() {
3002affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            final MoreKeysKeyboardParams params = mParams;
3019d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            for (int n = 0; n < mMoreKeys.length; n++) {
302bd7b160cfb05ee543e3cb6ddc7bd231b3f3aba0bTadashi G. Takaoka                final String moreKeySpec = mMoreKeys[n];
30332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                final int row = n / params.mNumColumns;
3049d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka                final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row),
3052fc4248700023853980b0006c12425079e3f9257Tadashi G. Takaoka                        params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight);
3062fc4248700023853980b0006c12425079e3f9257Tadashi G. Takaoka                params.markAsEdgeKey(key, row);
30732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka                params.onAddKey(key);
30832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka            }
3092affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka            return new MoreKeysKeyboard(params);
31032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka        }
31132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka    }
31204c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka}
313