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.R; 30 import com.android.inputmethod.latin.SuggestedWords; 31 import com.android.inputmethod.latin.common.Constants; 32 import com.android.inputmethod.latin.utils.TypefaceUtils; 33 34 public final class MoreSuggestions extends Keyboard { 35 public final SuggestedWords mSuggestedWords; 36 MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords)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 MoreSuggestionsParam()52 public MoreSuggestionsParam() { 53 super(); 54 } 55 layout(final SuggestedWords suggestedWords, final int fromIndex, final int maxWidth, final int minWidth, final int maxRow, final Paint paint, final Resources res)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 fitInWidth(final int startIndex, final int endIndex, final int width)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 calcurateMaxRowWidth(final int startIndex, final int endIndex)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 getNumColumnInRow(final int index)133 public int getNumColumnInRow(final int index) { 134 return mNumColumnsInRow[mRowNumbers[index]]; 135 } 136 getColumnNumber(final int index)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 getX(final int index)143 public int getX(final int index) { 144 final int columnNumber = getColumnNumber(index); 145 return columnNumber * (getWidth(index) + mDividerWidth); 146 } 147 getY(final int index)148 public int getY(final int index) { 149 final int row = mRowNumbers[index]; 150 return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; 151 } 152 getWidth(final int index)153 public int getWidth(final int index) { 154 final int numColumnInRow = getNumColumnInRow(index); 155 return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; 156 } 157 markAsEdgeKey(final Key key, final int index)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 isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords, final int index)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 Builder(final Context context, final MoreSuggestionsView paneView)185 public Builder(final Context context, final MoreSuggestionsView paneView) { 186 super(context, new MoreSuggestionsParam()); 187 mPaneView = paneView; 188 } 189 layout(final SuggestedWords suggestedWords, final int fromIndex, final int maxWidth, final int minWidth, final int maxRow, final Keyboard parentKeyboard)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 build()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 MoreSuggestionKey(final String word, final String info, final int index, final MoreSuggestionsParam params)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 Divider(final KeyboardParams params, final Drawable icon, final int x, final int y, final int width, final int height)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 getIcon(final KeyboardIconsSet iconSet, final int alpha)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