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; 205ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaokaimport android.graphics.drawable.Drawable; 217ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaokaimport android.view.View; 2232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 2335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyboardBuilder; 245ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 2535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyboardParams; 2635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.MoreKeySpec; 2732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaokaimport com.android.inputmethod.latin.R; 28cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaokaimport com.android.inputmethod.latin.StringUtils; 2904c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 30a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class MoreKeysKeyboard extends Keyboard { 318da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka private final int mDefaultKeyCoordX; 3204c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 3335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka MoreKeysKeyboard(final MoreKeysKeyboardParams params) { 348da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka super(params); 358da9a13760896cd78235b60d0ea680ea13620532Tadashi G. Takaoka mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; 3604c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka } 3704c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka 3804c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka public int getDefaultCoordX() { 3904c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka return mDefaultKeyCoordX; 4004c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka } 4132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 4235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka /* package for test */ 4335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka static class MoreKeysKeyboardParams extends KeyboardParams { 4435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public boolean mIsFixedOrder; 4535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka /* package */int mTopRowAdjustment; 4635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mNumRows; 4735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mNumColumns; 4835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mTopKeys; 4935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mLeftKeys; 5035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mRightKeys; // includes default key. 5135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mDividerWidth; 5235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int mColumnWidth; 5335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 5435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public MoreKeysKeyboardParams() { 5535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka super(); 5635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 57e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka 5835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka /** 5935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * Set keyboard parameters of more keys keyboard. 6035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * 6135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param numKeys number of keys in this more keys keyboard. 6235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param maxColumns number of maximum columns of this more keys keyboard. 6335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param keyWidth more keys keyboard key width in pixel, including horizontal gap. 6435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param rowHeight more keys keyboard row height in pixel, including vertical gap. 6535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param coordXInParent coordinate x of the key preview in parent keyboard. 6635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param parentKeyboardWidth parent keyboard width in pixel. 6735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param isFixedColumnOrder if true, more keys should be laid out in fixed order. 6835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * @param dividerWidth width of divider, zero for no dividers. 6935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka */ 7035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public void setParameters(final int numKeys, final int maxColumns, final int keyWidth, 7135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int rowHeight, final int coordXInParent, final int parentKeyboardWidth, 7235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final boolean isFixedColumnOrder, final int dividerWidth) { 7335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mIsFixedOrder = isFixedColumnOrder; 7435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (parentKeyboardWidth / keyWidth < maxColumns) { 7535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka throw new IllegalArgumentException( 7635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka "Keyboard is too small to hold more keys keyboard: " 7735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); 7832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 7935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mDefaultKeyWidth = keyWidth; 8035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mDefaultRowHeight = rowHeight; 8135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 8235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numRows = (numKeys + maxColumns - 1) / maxColumns; 8335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mNumRows = numRows; 8435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns) 8535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka : getOptimizedColumns(numKeys, maxColumns); 8635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mNumColumns = numColumns; 8735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int topKeys = numKeys % numColumns; 8835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mTopKeys = topKeys == 0 ? numColumns : topKeys; 8935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 9035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numLeftKeys = (numColumns - 1) / 2; 9135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numRightKeys = numColumns - numLeftKeys; // including default key. 9235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Maximum number of keys we can layout both side of the parent key 9335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int maxLeftKeys = coordXInParent / keyWidth; 9435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; 9535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int leftKeys, rightKeys; 9635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (numLeftKeys > maxLeftKeys) { 9735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys = maxLeftKeys; 9835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys = numColumns - leftKeys; 9935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else if (numRightKeys > maxRightKeys + 1) { 10035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys = maxRightKeys + 1; // include default key 10135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys = numColumns - rightKeys; 10235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else { 10335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys = numLeftKeys; 10435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys = numRightKeys; 10532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 10635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // If the left keys fill the left side of the parent key, entire more keys keyboard 10735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // should be shifted to the right unless the parent key is on the left edge. 10835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (maxLeftKeys == leftKeys && leftKeys > 0) { 10935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys--; 11035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys++; 111aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 11235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // If the right keys fill the right side of the parent key, entire more keys 11335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // should be shifted to the left unless the parent key is on the right edge. 11435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { 11535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys++; 11635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka rightKeys--; 117aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 11835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mLeftKeys = leftKeys; 11935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mRightKeys = rightKeys; 12035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 12135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Adjustment of the top row. 12235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment() 12335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka : getAutoOrderTopRowAdjustment(); 12435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mDividerWidth = dividerWidth; 12535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mColumnWidth = mDefaultKeyWidth + mDividerWidth; 12635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; 12735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Need to subtract the bottom row's gutter only. 12835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap 12935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka + mTopPadding + mBottomPadding; 13035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 131aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 13235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getFixedOrderTopRowAdjustment() { 13335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns 13435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka || mLeftKeys == 0 || mRightKeys == 1) { 13535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return 0; 136aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 13735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return -1; 13835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 139aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 14035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getAutoOrderTopRowAdjustment() { 14135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 14235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka || mLeftKeys == 0 || mRightKeys == 1) { 14335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return 0; 144aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka } 14535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return -1; 14635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 147aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 14835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Return key position according to column count (0 is default). 14935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka /* package */int getColumnPos(final int n) { 15035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); 15135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 152aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka 15335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getFixedOrderColumnPos(final int n) { 15435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int col = n % mNumColumns; 15535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int row = n / mNumColumns; 15635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (!isTopRow(row)) { 15735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return col - mLeftKeys; 15835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 15935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int rightSideKeys = mTopKeys / 2; 16035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int leftSideKeys = mTopKeys - (rightSideKeys + 1); 16135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int pos = col - leftSideKeys; 16235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numLeftKeys = mLeftKeys + mTopRowAdjustment; 16335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int numRightKeys = mRightKeys - 1; 16435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { 16532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka return pos; 16635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else if (numRightKeys < rightSideKeys) { 16735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return pos - (rightSideKeys - numRightKeys); 16835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } else { // numLeftKeys < leftSideKeys 16935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return pos + (leftSideKeys - numLeftKeys); 17032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 17135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 17232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 17335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getAutomaticColumnPos(final int n) { 17435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int col = n % mNumColumns; 17535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int row = n / mNumColumns; 17635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int leftKeys = mLeftKeys; 17735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (isTopRow(row)) { 17835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka leftKeys += mTopRowAdjustment; 17935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 18035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (col == 0) { 18135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // default position. 18235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return 0; 18332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 18432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 18535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int pos = 0; 18635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int right = 1; // include default position key. 18735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int left = 0; 18835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int i = 0; 18935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka while (true) { 19035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Assign right key if available. 19135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (right < mRightKeys) { 19235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka pos = right; 19335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka right++; 19435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka i++; 19532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 19635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (i >= col) 19735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka break; 19835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // Assign left key if available. 19935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (left < leftKeys) { 20035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka left++; 20135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka pos = -left; 20235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka i++; 20335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 20435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (i >= col) 20535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka break; 20632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 20735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return pos; 20835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 20932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 21035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { 21135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int remainings = numKeys % numColumns; 21235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return remainings == 0 ? 0 : numColumns - remainings; 21335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 21432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 21535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private int getOptimizedColumns(final int numKeys, final int maxColumns) { 21635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka int numColumns = Math.min(numKeys, maxColumns); 21735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { 21835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka numColumns--; 21932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 22035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return numColumns; 22135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 22232572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 22335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int getDefaultKeyCoordX() { 22435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return mLeftKeys * mColumnWidth; 22535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 22632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 22735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int getX(final int n, final int row) { 22835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); 22935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (isTopRow(row)) { 23035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return x + mTopRowAdjustment * (mColumnWidth / 2); 23132572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 23235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return x; 23335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 23432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 23535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public int getY(final int row) { 23635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; 23732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 23832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 23935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public void markAsEdgeKey(final Key key, final int row) { 24035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (row == 0) 24135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka key.markAsTopEdge(this); 24235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka if (isTopRow(row)) 24335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka key.markAsBottomEdge(this); 24435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 24535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 24635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private boolean isTopRow(final int rowCount) { 24735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return mNumRows > 1 && rowCount == mNumRows - 1; 24835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 24935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 25035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 25135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> { 25235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private final Key mParentKey; 25335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private final Drawable mDivider; 25435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 25535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static final float LABEL_PADDING_RATIO = 0.2f; 25635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static final float DIVIDER_RATIO = 0.2f; 25735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 25835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 2597ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka /** 2607ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka * The builder of MoreKeysKeyboard. 2617ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka * @param containerView the container of {@link MoreKeysKeyboardView}. 2627ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka * @param parentKey the {@link Key} that invokes more keys keyboard. 2637ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey. 2647ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka */ 26535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public Builder(final View containerView, final Key parentKey, 26635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final KeyboardView parentKeyboardView) { 2677ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka super(containerView.getContext(), new MoreKeysKeyboardParams()); 2687ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka final Keyboard parentKeyboard = parentKeyboardView.getKeyboard(); 2697ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId); 27032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 2712affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka // TODO: More keys keyboard's vertical gap is currently calculated heuristically. 272ea0c567f86bd19015d53fc038c4579df776cfec3Tadashi G. Takaoka // Should revise the algorithm. 273ea0c567f86bd19015d53fc038c4579df776cfec3Tadashi G. Takaoka mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2; 27442fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka mParentKey = parentKey; 27532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 276a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka final int width, height; 2777ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled() 2787ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1; 27942fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka if (singleMoreKeyWithPreview) { 2807ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // Use pre-computed width and height if this more keys keyboard has only one key to 2817ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // mitigate visual flicker between key preview and more keys keyboard. 2827ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // Caveats for the visual assets: To achieve this effect, both the key preview 2837ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // backgrounds and the more keys keyboard panel background have the exact same 2847ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // left/right/top paddings. The bottom paddings of both backgrounds don't need to 2857ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // be considered because the vertical positions of both backgrounds were already 2867ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka // adjusted with their bottom paddings deducted. 2877ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka width = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleWidth; 2887ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka height = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleHeight 2897ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka + mParams.mVerticalGap; 290a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka } else { 2917ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth); 292a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka height = parentKeyboard.mMostCommonKeyHeight; 293a0e4f40994f779ad98268921c63d6535ad04224fTadashi G. Takaoka } 2945ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int dividerWidth; 2955ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka if (parentKey.needsDividersInMoreKeys()) { 2965ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka mDivider = mResources.getDrawable(R.drawable.more_keys_divider); 2975ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka dividerWidth = (int)(width * DIVIDER_RATIO); 2985ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka } else { 2995ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka mDivider = null; 3005ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka dividerWidth = 0; 3015ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka } 30242fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(), 3037ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka width, height, parentKey.mX + parentKey.mWidth / 2, 3047ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka parentKeyboardView.getMeasuredWidth(), parentKey.isFixedColumnOrderMoreKeys(), 3057ecc1081ab9b4e41e4b2aec7877aaaf8df29e611Tadashi G. Takaoka dividerWidth); 30632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 30732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 30835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey, 30935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int minKeyWidth) { 310e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka final int padding = (int)(view.getResources() 311e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding) 312e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0)); 31372913f97edb74e877f78b25418a568a4b0f5ae5cTadashi G. Takaoka final Paint paint = view.newDefaultLabelPaint(); 314dc34da218a22489d92d1015e9e5dac8d951b89f4Tadashi G. Takaoka paint.setTypeface(parentKey.selectTypeface(view.mKeyDrawParams)); 315dc34da218a22489d92d1015e9e5dac8d951b89f4Tadashi G. Takaoka paint.setTextSize(parentKey.selectMoreKeyTextSize(view.mKeyDrawParams)); 3162315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka int maxWidth = minKeyWidth; 317ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka for (final MoreKeySpec spec : parentKey.mMoreKeys) { 318ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka final String label = spec.mLabel; 319bd7b160cfb05ee543e3cb6ddc7bd231b3f3aba0bTadashi G. Takaoka // If the label is single letter, minKeyWidth is enough to hold the label. 320cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka if (label != null && StringUtils.codePointCount(label) > 1) { 32172913f97edb74e877f78b25418a568a4b0f5ae5cTadashi G. Takaoka final int width = (int)view.getLabelWidth(label, paint) + padding; 3222315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka if (maxWidth < width) { 3232315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka maxWidth = width; 3242315bfc7c8df0f6d9fb627456f2a298f5580b52dTadashi G. Takaoka } 32532572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 32632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 327be34d973349909196dc3427a5653f4e119092ea7Tadashi G. Takaoka return maxWidth; 32832572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 32932572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka 33032572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka @Override 3312affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka public MoreKeysKeyboard build() { 3322affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka final MoreKeysKeyboardParams params = mParams; 333ab0d0d8a021a9b0f179281ac9e18604ad331cc43Tadashi G. Takaoka final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags(); 334ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka final MoreKeySpec[] moreKeys = mParentKey.mMoreKeys; 33542fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka for (int n = 0; n < moreKeys.length; n++) { 336ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka final MoreKeySpec moreKeySpec = moreKeys[n]; 33732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka final int row = n / params.mNumColumns; 3385ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int x = params.getX(n, row); 3395ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int y = params.getY(row); 340ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka final Key key = new Key(params, moreKeySpec, x, y, 3415ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags); 3422fc4248700023853980b0006c12425079e3f9257Tadashi G. Takaoka params.markAsEdgeKey(key, row); 34332572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka params.onAddKey(key); 3445ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka 3455ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int pos = params.getColumnPos(n); 3465ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka // The "pos" value represents the offset from the default position. Negative means 3475ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka // left of the default position. 3485ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka if (params.mDividerWidth > 0 && pos != 0) { 3495ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final int dividerX = (pos > 0) ? x - params.mDividerWidth 3505ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka : x + params.mDefaultKeyWidth; 3515ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y); 3525ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka params.onAddKey(divider); 3535ef4fccbb90491e1f6c2e87b47ebf9f3659949fbTadashi G. Takaoka } 35432572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 3552affaf91a04d63e0994102299816014a8bbe11e1Tadashi G. Takaoka return new MoreKeysKeyboard(params); 35632572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 35732572948d7e3956efebcbd69d7c7d8403bb659e6Tadashi G. Takaoka } 35835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 35935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private static class MoreKeyDivider extends Key.Spacer { 36035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka private final Drawable mIcon; 36135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 36235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon, 36335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka final int x, final int y) { 36435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight); 36535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mIcon = icon; 36635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 36735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka 36835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka @Override 36935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 37035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the 37135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // constructor. 37235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka // TODO: Drawable itself should have an alpha value. 37335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka mIcon.setAlpha(128); 37435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka return mIcon; 37535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 37635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka } 37704c96ab966e8a58e5cd401362b49509751ce75d9Tadashi G. Takaoka} 378