OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / widget / MultiAutoCompleteTextView.java
1 /*
2  * Copyright (C) 2007 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 android.widget;
18
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Rect;
22 import android.graphics.drawable.Drawable;
23 import android.text.Editable;
24 import android.text.Selection;
25 import android.text.Spanned;
26 import android.text.Spannable;
27 import android.text.SpannableString;
28 import android.text.TextUtils;
29 import android.text.method.QwertyKeyListener;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.view.KeyEvent;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36
37 import com.android.internal.R;
38
39 /**
40  * An editable text view, extending {@link AutoCompleteTextView}, that
41  * can show completion suggestions for the substring of the text where
42  * the user is typing instead of necessarily for the entire thing.
43  * <p>
44  * You must must provide a {@link Tokenizer} to distinguish the
45  * various substrings.
46  *
47  * <p>The following code snippet shows how to create a text view which suggests
48  * various countries names while the user is typing:</p>
49  *
50  * <pre class="prettyprint">
51  * public class CountriesActivity extends Activity {
52  *     protected void onCreate(Bundle savedInstanceState) {
53  *         super.onCreate(savedInstanceState);
54  *         setContentView(R.layout.autocomplete_7);
55  * 
56  *         ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
57  *                 android.R.layout.simple_dropdown_item_1line, COUNTRIES);
58  *         MultiAutoCompleteTextView textView = (MultiAutoCompleteTextView) findViewById(R.id.edit);
59  *         textView.setAdapter(adapter);
60  *         textView.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
61  *     }
62  *
63  *     private static final String[] COUNTRIES = new String[] {
64  *         "Belgium", "France", "Italy", "Germany", "Spain"
65  *     };
66  * }</pre>
67  */
68
69 public class MultiAutoCompleteTextView extends AutoCompleteTextView {
70     private Tokenizer mTokenizer;
71
72     public MultiAutoCompleteTextView(Context context) {
73         this(context, null);
74     }
75
76     public MultiAutoCompleteTextView(Context context, AttributeSet attrs) {
77         this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
78     }
79
80     public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
81         super(context, attrs, defStyle);
82     }
83
84     /* package */ void finishInit() { }
85
86     /**
87      * Sets the Tokenizer that will be used to determine the relevant
88      * range of the text where the user is typing.
89      */
90     public void setTokenizer(Tokenizer t) {
91         mTokenizer = t;
92     }
93
94     /**
95      * Instead of filtering on the entire contents of the edit box,
96      * this subclass method filters on the range from
97      * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
98      * if the length of that range meets or exceeds {@link #getThreshold}.
99      */
100     @Override
101     protected void performFiltering(CharSequence text, int keyCode) {
102         if (enoughToFilter()) {
103             int end = getSelectionEnd();
104             int start = mTokenizer.findTokenStart(text, end);
105
106             performFiltering(text, start, end, keyCode);
107         } else {
108             dismissDropDown();
109
110             Filter f = getFilter();
111             if (f != null) {
112                 f.filter(null);
113             }
114         }
115     }
116
117     /**
118      * Instead of filtering whenever the total length of the text
119      * exceeds the threshhold, this subclass filters only when the
120      * length of the range from
121      * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
122      * meets or exceeds {@link #getThreshold}.
123      */
124     @Override
125     public boolean enoughToFilter() {
126         Editable text = getText();
127
128         int end = getSelectionEnd();
129         if (end < 0 || mTokenizer == null) {
130             return false;
131         }
132
133         int start = mTokenizer.findTokenStart(text, end);
134
135         if (end - start >= getThreshold()) {
136             return true;
137         } else {
138             return false;
139         }
140     }
141
142     /**
143      * Instead of validating the entire text, this subclass method validates
144      * each token of the text individually.  Empty tokens are removed.
145      */
146     @Override 
147     public void performValidation() {
148         Validator v = getValidator();
149
150         if (v == null || mTokenizer == null) {
151             return;
152         }
153
154         Editable e = getText();
155         int i = getText().length();
156         while (i > 0) {
157             int start = mTokenizer.findTokenStart(e, i);
158             int end = mTokenizer.findTokenEnd(e, start);
159
160             CharSequence sub = e.subSequence(start, end);
161             if (TextUtils.isEmpty(sub)) {
162                 e.replace(start, i, "");
163             } else if (!v.isValid(sub)) {
164                 e.replace(start, i,
165                           mTokenizer.terminateToken(v.fixText(sub)));
166             }
167
168             i = start;
169         }
170     }
171
172     /**
173      * <p>Starts filtering the content of the drop down list. The filtering
174      * pattern is the specified range of text from the edit box. Subclasses may
175      * override this method to filter with a different pattern, for
176      * instance a smaller substring of <code>text</code>.</p>
177      */
178     protected void performFiltering(CharSequence text, int start, int end,
179                                     int keyCode) {
180         getFilter().filter(text.subSequence(start, end), this);
181     }
182
183     /**
184      * <p>Performs the text completion by replacing the range from
185      * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} by the
186      * the result of passing <code>text</code> through
187      * {@link Tokenizer#terminateToken}.
188      * In addition, the replaced region will be marked as an AutoText
189      * substition so that if the user immediately presses DEL, the
190      * completion will be undone.
191      * Subclasses may override this method to do some different
192      * insertion of the content into the edit box.</p>
193      *
194      * @param text the selected suggestion in the drop down list
195      */
196     @Override
197     protected void replaceText(CharSequence text) {
198         clearComposingText();
199
200         int end = getSelectionEnd();
201         int start = mTokenizer.findTokenStart(getText(), end);
202
203         Editable editable = getText();
204         String original = TextUtils.substring(editable, start, end);
205
206         QwertyKeyListener.markAsReplaced(editable, start, end, original);
207         editable.replace(start, end, mTokenizer.terminateToken(text));
208     }
209
210     public static interface Tokenizer {
211         /**
212          * Returns the start of the token that ends at offset
213          * <code>cursor</code> within <code>text</code>.
214          */
215         public int findTokenStart(CharSequence text, int cursor);
216
217         /**
218          * Returns the end of the token (minus trailing punctuation)
219          * that begins at offset <code>cursor</code> within <code>text</code>.
220          */
221         public int findTokenEnd(CharSequence text, int cursor);
222
223         /**
224          * Returns <code>text</code>, modified, if necessary, to ensure that
225          * it ends with a token terminator (for example a space or comma).
226          */
227         public CharSequence terminateToken(CharSequence text);
228     }
229
230     /**
231      * This simple Tokenizer can be used for lists where the items are
232      * separated by a comma and one or more spaces.
233      */
234     public static class CommaTokenizer implements Tokenizer {
235         public int findTokenStart(CharSequence text, int cursor) {
236             int i = cursor;
237
238             while (i > 0 && text.charAt(i - 1) != ',') {
239                 i--;
240             }
241             while (i < cursor && text.charAt(i) == ' ') {
242                 i++;
243             }
244
245             return i;
246         }
247
248         public int findTokenEnd(CharSequence text, int cursor) {
249             int i = cursor;
250             int len = text.length();
251
252             while (i < len) {
253                 if (text.charAt(i) == ',') {
254                     return i;
255                 } else {
256                     i++;
257                 }
258             }
259
260             return len;
261         }
262
263         public CharSequence terminateToken(CharSequence text) {
264             int i = text.length();
265
266             while (i > 0 && text.charAt(i - 1) == ' ') {
267                 i--;
268             }
269
270             if (i > 0 && text.charAt(i - 1) == ',') {
271                 return text;
272             } else {
273                 if (text instanceof Spanned) {
274                     SpannableString sp = new SpannableString(text + ", ");
275                     TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
276                                             Object.class, sp, 0);
277                     return sp;
278                 } else {
279                     return text + ", ";
280                 }
281             }
282         }
283     }
284 }