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.latin; 18 19 import android.content.res.Resources; 20 import android.graphics.Paint; 21 import android.graphics.drawable.Drawable; 22 import android.text.TextUtils; 23 24 import com.android.inputmethod.keyboard.Key; 25 import com.android.inputmethod.keyboard.Keyboard; 26 import com.android.inputmethod.keyboard.KeyboardSwitcher; 27 import com.android.inputmethod.keyboard.KeyboardView; 28 import com.android.inputmethod.keyboard.internal.KeyboardBuilder; 29 import com.android.inputmethod.keyboard.internal.KeyboardParams; 30 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 31 32 public class MoreSuggestions extends Keyboard { 33 private static final boolean DBG = LatinImeLogger.sDBG; 34 35 public static final int SUGGESTION_CODE_BASE = 1024; 36 37 private MoreSuggestions(Builder.MoreSuggestionsParam params) { 38 super(params); 39 } 40 41 public static class Builder extends KeyboardBuilder<Builder.MoreSuggestionsParam> { 42 private final MoreSuggestionsView mPaneView; 43 private SuggestedWords mSuggestions; 44 private int mFromPos; 45 private int mToPos; 46 47 public static class MoreSuggestionsParam extends KeyboardParams { 48 private final int[] mWidths = new int[SuggestionsView.MAX_SUGGESTIONS]; 49 private final int[] mRowNumbers = new int[SuggestionsView.MAX_SUGGESTIONS]; 50 private final int[] mColumnOrders = new int[SuggestionsView.MAX_SUGGESTIONS]; 51 private final int[] mNumColumnsInRow = new int[SuggestionsView.MAX_SUGGESTIONS]; 52 private static final int MAX_COLUMNS_IN_ROW = 3; 53 private int mNumRows; 54 public Drawable mDivider; 55 public int mDividerWidth; 56 57 public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth, 58 int maxRow, KeyboardView view) { 59 clearKeys(); 60 final Paint paint = new Paint(); 61 paint.setAntiAlias(true); 62 final Resources res = view.getContext().getResources(); 63 mDivider = res.getDrawable(R.drawable.more_suggestions_divider); 64 // TODO: Drawable itself should has an alpha value. 65 mDivider.setAlpha(128); 66 mDividerWidth = mDivider.getIntrinsicWidth(); 67 final int padding = (int) res.getDimension( 68 R.dimen.more_suggestions_key_horizontal_padding); 69 70 int row = 0; 71 int pos = fromPos, rowStartPos = fromPos; 72 final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS); 73 while (pos < size) { 74 final CharSequence word = suggestions.getWord(pos); 75 // TODO: Should take care of text x-scaling. 76 mWidths[pos] = (int)view.getDefaultLabelWidth(word, paint) + padding; 77 final int numColumn = pos - rowStartPos + 1; 78 final int columnWidth = 79 (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn; 80 if (numColumn > MAX_COLUMNS_IN_ROW 81 || !fitInWidth(rowStartPos, pos + 1, columnWidth)) { 82 if ((row + 1) >= maxRow) { 83 break; 84 } 85 mNumColumnsInRow[row] = pos - rowStartPos; 86 rowStartPos = pos; 87 row++; 88 } 89 mColumnOrders[pos] = pos - rowStartPos; 90 mRowNumbers[pos] = row; 91 pos++; 92 } 93 mNumColumnsInRow[row] = pos - rowStartPos; 94 mNumRows = row + 1; 95 mBaseWidth = mOccupiedWidth = Math.max( 96 minWidth, calcurateMaxRowWidth(fromPos, pos)); 97 mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; 98 return pos - fromPos; 99 } 100 101 private boolean fitInWidth(int startPos, int endPos, int width) { 102 for (int pos = startPos; pos < endPos; pos++) { 103 if (mWidths[pos] > width) 104 return false; 105 } 106 return true; 107 } 108 109 private int calcurateMaxRowWidth(int startPos, int endPos) { 110 int maxRowWidth = 0; 111 int pos = startPos; 112 for (int row = 0; row < mNumRows; row++) { 113 final int numColumnInRow = mNumColumnsInRow[row]; 114 int maxKeyWidth = 0; 115 while (pos < endPos && mRowNumbers[pos] == row) { 116 maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]); 117 pos++; 118 } 119 maxRowWidth = Math.max(maxRowWidth, 120 maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1)); 121 } 122 return maxRowWidth; 123 } 124 125 private static final int[][] COLUMN_ORDER_TO_NUMBER = { 126 { 0, }, 127 { 1, 0, }, 128 { 2, 0, 1}, 129 }; 130 131 public int getNumColumnInRow(int pos) { 132 return mNumColumnsInRow[mRowNumbers[pos]]; 133 } 134 135 public int getColumnNumber(int pos) { 136 final int columnOrder = mColumnOrders[pos]; 137 final int numColumn = getNumColumnInRow(pos); 138 return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; 139 } 140 141 public int getX(int pos) { 142 final int columnNumber = getColumnNumber(pos); 143 return columnNumber * (getWidth(pos) + mDividerWidth); 144 } 145 146 public int getY(int pos) { 147 final int row = mRowNumbers[pos]; 148 return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; 149 } 150 151 public int getWidth(int pos) { 152 final int numColumnInRow = getNumColumnInRow(pos); 153 return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; 154 } 155 156 public void markAsEdgeKey(Key key, int pos) { 157 final int row = mRowNumbers[pos]; 158 if (row == 0) 159 key.markAsBottomEdge(this); 160 if (row == mNumRows - 1) 161 key.markAsTopEdge(this); 162 163 final int numColumnInRow = mNumColumnsInRow[row]; 164 final int column = getColumnNumber(pos); 165 if (column == 0) 166 key.markAsLeftEdge(this); 167 if (column == numColumnInRow - 1) 168 key.markAsRightEdge(this); 169 } 170 } 171 172 public Builder(MoreSuggestionsView paneView) { 173 super(paneView.getContext(), new MoreSuggestionsParam()); 174 mPaneView = paneView; 175 } 176 177 public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth, 178 int minWidth, int maxRow) { 179 final Keyboard keyboard = KeyboardSwitcher.getInstance().getLatinKeyboard(); 180 final int xmlId = R.xml.kbd_suggestions_pane_template; 181 load(keyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId)); 182 mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2; 183 184 final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow, 185 mPaneView); 186 mFromPos = fromPos; 187 mToPos = fromPos + count; 188 mSuggestions = suggestions; 189 return this; 190 } 191 192 private static String getDebugInfo(SuggestedWords suggestions, int pos) { 193 if (!DBG) return null; 194 final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); 195 if (wordInfo == null) return null; 196 final String info = wordInfo.getDebugString(); 197 if (TextUtils.isEmpty(info)) return null; 198 return info; 199 } 200 201 @Override 202 public MoreSuggestions build() { 203 final MoreSuggestionsParam params = mParams; 204 for (int pos = mFromPos; pos < mToPos; pos++) { 205 final int x = params.getX(pos); 206 final int y = params.getY(pos); 207 final int width = params.getWidth(pos); 208 final String word = mSuggestions.getWord(pos).toString(); 209 final String info = getDebugInfo(mSuggestions, pos); 210 final int index = pos + SUGGESTION_CODE_BASE; 211 final Key key = new Key( 212 params, word, info, null, index, null, x, y, width, 213 params.mDefaultRowHeight); 214 params.markAsEdgeKey(key, pos); 215 params.onAddKey(key); 216 final int columnNumber = params.getColumnNumber(pos); 217 final int numColumnInRow = params.getNumColumnInRow(pos); 218 if (columnNumber < numColumnInRow - 1) { 219 final Key.Spacer spacer = new Key.Spacer(params, params.mDivider, x + width, y, 220 params.mDividerWidth, params.mDefaultRowHeight); 221 params.onAddKey(spacer); 222 } 223 } 224 return new MoreSuggestions(params); 225 } 226 } 227 } 228