From 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:31:44 -0800 Subject: auto import from //depot/cupcake/@135843 --- .../android/widget/MultiAutoCompleteTextView.java | 282 +++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 core/java/android/widget/MultiAutoCompleteTextView.java (limited to 'core/java/android/widget/MultiAutoCompleteTextView.java') diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java new file mode 100644 index 0000000..05abc26 --- /dev/null +++ b/core/java/android/widget/MultiAutoCompleteTextView.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.text.Editable; +import android.text.Selection; +import android.text.Spanned; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.method.QwertyKeyListener; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.R; + +/** + * An editable text view, extending {@link AutoCompleteTextView}, that + * can show completion suggestions for the substring of the text where + * the user is typing instead of necessarily for the entire thing. + *

+ * You must must provide a {@link Tokenizer} to distinguish the + * various substrings. + * + *

The following code snippet shows how to create a text view which suggests + * various countries names while the user is typing:

+ * + *
+ * public class CountriesActivity extends Activity {
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *         setContentView(R.layout.autocomplete_7);
+ * 
+ *         ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+ *                 android.R.layout.simple_dropdown_item_1line, COUNTRIES);
+ *         MultiAutoCompleteTextView textView = (MultiAutoCompleteTextView) findViewById(R.id.edit);
+ *         textView.setAdapter(adapter);
+ *         textView.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
+ *     }
+ *
+ *     private static final String[] COUNTRIES = new String[] {
+ *         "Belgium", "France", "Italy", "Germany", "Spain"
+ *     };
+ * }
+ */ + +public class MultiAutoCompleteTextView extends AutoCompleteTextView { + private Tokenizer mTokenizer; + + public MultiAutoCompleteTextView(Context context) { + this(context, null); + } + + public MultiAutoCompleteTextView(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); + } + + public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /* package */ void finishInit() { } + + /** + * Sets the Tokenizer that will be used to determine the relevant + * range of the text where the user is typing. + */ + public void setTokenizer(Tokenizer t) { + mTokenizer = t; + } + + /** + * Instead of filtering on the entire contents of the edit box, + * this subclass method filters on the range from + * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} + * if the length of that range meets or exceeds {@link #getThreshold}. + */ + @Override + protected void performFiltering(CharSequence text, int keyCode) { + if (enoughToFilter()) { + int end = getSelectionEnd(); + int start = mTokenizer.findTokenStart(text, end); + + performFiltering(text, start, end, keyCode); + } else { + dismissDropDown(); + + Filter f = getFilter(); + if (f != null) { + f.filter(null); + } + } + } + + /** + * Instead of filtering whenever the total length of the text + * exceeds the threshhold, this subclass filters only when the + * length of the range from + * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} + * meets or exceeds {@link #getThreshold}. + */ + @Override + public boolean enoughToFilter() { + Editable text = getText(); + + int end = getSelectionEnd(); + if (end < 0 || mTokenizer == null) { + return false; + } + + int start = mTokenizer.findTokenStart(text, end); + + if (end - start >= getThreshold()) { + return true; + } else { + return false; + } + } + + /** + * Instead of validating the entire text, this subclass method validates + * each token of the text individually. Empty tokens are removed. + */ + @Override + public void performValidation() { + Validator v = getValidator(); + + if (v == null || mTokenizer == null) { + return; + } + + Editable e = getText(); + int i = getText().length(); + while (i > 0) { + int start = mTokenizer.findTokenStart(e, i); + int end = mTokenizer.findTokenEnd(e, start); + + CharSequence sub = e.subSequence(start, end); + if (TextUtils.isEmpty(sub)) { + e.replace(start, i, ""); + } else if (!v.isValid(sub)) { + e.replace(start, i, + mTokenizer.terminateToken(v.fixText(sub))); + } + + i = start; + } + } + + /** + *

Starts filtering the content of the drop down list. The filtering + * pattern is the specified range of text from the edit box. Subclasses may + * override this method to filter with a different pattern, for + * instance a smaller substring of text.

+ */ + protected void performFiltering(CharSequence text, int start, int end, + int keyCode) { + getFilter().filter(text.subSequence(start, end), this); + } + + /** + *

Performs the text completion by replacing the range from + * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} by the + * the result of passing text through + * {@link Tokenizer#terminateToken}. + * In addition, the replaced region will be marked as an AutoText + * substition so that if the user immediately presses DEL, the + * completion will be undone. + * Subclasses may override this method to do some different + * insertion of the content into the edit box.

+ * + * @param text the selected suggestion in the drop down list + */ + @Override + protected void replaceText(CharSequence text) { + int end = getSelectionEnd(); + int start = mTokenizer.findTokenStart(getText(), end); + + Editable editable = getText(); + String original = TextUtils.substring(editable, start, end); + + QwertyKeyListener.markAsReplaced(editable, start, end, original); + editable.replace(start, end, mTokenizer.terminateToken(text)); + } + + public static interface Tokenizer { + /** + * Returns the start of the token that ends at offset + * cursor within text. + */ + public int findTokenStart(CharSequence text, int cursor); + + /** + * Returns the end of the token (minus trailing punctuation) + * that begins at offset cursor within text. + */ + public int findTokenEnd(CharSequence text, int cursor); + + /** + * Returns text, modified, if necessary, to ensure that + * it ends with a token terminator (for example a space or comma). + */ + public CharSequence terminateToken(CharSequence text); + } + + /** + * This simple Tokenizer can be used for lists where the items are + * separated by a comma and one or more spaces. + */ + public static class CommaTokenizer implements Tokenizer { + public int findTokenStart(CharSequence text, int cursor) { + int i = cursor; + + while (i > 0 && text.charAt(i - 1) != ',') { + i--; + } + while (i < cursor && text.charAt(i) == ' ') { + i++; + } + + return i; + } + + public int findTokenEnd(CharSequence text, int cursor) { + int i = cursor; + int len = text.length(); + + while (i < len) { + if (text.charAt(i) == ',') { + return i; + } else { + i++; + } + } + + return len; + } + + public CharSequence terminateToken(CharSequence text) { + int i = text.length(); + + while (i > 0 && text.charAt(i - 1) == ' ') { + i--; + } + + if (i > 0 && text.charAt(i - 1) == ',') { + return text; + } else { + if (text instanceof Spanned) { + SpannableString sp = new SpannableString(text + ", "); + TextUtils.copySpansFrom((Spanned) text, 0, text.length(), + Object.class, sp, 0); + return sp; + } else { + return text + ", "; + } + } + } + } +} -- cgit v1.1