Home | History | Annotate | Download | only in keyboard
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.graphics.Paint;
     20 
     21 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
     22 import com.android.inputmethod.keyboard.internal.KeyboardParams;
     23 import com.android.inputmethod.keyboard.internal.MoreKeySpecParser;
     24 import com.android.inputmethod.latin.R;
     25 
     26 public class MiniKeyboard extends Keyboard {
     27     private final int mDefaultKeyCoordX;
     28 
     29     private MiniKeyboard(Builder.MiniKeyboardParams params) {
     30         super(params);
     31         mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
     32     }
     33 
     34     public int getDefaultCoordX() {
     35         return mDefaultKeyCoordX;
     36     }
     37 
     38     public static class Builder extends KeyboardBuilder<Builder.MiniKeyboardParams> {
     39         private final CharSequence[] mMoreKeys;
     40 
     41         public static class MiniKeyboardParams extends KeyboardParams {
     42             /* package */int mTopRowAdjustment;
     43             public int mNumRows;
     44             public int mNumColumns;
     45             public int mLeftKeys;
     46             public int mRightKeys; // includes default key.
     47 
     48             public MiniKeyboardParams() {
     49                 super();
     50             }
     51 
     52             /* package for test */MiniKeyboardParams(int numKeys, int maxColumns, int keyWidth,
     53                     int rowHeight, int coordXInParent, int parentKeyboardWidth) {
     54                 super();
     55                 setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent,
     56                         parentKeyboardWidth);
     57             }
     58 
     59             /**
     60              * Set keyboard parameters of mini keyboard.
     61              *
     62              * @param numKeys number of keys in this mini keyboard.
     63              * @param maxColumns number of maximum columns of this mini keyboard.
     64              * @param keyWidth mini keyboard key width in pixel, including horizontal gap.
     65              * @param rowHeight mini keyboard row height in pixel, including vertical gap.
     66              * @param coordXInParent coordinate x of the popup key in parent keyboard.
     67              * @param parentKeyboardWidth parent keyboard width in pixel.
     68              */
     69             public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
     70                     int coordXInParent, int parentKeyboardWidth) {
     71                 if (parentKeyboardWidth / keyWidth < maxColumns) {
     72                     throw new IllegalArgumentException(
     73                             "Keyboard is too small to hold mini keyboard: " + parentKeyboardWidth
     74                                     + " " + keyWidth + " " + maxColumns);
     75                 }
     76                 mDefaultKeyWidth = keyWidth;
     77                 mDefaultRowHeight = rowHeight;
     78 
     79                 final int numRows = (numKeys + maxColumns - 1) / maxColumns;
     80                 mNumRows = numRows;
     81                 final int numColumns = getOptimizedColumns(numKeys, maxColumns);
     82                 mNumColumns = numColumns;
     83 
     84                 final int numLeftKeys = (numColumns - 1) / 2;
     85                 final int numRightKeys = numColumns - numLeftKeys; // including default key.
     86                 final int maxLeftKeys = coordXInParent / keyWidth;
     87                 final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent)
     88                         / keyWidth);
     89                 int leftKeys, rightKeys;
     90                 if (numLeftKeys > maxLeftKeys) {
     91                     leftKeys = maxLeftKeys;
     92                     rightKeys = numColumns - maxLeftKeys;
     93                 } else if (numRightKeys > maxRightKeys) {
     94                     leftKeys = numColumns - maxRightKeys;
     95                     rightKeys = maxRightKeys;
     96                 } else {
     97                     leftKeys = numLeftKeys;
     98                     rightKeys = numRightKeys;
     99                 }
    100                 // Shift right if the left edge of mini keyboard is on the edge of parent keyboard
    101                 // unless the parent key is on the left edge.
    102                 if (leftKeys * keyWidth >= coordXInParent && leftKeys > 0) {
    103                     leftKeys--;
    104                     rightKeys++;
    105                 }
    106                 // Shift left if the right edge of mini keyboard is on the edge of parent keyboard
    107                 // unless the parent key is on the right edge.
    108                 if (rightKeys * keyWidth + coordXInParent >= parentKeyboardWidth && rightKeys > 1) {
    109                     leftKeys++;
    110                     rightKeys--;
    111                 }
    112                 mLeftKeys = leftKeys;
    113                 mRightKeys = rightKeys;
    114 
    115                 // Centering of the top row.
    116                 final boolean onEdge = (leftKeys == 0 || rightKeys == 1);
    117                 if (numRows < 2 || onEdge || getTopRowEmptySlots(numKeys, numColumns) % 2 == 0) {
    118                     mTopRowAdjustment = 0;
    119                 } else if (mLeftKeys < mRightKeys - 1) {
    120                     mTopRowAdjustment = 1;
    121                 } else {
    122                     mTopRowAdjustment = -1;
    123                 }
    124 
    125                 mBaseWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
    126                 // Need to subtract the bottom row's gutter only.
    127                 mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
    128                         + mTopPadding + mBottomPadding;
    129             }
    130 
    131             // Return key position according to column count (0 is default).
    132             /* package */int getColumnPos(int n) {
    133                 final int col = n % mNumColumns;
    134                 if (col == 0) {
    135                     // default position.
    136                     return 0;
    137                 }
    138                 int pos = 0;
    139                 int right = 1; // include default position key.
    140                 int left = 0;
    141                 int i = 0;
    142                 while (true) {
    143                     // Assign right key if available.
    144                     if (right < mRightKeys) {
    145                         pos = right;
    146                         right++;
    147                         i++;
    148                     }
    149                     if (i >= col)
    150                         break;
    151                     // Assign left key if available.
    152                     if (left < mLeftKeys) {
    153                         left++;
    154                         pos = -left;
    155                         i++;
    156                     }
    157                     if (i >= col)
    158                         break;
    159                 }
    160                 return pos;
    161             }
    162 
    163             private static int getTopRowEmptySlots(int numKeys, int numColumns) {
    164                 final int remainingKeys = numKeys % numColumns;
    165                 if (remainingKeys == 0) {
    166                     return 0;
    167                 } else {
    168                     return numColumns - remainingKeys;
    169                 }
    170             }
    171 
    172             private int getOptimizedColumns(int numKeys, int maxColumns) {
    173                 int numColumns = Math.min(numKeys, maxColumns);
    174                 while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
    175                     numColumns--;
    176                 }
    177                 return numColumns;
    178             }
    179 
    180             public int getDefaultKeyCoordX() {
    181                 return mLeftKeys * mDefaultKeyWidth;
    182             }
    183 
    184             public int getX(int n, int row) {
    185                 final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
    186                 if (isTopRow(row)) {
    187                     return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
    188                 }
    189                 return x;
    190             }
    191 
    192             public int getY(int row) {
    193                 return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
    194             }
    195 
    196             public void markAsEdgeKey(Key key, int row) {
    197                 if (row == 0)
    198                     key.markAsTopEdge(this);
    199                 if (isTopRow(row))
    200                     key.markAsBottomEdge(this);
    201             }
    202 
    203             private boolean isTopRow(int rowCount) {
    204                 return rowCount == mNumRows - 1;
    205             }
    206         }
    207 
    208         public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
    209             super(view.getContext(), new MiniKeyboardParams());
    210             load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
    211 
    212             // TODO: Mini keyboard's vertical gap is currently calculated heuristically.
    213             // Should revise the algorithm.
    214             mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
    215             mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard;
    216             mMoreKeys = parentKey.mMoreKeys;
    217 
    218             final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth;
    219             final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight;
    220             final int width, height;
    221             // Use pre-computed width and height if these values are available and mini keyboard
    222             // has only one key to mitigate visual flicker between key preview and mini keyboard.
    223             if (view.isKeyPreviewPopupEnabled() && mMoreKeys.length == 1 && previewWidth > 0
    224                     && previewHeight > 0) {
    225                 width = previewWidth;
    226                 height = previewHeight + mParams.mVerticalGap;
    227             } else {
    228                 width = getMaxKeyWidth(view, parentKey.mMoreKeys, mParams.mDefaultKeyWidth);
    229                 height = parentKeyboard.mMostCommonKeyHeight;
    230             }
    231             mParams.setParameters(mMoreKeys.length, parentKey.mMaxMoreKeysColumn, width, height,
    232                     parentKey.mX + (mParams.mDefaultKeyWidth - width) / 2, view.getMeasuredWidth());
    233         }
    234 
    235         private static int getMaxKeyWidth(KeyboardView view, CharSequence[] moreKeys,
    236                 int minKeyWidth) {
    237             final int padding = (int) view.getContext().getResources()
    238                     .getDimension(R.dimen.mini_keyboard_key_horizontal_padding);
    239             Paint paint = null;
    240             int maxWidth = minKeyWidth;
    241             for (CharSequence moreKeySpec : moreKeys) {
    242                 final CharSequence label = MoreKeySpecParser.getLabel(moreKeySpec.toString());
    243                 // If the label is single letter, minKeyWidth is enough to hold
    244                 // the label.
    245                 if (label != null && label.length() > 1) {
    246                     if (paint == null) {
    247                         paint = new Paint();
    248                         paint.setAntiAlias(true);
    249                     }
    250                     final int width = (int)view.getDefaultLabelWidth(label, paint) + padding;
    251                     if (maxWidth < width) {
    252                         maxWidth = width;
    253                     }
    254                 }
    255             }
    256             return maxWidth;
    257         }
    258 
    259         @Override
    260         public MiniKeyboard build() {
    261             final MiniKeyboardParams params = mParams;
    262             for (int n = 0; n < mMoreKeys.length; n++) {
    263                 final String moreKeySpec = mMoreKeys[n].toString();
    264                 final int row = n / params.mNumColumns;
    265                 final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row),
    266                         params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight);
    267                 params.markAsEdgeKey(key, row);
    268                 params.onAddKey(key);
    269             }
    270             return new MiniKeyboard(params);
    271         }
    272     }
    273 }
    274