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