Home | History | Annotate | Download | only in suggestions
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.latin.suggestions;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Paint;
     22 import android.graphics.drawable.Drawable;
     23 
     24 import com.android.inputmethod.keyboard.Key;
     25 import com.android.inputmethod.keyboard.Keyboard;
     26 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
     27 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
     28 import com.android.inputmethod.keyboard.internal.KeyboardParams;
     29 import com.android.inputmethod.latin.Constants;
     30 import com.android.inputmethod.latin.R;
     31 import com.android.inputmethod.latin.SuggestedWords;
     32 import com.android.inputmethod.latin.utils.TypefaceUtils;
     33 
     34 public final class MoreSuggestions extends Keyboard {
     35     public final SuggestedWords mSuggestedWords;
     36 
     37     MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) {
     38         super(params);
     39         mSuggestedWords = suggestedWords;
     40     }
     41 
     42     private static final class MoreSuggestionsParam extends KeyboardParams {
     43         private final int[] mWidths = new int[SuggestedWords.MAX_SUGGESTIONS];
     44         private final int[] mRowNumbers = new int[SuggestedWords.MAX_SUGGESTIONS];
     45         private final int[] mColumnOrders = new int[SuggestedWords.MAX_SUGGESTIONS];
     46         private final int[] mNumColumnsInRow = new int[SuggestedWords.MAX_SUGGESTIONS];
     47         private static final int MAX_COLUMNS_IN_ROW = 3;
     48         private int mNumRows;
     49         public Drawable mDivider;
     50         public int mDividerWidth;
     51 
     52         public MoreSuggestionsParam() {
     53             super();
     54         }
     55 
     56         public int layout(final SuggestedWords suggestedWords, final int fromIndex,
     57                 final int maxWidth, final int minWidth, final int maxRow, final Paint paint,
     58                 final Resources res) {
     59             clearKeys();
     60             mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
     61             mDividerWidth = mDivider.getIntrinsicWidth();
     62             final float padding = res.getDimension(
     63                     R.dimen.config_more_suggestions_key_horizontal_padding);
     64 
     65             int row = 0;
     66             int index = fromIndex;
     67             int rowStartIndex = fromIndex;
     68             final int size = Math.min(suggestedWords.size(), SuggestedWords.MAX_SUGGESTIONS);
     69             while (index < size) {
     70                 final String word;
     71                 if (isIndexSubjectToAutoCorrection(suggestedWords, index)) {
     72                     // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped.
     73                     word = suggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD);
     74                 } else {
     75                     word = suggestedWords.getLabel(index);
     76                 }
     77                 // TODO: Should take care of text x-scaling.
     78                 mWidths[index] = (int)(TypefaceUtils.getStringWidth(word, paint) + padding);
     79                 final int numColumn = index - rowStartIndex + 1;
     80                 final int columnWidth =
     81                         (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
     82                 if (numColumn > MAX_COLUMNS_IN_ROW
     83                         || !fitInWidth(rowStartIndex, index + 1, columnWidth)) {
     84                     if ((row + 1) >= maxRow) {
     85                         break;
     86                     }
     87                     mNumColumnsInRow[row] = index - rowStartIndex;
     88                     rowStartIndex = index;
     89                     row++;
     90                 }
     91                 mColumnOrders[index] = index - rowStartIndex;
     92                 mRowNumbers[index] = row;
     93                 index++;
     94             }
     95             mNumColumnsInRow[row] = index - rowStartIndex;
     96             mNumRows = row + 1;
     97             mBaseWidth = mOccupiedWidth = Math.max(
     98                     minWidth, calcurateMaxRowWidth(fromIndex, index));
     99             mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
    100             return index - fromIndex;
    101         }
    102 
    103         private boolean fitInWidth(final int startIndex, final int endIndex, final int width) {
    104             for (int index = startIndex; index < endIndex; index++) {
    105                 if (mWidths[index] > width)
    106                     return false;
    107             }
    108             return true;
    109         }
    110 
    111         private int calcurateMaxRowWidth(final int startIndex, final int endIndex) {
    112             int maxRowWidth = 0;
    113             int index = startIndex;
    114             for (int row = 0; row < mNumRows; row++) {
    115                 final int numColumnInRow = mNumColumnsInRow[row];
    116                 int maxKeyWidth = 0;
    117                 while (index < endIndex && mRowNumbers[index] == row) {
    118                     maxKeyWidth = Math.max(maxKeyWidth, mWidths[index]);
    119                     index++;
    120                 }
    121                 maxRowWidth = Math.max(maxRowWidth,
    122                         maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
    123             }
    124             return maxRowWidth;
    125         }
    126 
    127         private static final int[][] COLUMN_ORDER_TO_NUMBER = {
    128             { 0 }, // center
    129             { 1, 0 }, // right-left
    130             { 1, 0, 2 }, // center-left-right
    131         };
    132 
    133         public int getNumColumnInRow(final int index) {
    134             return mNumColumnsInRow[mRowNumbers[index]];
    135         }
    136 
    137         public int getColumnNumber(final int index) {
    138             final int columnOrder = mColumnOrders[index];
    139             final int numColumn = getNumColumnInRow(index);
    140             return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
    141         }
    142 
    143         public int getX(final int index) {
    144             final int columnNumber = getColumnNumber(index);
    145             return columnNumber * (getWidth(index) + mDividerWidth);
    146         }
    147 
    148         public int getY(final int index) {
    149             final int row = mRowNumbers[index];
    150             return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
    151         }
    152 
    153         public int getWidth(final int index) {
    154             final int numColumnInRow = getNumColumnInRow(index);
    155             return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
    156         }
    157 
    158         public void markAsEdgeKey(final Key key, final int index) {
    159             final int row = mRowNumbers[index];
    160             if (row == 0)
    161                 key.markAsBottomEdge(this);
    162             if (row == mNumRows - 1)
    163                 key.markAsTopEdge(this);
    164 
    165             final int numColumnInRow = mNumColumnsInRow[row];
    166             final int column = getColumnNumber(index);
    167             if (column == 0)
    168                 key.markAsLeftEdge(this);
    169             if (column == numColumnInRow - 1)
    170                 key.markAsRightEdge(this);
    171         }
    172     }
    173 
    174     static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords,
    175             final int index) {
    176         return suggestedWords.mWillAutoCorrect && index == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
    177     }
    178 
    179     public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
    180         private final MoreSuggestionsView mPaneView;
    181         private SuggestedWords mSuggestedWords;
    182         private int mFromIndex;
    183         private int mToIndex;
    184 
    185         public Builder(final Context context, final MoreSuggestionsView paneView) {
    186             super(context, new MoreSuggestionsParam());
    187             mPaneView = paneView;
    188         }
    189 
    190         public Builder layout(final SuggestedWords suggestedWords, final int fromIndex,
    191                 final int maxWidth, final int minWidth, final int maxRow,
    192                 final Keyboard parentKeyboard) {
    193             final int xmlId = R.xml.kbd_suggestions_pane_template;
    194             load(xmlId, parentKeyboard.mId);
    195             mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
    196             mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
    197             final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow,
    198                     mPaneView.newLabelPaint(null /* key */), mResources);
    199             mFromIndex = fromIndex;
    200             mToIndex = fromIndex + count;
    201             mSuggestedWords = suggestedWords;
    202             return this;
    203         }
    204 
    205         @Override
    206         public MoreSuggestions build() {
    207             final MoreSuggestionsParam params = mParams;
    208             for (int index = mFromIndex; index < mToIndex; index++) {
    209                 final int x = params.getX(index);
    210                 final int y = params.getY(index);
    211                 final int width = params.getWidth(index);
    212                 final String word;
    213                 final String info;
    214                 if (isIndexSubjectToAutoCorrection(mSuggestedWords, index)) {
    215                     // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped.
    216                     word = mSuggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD);
    217                     info = mSuggestedWords.getDebugString(SuggestedWords.INDEX_OF_TYPED_WORD);
    218                 } else {
    219                     word = mSuggestedWords.getLabel(index);
    220                     info = mSuggestedWords.getDebugString(index);
    221                 }
    222                 final Key key = new MoreSuggestionKey(word, info, index, params);
    223                 params.markAsEdgeKey(key, index);
    224                 params.onAddKey(key);
    225                 final int columnNumber = params.getColumnNumber(index);
    226                 final int numColumnInRow = params.getNumColumnInRow(index);
    227                 if (columnNumber < numColumnInRow - 1) {
    228                     final Divider divider = new Divider(params, params.mDivider, x + width, y,
    229                             params.mDividerWidth, params.mDefaultRowHeight);
    230                     params.onAddKey(divider);
    231                 }
    232             }
    233             return new MoreSuggestions(params, mSuggestedWords);
    234         }
    235     }
    236 
    237     static final class MoreSuggestionKey extends Key {
    238         public final int mSuggestedWordIndex;
    239 
    240         public MoreSuggestionKey(final String word, final String info, final int index,
    241                 final MoreSuggestionsParam params) {
    242             super(word /* label */, KeyboardIconsSet.ICON_UNDEFINED, Constants.CODE_OUTPUT_TEXT,
    243                     word /* outputText */, info, 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL,
    244                     params.getX(index), params.getY(index), params.getWidth(index),
    245                     params.mDefaultRowHeight, params.mHorizontalGap, params.mVerticalGap);
    246             mSuggestedWordIndex = index;
    247         }
    248     }
    249 
    250     private static final class Divider extends Key.Spacer {
    251         private final Drawable mIcon;
    252 
    253         public Divider(final KeyboardParams params, final Drawable icon, final int x,
    254                 final int y, final int width, final int height) {
    255             super(params, x, y, width, height);
    256             mIcon = icon;
    257         }
    258 
    259         @Override
    260         public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
    261             // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
    262             // constructor.
    263             // TODO: Drawable itself should have an alpha value.
    264             mIcon.setAlpha(128);
    265             return mIcon;
    266         }
    267     }
    268 }
    269