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.KeyboardActionListener; 27 import com.android.inputmethod.keyboard.internal.KeyboardBuilder; 28 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 29 import com.android.inputmethod.keyboard.internal.KeyboardParams; 30 import com.android.inputmethod.latin.R; 31 import com.android.inputmethod.latin.SuggestedWords; 32 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 33 import com.android.inputmethod.latin.utils.TypefaceUtils; 34 35 public final class MoreSuggestions extends Keyboard { 36 public static final int SUGGESTION_CODE_BASE = 1024; 37 38 public final SuggestedWords mSuggestedWords; 39 40 public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter { 41 public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info); 42 } 43 44 MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) { 45 super(params); 46 mSuggestedWords = suggestedWords; 47 } 48 49 private static final class MoreSuggestionsParam extends KeyboardParams { 50 private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS]; 51 private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS]; 52 private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS]; 53 private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS]; 54 private static final int MAX_COLUMNS_IN_ROW = 3; 55 private int mNumRows; 56 public Drawable mDivider; 57 public int mDividerWidth; 58 59 public MoreSuggestionsParam() { 60 super(); 61 } 62 63 public int layout(final SuggestedWords suggestedWords, final int fromIndex, 64 final int maxWidth, final int minWidth, final int maxRow, final Paint paint, 65 final Resources res) { 66 clearKeys(); 67 mDivider = res.getDrawable(R.drawable.more_suggestions_divider); 68 mDividerWidth = mDivider.getIntrinsicWidth(); 69 final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding); 70 71 int row = 0; 72 int index = fromIndex; 73 int rowStartIndex = fromIndex; 74 final int size = Math.min(suggestedWords.size(), SuggestionStripView.MAX_SUGGESTIONS); 75 while (index < size) { 76 final String word = suggestedWords.getWord(index); 77 // TODO: Should take care of text x-scaling. 78 mWidths[index] = (int)(TypefaceUtils.getLabelWidth(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, }, 129 { 1, 0, }, 130 { 2, 0, 1}, 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 public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> { 175 private final MoreSuggestionsView mPaneView; 176 private SuggestedWords mSuggestedWords; 177 private int mFromIndex; 178 private int mToIndex; 179 180 public Builder(final Context context, final MoreSuggestionsView paneView) { 181 super(context, new MoreSuggestionsParam()); 182 mPaneView = paneView; 183 } 184 185 public Builder layout(final SuggestedWords suggestedWords, final int fromIndex, 186 final int maxWidth, final int minWidth, final int maxRow, 187 final Keyboard parentKeyboard) { 188 final int xmlId = R.xml.kbd_suggestions_pane_template; 189 load(xmlId, parentKeyboard.mId); 190 mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; 191 192 mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight); 193 final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow, 194 mPaneView.newLabelPaint(null /* key */), mResources); 195 mFromIndex = fromIndex; 196 mToIndex = fromIndex + count; 197 mSuggestedWords = suggestedWords; 198 return this; 199 } 200 201 @Override 202 public MoreSuggestions build() { 203 final MoreSuggestionsParam params = mParams; 204 for (int index = mFromIndex; index < mToIndex; index++) { 205 final int x = params.getX(index); 206 final int y = params.getY(index); 207 final int width = params.getWidth(index); 208 final String word = mSuggestedWords.getWord(index); 209 final String info = mSuggestedWords.getDebugString(index); 210 final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE; 211 final Key key = new Key( 212 params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions, 213 null /* outputText */, x, y, width, params.mDefaultRowHeight, 214 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL); 215 params.markAsEdgeKey(key, index); 216 params.onAddKey(key); 217 final int columnNumber = params.getColumnNumber(index); 218 final int numColumnInRow = params.getNumColumnInRow(index); 219 if (columnNumber < numColumnInRow - 1) { 220 final Divider divider = new Divider(params, params.mDivider, x + width, y, 221 params.mDividerWidth, params.mDefaultRowHeight); 222 params.onAddKey(divider); 223 } 224 } 225 return new MoreSuggestions(params, mSuggestedWords); 226 } 227 } 228 229 private static final class Divider extends Key.Spacer { 230 private final Drawable mIcon; 231 232 public Divider(final KeyboardParams params, final Drawable icon, final int x, 233 final int y, final int width, final int height) { 234 super(params, x, y, width, height); 235 mIcon = icon; 236 } 237 238 @Override 239 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 240 // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the 241 // constructor. 242 // TODO: Drawable itself should have an alpha value. 243 mIcon.setAlpha(128); 244 return mIcon; 245 } 246 } 247 } 248