MoreSuggestions.java revision 2637be27c51be03e39b0db1c66312c4cc55bc7de
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
17package com.android.inputmethod.latin.suggestions;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.Paint;
22import android.graphics.drawable.Drawable;
23
24import com.android.inputmethod.keyboard.Key;
25import com.android.inputmethod.keyboard.Keyboard;
26import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
27import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
28import com.android.inputmethod.keyboard.internal.KeyboardParams;
29import com.android.inputmethod.latin.Constants;
30import com.android.inputmethod.latin.R;
31import com.android.inputmethod.latin.SuggestedWords;
32import com.android.inputmethod.latin.utils.TypefaceUtils;
33
34public 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