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