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 import android.graphics.drawable.Drawable;
     21 import android.view.View;
     22 
     23 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
     24 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
     25 import com.android.inputmethod.keyboard.internal.KeyboardParams;
     26 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
     27 import com.android.inputmethod.latin.R;
     28 import com.android.inputmethod.latin.StringUtils;
     29 
     30 public final class MoreKeysKeyboard extends Keyboard {
     31     private final int mDefaultKeyCoordX;
     32 
     33     MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
     34         super(params);
     35         mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
     36     }
     37 
     38     public int getDefaultCoordX() {
     39         return mDefaultKeyCoordX;
     40     }
     41 
     42     /* package for test */
     43     static class MoreKeysKeyboardParams extends KeyboardParams {
     44         public boolean mIsFixedOrder;
     45         /* package */int mTopRowAdjustment;
     46         public int mNumRows;
     47         public int mNumColumns;
     48         public int mTopKeys;
     49         public int mLeftKeys;
     50         public int mRightKeys; // includes default key.
     51         public int mDividerWidth;
     52         public int mColumnWidth;
     53 
     54         public MoreKeysKeyboardParams() {
     55             super();
     56         }
     57 
     58         /**
     59          * Set keyboard parameters of more keys keyboard.
     60          *
     61          * @param numKeys number of keys in this more keys keyboard.
     62          * @param maxColumns number of maximum columns of this more keys keyboard.
     63          * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
     64          * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
     65          * @param coordXInParent coordinate x of the key preview in parent keyboard.
     66          * @param parentKeyboardWidth parent keyboard width in pixel.
     67          * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
     68          * @param dividerWidth width of divider, zero for no dividers.
     69          */
     70         public void setParameters(final int numKeys, final int maxColumns, final int keyWidth,
     71                 final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
     72                 final boolean isFixedColumnOrder, final int dividerWidth) {
     73             mIsFixedOrder = isFixedColumnOrder;
     74             if (parentKeyboardWidth / keyWidth < maxColumns) {
     75                 throw new IllegalArgumentException(
     76                         "Keyboard is too small to hold more keys keyboard: "
     77                                 + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
     78             }
     79             mDefaultKeyWidth = keyWidth;
     80             mDefaultRowHeight = rowHeight;
     81 
     82             final int numRows = (numKeys + maxColumns - 1) / maxColumns;
     83             mNumRows = numRows;
     84             final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
     85                     : getOptimizedColumns(numKeys, maxColumns);
     86             mNumColumns = numColumns;
     87             final int topKeys = numKeys % numColumns;
     88             mTopKeys = topKeys == 0 ? numColumns : topKeys;
     89 
     90             final int numLeftKeys = (numColumns - 1) / 2;
     91             final int numRightKeys = numColumns - numLeftKeys; // including default key.
     92             // Maximum number of keys we can layout both side of the parent key
     93             final int maxLeftKeys = coordXInParent / keyWidth;
     94             final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
     95             int leftKeys, rightKeys;
     96             if (numLeftKeys > maxLeftKeys) {
     97                 leftKeys = maxLeftKeys;
     98                 rightKeys = numColumns - leftKeys;
     99             } else if (numRightKeys > maxRightKeys + 1) {
    100                 rightKeys = maxRightKeys + 1; // include default key
    101                 leftKeys = numColumns - rightKeys;
    102             } else {
    103                 leftKeys = numLeftKeys;
    104                 rightKeys = numRightKeys;
    105             }
    106             // If the left keys fill the left side of the parent key, entire more keys keyboard
    107             // should be shifted to the right unless the parent key is on the left edge.
    108             if (maxLeftKeys == leftKeys && leftKeys > 0) {
    109                 leftKeys--;
    110                 rightKeys++;
    111             }
    112             // If the right keys fill the right side of the parent key, entire more keys
    113             // should be shifted to the left unless the parent key is on the right edge.
    114             if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
    115                 leftKeys++;
    116                 rightKeys--;
    117             }
    118             mLeftKeys = leftKeys;
    119             mRightKeys = rightKeys;
    120 
    121             // Adjustment of the top row.
    122             mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
    123                     : getAutoOrderTopRowAdjustment();
    124             mDividerWidth = dividerWidth;
    125             mColumnWidth = mDefaultKeyWidth + mDividerWidth;
    126             mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
    127             // Need to subtract the bottom row's gutter only.
    128             mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
    129                     + mTopPadding + mBottomPadding;
    130         }
    131 
    132         private int getFixedOrderTopRowAdjustment() {
    133             if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
    134                     || mLeftKeys == 0  || mRightKeys == 1) {
    135                 return 0;
    136             }
    137             return -1;
    138         }
    139 
    140         private int getAutoOrderTopRowAdjustment() {
    141             if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
    142                     || mLeftKeys == 0 || mRightKeys == 1) {
    143                 return 0;
    144             }
    145             return -1;
    146         }
    147 
    148         // Return key position according to column count (0 is default).
    149         /* package */int getColumnPos(final int n) {
    150             return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
    151         }
    152 
    153         private int getFixedOrderColumnPos(final int n) {
    154             final int col = n % mNumColumns;
    155             final int row = n / mNumColumns;
    156             if (!isTopRow(row)) {
    157                 return col - mLeftKeys;
    158             }
    159             final int rightSideKeys = mTopKeys / 2;
    160             final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
    161             final int pos = col - leftSideKeys;
    162             final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
    163             final int numRightKeys = mRightKeys - 1;
    164             if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
    165                 return pos;
    166             } else if (numRightKeys < rightSideKeys) {
    167                 return pos - (rightSideKeys - numRightKeys);
    168             } else { // numLeftKeys < leftSideKeys
    169                 return pos + (leftSideKeys - numLeftKeys);
    170             }
    171         }
    172 
    173         private int getAutomaticColumnPos(final int n) {
    174             final int col = n % mNumColumns;
    175             final int row = n / mNumColumns;
    176             int leftKeys = mLeftKeys;
    177             if (isTopRow(row)) {
    178                 leftKeys += mTopRowAdjustment;
    179             }
    180             if (col == 0) {
    181                 // default position.
    182                 return 0;
    183             }
    184 
    185             int pos = 0;
    186             int right = 1; // include default position key.
    187             int left = 0;
    188             int i = 0;
    189             while (true) {
    190                 // Assign right key if available.
    191                 if (right < mRightKeys) {
    192                     pos = right;
    193                     right++;
    194                     i++;
    195                 }
    196                 if (i >= col)
    197                     break;
    198                 // Assign left key if available.
    199                 if (left < leftKeys) {
    200                     left++;
    201                     pos = -left;
    202                     i++;
    203                 }
    204                 if (i >= col)
    205                     break;
    206             }
    207             return pos;
    208         }
    209 
    210         private static int getTopRowEmptySlots(final int numKeys, final int numColumns) {
    211             final int remainings = numKeys % numColumns;
    212             return remainings == 0 ? 0 : numColumns - remainings;
    213         }
    214 
    215         private int getOptimizedColumns(final int numKeys, final int maxColumns) {
    216             int numColumns = Math.min(numKeys, maxColumns);
    217             while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
    218                 numColumns--;
    219             }
    220             return numColumns;
    221         }
    222 
    223         public int getDefaultKeyCoordX() {
    224             return mLeftKeys * mColumnWidth;
    225         }
    226 
    227         public int getX(final int n, final int row) {
    228             final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
    229             if (isTopRow(row)) {
    230                 return x + mTopRowAdjustment * (mColumnWidth / 2);
    231             }
    232             return x;
    233         }
    234 
    235         public int getY(final int row) {
    236             return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
    237         }
    238 
    239         public void markAsEdgeKey(final Key key, final int row) {
    240             if (row == 0)
    241                 key.markAsTopEdge(this);
    242             if (isTopRow(row))
    243                 key.markAsBottomEdge(this);
    244         }
    245 
    246         private boolean isTopRow(final int rowCount) {
    247             return mNumRows > 1 && rowCount == mNumRows - 1;
    248         }
    249     }
    250 
    251     public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> {
    252         private final Key mParentKey;
    253         private final Drawable mDivider;
    254 
    255         private static final float LABEL_PADDING_RATIO = 0.2f;
    256         private static final float DIVIDER_RATIO = 0.2f;
    257 
    258 
    259         /**
    260          * The builder of MoreKeysKeyboard.
    261          * @param containerView the container of {@link MoreKeysKeyboardView}.
    262          * @param parentKey the {@link Key} that invokes more keys keyboard.
    263          * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
    264          */
    265         public Builder(final View containerView, final Key parentKey,
    266                 final KeyboardView parentKeyboardView) {
    267             super(containerView.getContext(), new MoreKeysKeyboardParams());
    268             final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
    269             load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
    270 
    271             // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
    272             // Should revise the algorithm.
    273             mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
    274             mParentKey = parentKey;
    275 
    276             final int width, height;
    277             final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
    278                     && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1;
    279             if (singleMoreKeyWithPreview) {
    280                 // Use pre-computed width and height if this more keys keyboard has only one key to
    281                 // mitigate visual flicker between key preview and more keys keyboard.
    282                 // Caveats for the visual assets: To achieve this effect, both the key preview
    283                 // backgrounds and the more keys keyboard panel background have the exact same
    284                 // left/right/top paddings. The bottom paddings of both backgrounds don't need to
    285                 // be considered because the vertical positions of both backgrounds were already
    286                 // adjusted with their bottom paddings deducted.
    287                 width = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleWidth;
    288                 height = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleHeight
    289                         + mParams.mVerticalGap;
    290             } else {
    291                 width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth);
    292                 height = parentKeyboard.mMostCommonKeyHeight;
    293             }
    294             final int dividerWidth;
    295             if (parentKey.needsDividersInMoreKeys()) {
    296                 mDivider = mResources.getDrawable(R.drawable.more_keys_divider);
    297                 dividerWidth = (int)(width * DIVIDER_RATIO);
    298             } else {
    299                 mDivider = null;
    300                 dividerWidth = 0;
    301             }
    302             mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
    303                     width, height, parentKey.mX + parentKey.mWidth / 2,
    304                     parentKeyboardView.getMeasuredWidth(), parentKey.isFixedColumnOrderMoreKeys(),
    305                     dividerWidth);
    306         }
    307 
    308         private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
    309                 final int minKeyWidth) {
    310             final int padding = (int)(view.getResources()
    311                     .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
    312                     + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
    313             final Paint paint = view.newDefaultLabelPaint();
    314             paint.setTypeface(parentKey.selectTypeface(view.mKeyDrawParams));
    315             paint.setTextSize(parentKey.selectMoreKeyTextSize(view.mKeyDrawParams));
    316             int maxWidth = minKeyWidth;
    317             for (final MoreKeySpec spec : parentKey.mMoreKeys) {
    318                 final String label = spec.mLabel;
    319                 // If the label is single letter, minKeyWidth is enough to hold the label.
    320                 if (label != null && StringUtils.codePointCount(label) > 1) {
    321                     final int width = (int)view.getLabelWidth(label, paint) + padding;
    322                     if (maxWidth < width) {
    323                         maxWidth = width;
    324                     }
    325                 }
    326             }
    327             return maxWidth;
    328         }
    329 
    330         @Override
    331         public MoreKeysKeyboard build() {
    332             final MoreKeysKeyboardParams params = mParams;
    333             final int moreKeyFlags = mParentKey.getMoreKeyLabelFlags();
    334             final MoreKeySpec[] moreKeys = mParentKey.mMoreKeys;
    335             for (int n = 0; n < moreKeys.length; n++) {
    336                 final MoreKeySpec moreKeySpec = moreKeys[n];
    337                 final int row = n / params.mNumColumns;
    338                 final int x = params.getX(n, row);
    339                 final int y = params.getY(row);
    340                 final Key key = new Key(params, moreKeySpec, x, y,
    341                         params.mDefaultKeyWidth, params.mDefaultRowHeight, moreKeyFlags);
    342                 params.markAsEdgeKey(key, row);
    343                 params.onAddKey(key);
    344 
    345                 final int pos = params.getColumnPos(n);
    346                 // The "pos" value represents the offset from the default position. Negative means
    347                 // left of the default position.
    348                 if (params.mDividerWidth > 0 && pos != 0) {
    349                     final int dividerX = (pos > 0) ? x - params.mDividerWidth
    350                             : x + params.mDefaultKeyWidth;
    351                     final Key divider = new MoreKeyDivider(params, mDivider, dividerX, y);
    352                     params.onAddKey(divider);
    353                 }
    354             }
    355             return new MoreKeysKeyboard(params);
    356         }
    357     }
    358 
    359     private static class MoreKeyDivider extends Key.Spacer {
    360         private final Drawable mIcon;
    361 
    362         public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon,
    363                 final int x, final int y) {
    364             super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
    365             mIcon = icon;
    366         }
    367 
    368         @Override
    369         public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
    370             // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
    371             // constructor.
    372             // TODO: Drawable itself should have an alpha value.
    373             mIcon.setAlpha(128);
    374             return mIcon;
    375         }
    376     }
    377 }
    378