diff options
Diffstat (limited to 'core/java/android/text/method')
24 files changed, 3797 insertions, 0 deletions
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java new file mode 100644 index 0000000..ac2e499 --- /dev/null +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; +import android.text.*; +import android.widget.TextView; +import android.view.View; +import android.view.MotionEvent; + +// XXX this doesn't extend MetaKeyKeyListener because the signatures +// don't match. Need to figure that out. Meanwhile the meta keys +// won't work in fields that don't take input. + +public class +ArrowKeyMovementMethod +implements MovementMethod +{ + private boolean up(TextView widget, Spannable buffer) { + boolean cap = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1; + boolean alt = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_ALT_ON) == 1; + Layout layout = widget.getLayout(); + + if (cap) { + if (alt) { + Selection.extendSelection(buffer, 0); + return true; + } else { + return Selection.extendUp(buffer, layout); + } + } else { + if (alt) { + Selection.setSelection(buffer, 0); + return true; + } else { + return Selection.moveUp(buffer, layout); + } + } + } + + private boolean down(TextView widget, Spannable buffer) { + boolean cap = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1; + boolean alt = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_ALT_ON) == 1; + Layout layout = widget.getLayout(); + + if (cap) { + if (alt) { + Selection.extendSelection(buffer, buffer.length()); + return true; + } else { + return Selection.extendDown(buffer, layout); + } + } else { + if (alt) { + Selection.setSelection(buffer, buffer.length()); + return true; + } else { + return Selection.moveDown(buffer, layout); + } + } + } + + private boolean left(TextView widget, Spannable buffer) { + boolean cap = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1; + boolean alt = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_ALT_ON) == 1; + Layout layout = widget.getLayout(); + + if (cap) { + if (alt) { + return Selection.extendToLeftEdge(buffer, layout); + } else { + return Selection.extendLeft(buffer, layout); + } + } else { + if (alt) { + return Selection.moveToLeftEdge(buffer, layout); + } else { + return Selection.moveLeft(buffer, layout); + } + } + } + + private boolean right(TextView widget, Spannable buffer) { + boolean cap = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1; + boolean alt = MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_ALT_ON) == 1; + Layout layout = widget.getLayout(); + + if (cap) { + if (alt) { + return Selection.extendToRightEdge(buffer, layout); + } else { + return Selection.extendRight(buffer, layout); + } + } else { + if (alt) { + return Selection.moveToRightEdge(buffer, layout); + } else { + return Selection.moveRight(buffer, layout); + } + } + } + + public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { + boolean handled = false; + + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + handled |= up(widget, buffer); + break; + + case KeyEvent.KEYCODE_DPAD_DOWN: + handled |= down(widget, buffer); + break; + + case KeyEvent.KEYCODE_DPAD_LEFT: + handled |= left(widget, buffer); + break; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + handled |= right(widget, buffer); + break; + } + + if (handled) { + MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); + MetaKeyKeyListener.resetLockedMeta(buffer); + } + + return handled; + } + + public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { + return false; + } + + public boolean onTrackballEvent(TextView widget, Spannable buffer, + MotionEvent event) { + boolean handled = false; + int x = (int) event.getX(); + int y = (int) event.getY(); + + for (; y < 0; y++) { + handled |= up(widget, buffer); + } + for (; y > 0; y--) { + handled |= down(widget, buffer); + } + + for (; x < 0; x++) { + handled |= left(widget, buffer); + } + for (; x > 0; x--) { + handled |= right(widget, buffer); + } + + if (handled) { + MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); + MetaKeyKeyListener.resetLockedMeta(buffer); + } + + return handled; + } + + public boolean onTouchEvent(TextView widget, Spannable buffer, + MotionEvent event) { + boolean handled = Touch.onTouchEvent(widget, buffer, event); + + if (widget.isFocused()) { + if (event.getAction() == MotionEvent.ACTION_UP) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0; + + if (cap) { + Selection.extendSelection(buffer, off); + } else { + Selection.setSelection(buffer, off); + } + + MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); + MetaKeyKeyListener.resetLockedMeta(buffer); + + return true; + } + } + + return handled; + } + + public boolean canSelectArbitrarily() { + return true; + } + + public void initialize(TextView widget, Spannable text) { + Selection.setSelection(text, 0); + } + + public void onTakeFocus(TextView view, Spannable text, int dir) { + if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) { + Layout layout = view.getLayout(); + + if (layout == null) { + /* + * This shouldn't be null, but do something sensible if it is. + */ + Selection.setSelection(text, text.length()); + } else { + /* + * Put the cursor at the end of the first line, which is + * either the last offset if there is only one line, or the + * offset before the first character of the second line + * if there is more than one line. + */ + if (layout.getLineCount() == 1) { + Selection.setSelection(text, text.length()); + } else { + Selection.setSelection(text, layout.getLineStart(1) - 1); + } + } + } else { + Selection.setSelection(text, text.length()); + } + } + + public static MovementMethod getInstance() { + if (sInstance == null) + sInstance = new ArrowKeyMovementMethod(); + + return sInstance; + } + + private static ArrowKeyMovementMethod sInstance; +} diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java new file mode 100644 index 0000000..3e92b7b --- /dev/null +++ b/core/java/android/text/method/BaseKeyListener.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; +import android.view.View; +import android.os.Message; +import android.util.Log; +import android.text.*; +import android.widget.TextView; + +public abstract class BaseKeyListener +extends MetaKeyKeyListener +implements KeyListener { + /* package */ static final Object OLD_SEL_START = new Object(); + + /** + * Performs the action that happens when you press the DEL key in + * a TextView. If there is a selection, deletes the selection; + * otherwise, DEL alone deletes the character before the cursor, + * if any; + * ALT+DEL deletes everything on the line the cursor is on. + * + * @return true if anything was deleted; false otherwise. + */ + public boolean backspace(View view, Editable content, int keyCode, + KeyEvent event) { + int selStart, selEnd; + boolean result = true; + + { + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + selStart = Math.min(a, b); + selEnd = Math.max(a, b); + } + + if (selStart != selEnd) { + content.delete(selStart, selEnd); + } else if (altBackspace(view, content, keyCode, event)) { + result = true; + } else { + int to = TextUtils.getOffsetBefore(content, selEnd); + + if (to != selEnd) { + content.delete(Math.min(to, selEnd), Math.max(to, selEnd)); + } + else { + result = false; + } + } + + if (result) + adjustMetaAfterKeypress(content); + + return result; + } + + private boolean altBackspace(View view, Editable content, int keyCode, + KeyEvent event) { + if (getMetaState(content, META_ALT_ON) != 1) { + return false; + } + + if (!(view instanceof TextView)) { + return false; + } + + Layout layout = ((TextView) view).getLayout(); + + if (layout == null) { + return false; + } + + int l = layout.getLineForOffset(Selection.getSelectionStart(content)); + int start = layout.getLineStart(l); + int end = layout.getLineEnd(l); + + if (end == start) { + return false; + } + + content.delete(start, end); + return true; + } + + public boolean onKeyDown(View view, Editable content, + int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + backspace(view, content, keyCode, event); + return true; + } + + return super.onKeyDown(view, content, keyCode, event); + } +} + diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java new file mode 100644 index 0000000..d787132 --- /dev/null +++ b/core/java/android/text/method/CharacterPickerDialog.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2008 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.text.method; + +import com.android.internal.R; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.text.*; +import android.view.LayoutInflater; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.GridView; +import android.widget.TextView; + +/** + * Dialog for choosing accented characters related to a base character. + */ +public class CharacterPickerDialog extends Dialog + implements OnItemClickListener, OnClickListener { + private View mView; + private Editable mText; + private String mOptions; + private boolean mInsert; + private LayoutInflater mInflater; + + /** + * Creates a new CharacterPickerDialog that presents the specified + * <code>options</code> for insertion or replacement (depending on + * the sense of <code>insert</code>) into <code>text</code>. + */ + public CharacterPickerDialog(Context context, View view, + Editable text, String options, + boolean insert) { + super(context); + + mView = view; + mText = text; + mOptions = options; + mInsert = insert; + mInflater = LayoutInflater.from(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.token = mView.getApplicationWindowToken(); + params.type = params.TYPE_APPLICATION_PANEL; + + setTitle(R.string.select_character); + setContentView(R.layout.character_picker); + + GridView grid = (GridView) findViewById(R.id.characterPicker); + grid.setAdapter(new OptionsAdapter(getContext())); + grid.setOnItemClickListener(this); + + findViewById(R.id.cancel).setOnClickListener(this); + } + + /** + * Handles clicks on the character buttons. + */ + public void onItemClick(AdapterView parent, View view, int position, long id) { + int selEnd = Selection.getSelectionEnd(mText); + String result = String.valueOf(mOptions.charAt(position)); + + if (mInsert || selEnd == 0) { + mText.insert(selEnd, result); + } else { + mText.replace(selEnd - 1, selEnd, result); + } + + dismiss(); + } + + /** + * Handles clicks on the Cancel button. + */ + public void onClick(View v) { + dismiss(); + } + + private class OptionsAdapter extends BaseAdapter { + private Context mContext; + + public OptionsAdapter(Context context) { + super(); + mContext = context; + } + + public View getView(int position, View convertView, ViewGroup parent) { + Button b = (Button) + mInflater.inflate(R.layout.character_picker_button, null); + b.setText(String.valueOf(mOptions.charAt(position))); + return b; + } + + public final int getCount() { + return mOptions.length(); + } + + public final Object getItem(int position) { + return String.valueOf(mOptions.charAt(position)); + } + + public final long getItemId(int position) { + return position; + } + } +} diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java new file mode 100644 index 0000000..0ca0332 --- /dev/null +++ b/core/java/android/text/method/DateKeyListener.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; + +/** + * For entering dates in a text field. + */ +public class DateKeyListener extends NumberKeyListener +{ + @Override + protected char[] getAcceptedChars() + { + return CHARACTERS; + } + + public static DateKeyListener getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new DateKeyListener(); + return sInstance; + } + + /** + * The characters that are used. + * + * @see KeyEvent#getMatch + * @see #getAcceptedChars + */ + public static final char[] CHARACTERS = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '/', '-', '.' + }; + + private static DateKeyListener sInstance; +} diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java new file mode 100644 index 0000000..304d326 --- /dev/null +++ b/core/java/android/text/method/DateTimeKeyListener.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; + +/** + * For entering dates and times in the same text field. + */ +public class DateTimeKeyListener extends NumberKeyListener +{ + @Override + protected char[] getAcceptedChars() + { + return CHARACTERS; + } + + public static DateTimeKeyListener getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new DateTimeKeyListener(); + return sInstance; + } + + /** + * The characters that are used. + * + * @see KeyEvent#getMatch + * @see #getAcceptedChars + */ + public static final char[] CHARACTERS = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'm', + 'p', ':', '/', '-', ' ' + }; + + private static DateTimeKeyListener sInstance; +} diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java new file mode 100644 index 0000000..e805ad7 --- /dev/null +++ b/core/java/android/text/method/DialerKeyListener.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; +import android.view.KeyCharacterMap.KeyData; +import android.util.SparseIntArray; +import android.text.Spannable; + +/** + * For dialing-only text entry + */ +public class DialerKeyListener extends NumberKeyListener +{ + @Override + protected char[] getAcceptedChars() + { + return CHARACTERS; + } + + public static DialerKeyListener getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new DialerKeyListener(); + return sInstance; + } + + /** + * Overrides the superclass's lookup method to prefer the number field + * from the KeyEvent. + */ + protected int lookup(KeyEvent event, Spannable content) { + int meta = getMetaState(content); + int number = event.getNumber(); + + /* + * Prefer number if no meta key is active, or if it produces something + * valid and the meta lookup does not. + */ + if ((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) { + if (number != 0) { + return number; + } + } + + int match = super.lookup(event, content); + + if (match != 0) { + return match; + } else { + /* + * If a meta key is active but the lookup with the meta key + * did not produce anything, try some other meta keys, because + * the user might have pressed SHIFT when they meant ALT, + * or vice versa. + */ + + if (meta != 0) { + KeyData kd = new KeyData(); + char[] accepted = getAcceptedChars(); + + if (event.getKeyData(kd)) { + for (int i = 1; i < kd.meta.length; i++) { + if (ok(accepted, kd.meta[i])) { + return kd.meta[i]; + } + } + } + } + + /* + * Otherwise, use the number associated with the key, since + * whatever they wanted to do with the meta key does not + * seem to be valid here. + */ + + return number; + } + } + + + /** + * The characters that are used. + * + * @see KeyEvent#getMatch + * @see #getAcceptedChars + */ + public static final char[] CHARACTERS = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*', + '+', '-', '(', ')', ',', '/', 'N', '.', ' ' + }; + + private static DialerKeyListener sInstance; +} diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java new file mode 100644 index 0000000..99a3f1a --- /dev/null +++ b/core/java/android/text/method/DigitsKeyListener.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.text.Spanned; +import android.text.SpannableStringBuilder; +import android.view.KeyEvent; + + +/** + * For digits-only text entry + */ +public class DigitsKeyListener extends NumberKeyListener +{ + private char[] mAccepted; + private boolean mSign; + private boolean mDecimal; + + private static final int SIGN = 1; + private static final int DECIMAL = 2; + + @Override + protected char[] getAcceptedChars() { + return mAccepted; + } + + /** + * The characters that are used. + * + * @see KeyEvent#getMatch + * @see #getAcceptedChars + */ + private static final char[][] CHARACTERS = new char[][] { + new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, + new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' }, + new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' }, + new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' }, + }; + + /** + * Allocates a DigitsKeyListener that accepts the digits 0 through 9. + */ + public DigitsKeyListener() { + this(false, false); + } + + /** + * Allocates a DigitsKeyListener that accepts the digits 0 through 9, + * plus the minus sign (only at the beginning) and/or decimal point + * (only one per field) if specified. + */ + public DigitsKeyListener(boolean sign, boolean decimal) { + mSign = sign; + mDecimal = decimal; + + int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); + mAccepted = CHARACTERS[kind]; + } + + /** + * Returns a DigitsKeyListener that accepts the digits 0 through 9. + */ + public static DigitsKeyListener getInstance() { + return getInstance(false, false); + } + + /** + * Returns a DigitsKeyListener that accepts the digits 0 through 9, + * plus the minus sign (only at the beginning) and/or decimal point + * (only one per field) if specified. + */ + public static DigitsKeyListener getInstance(boolean sign, boolean decimal) { + int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); + + if (sInstance[kind] != null) + return sInstance[kind]; + + sInstance[kind] = new DigitsKeyListener(sign, decimal); + return sInstance[kind]; + } + + /** + * Returns a DigitsKeyListener that accepts only the characters + * that appear in the specified String. Note that not all characters + * may be available on every keyboard. + */ + public static DigitsKeyListener getInstance(String accepted) { + // TODO: do we need a cache of these to avoid allocating? + + DigitsKeyListener dim = new DigitsKeyListener(); + + dim.mAccepted = new char[accepted.length()]; + accepted.getChars(0, accepted.length(), dim.mAccepted, 0); + + return dim; + } + + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + CharSequence out = super.filter(source, start, end, dest, dstart, dend); + + if (mSign == false && mDecimal == false) { + return out; + } + + if (out != null) { + source = out; + start = 0; + end = out.length(); + } + + int sign = -1; + int decimal = -1; + int dlen = dest.length(); + + /* + * Find out if the existing text has '-' or '.' characters. + */ + + for (int i = 0; i < dstart; i++) { + char c = dest.charAt(i); + + if (c == '-') { + sign = i; + } else if (c == '.') { + decimal = i; + } + } + for (int i = dend; i < dlen; i++) { + char c = dest.charAt(i); + + if (c == '-') { + return ""; // Nothing can be inserted in front of a '-'. + } else if (c == '.') { + decimal = i; + } + } + + /* + * If it does, we must strip them out from the source. + * In addition, '-' must be the very first character, + * and nothing can be inserted before an existing '-'. + * Go in reverse order so the offsets are stable. + */ + + SpannableStringBuilder stripped = null; + + for (int i = end - 1; i >= start; i--) { + char c = source.charAt(i); + boolean strip = false; + + if (c == '-') { + if (i != start || dstart != 0) { + strip = true; + } else if (sign >= 0) { + strip = true; + } else { + sign = i; + } + } else if (c == '.') { + if (decimal >= 0) { + strip = true; + } else { + decimal = i; + } + } + + if (strip) { + if (end == start + 1) { + return ""; // Only one character, and it was stripped. + } + + if (stripped == null) { + stripped = new SpannableStringBuilder(source, start, end); + } + + stripped.delete(i - start, i + 1 - start); + } + } + + if (stripped != null) { + return stripped; + } else if (out != null) { + return out; + } else { + return null; + } + } + + private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4]; +} diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java new file mode 100644 index 0000000..ce18692 --- /dev/null +++ b/core/java/android/text/method/HideReturnsTransformationMethod.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.graphics.Rect; +import android.text.GetChars; +import android.text.Spanned; +import android.text.SpannedString; +import android.text.TextUtils; +import android.view.View; + +/** + * This transformation method causes any carriage return characters (\r) + * to be hidden by displaying them as zero-width non-breaking space + * characters (\uFEFF). + */ +public class HideReturnsTransformationMethod +extends ReplacementTransformationMethod { + private static char[] ORIGINAL = new char[] { '\r' }; + private static char[] REPLACEMENT = new char[] { '\uFEFF' }; + + /** + * The character to be replaced is \r. + */ + protected char[] getOriginal() { + return ORIGINAL; + } + + /** + * The character that \r is replaced with is \uFEFF. + */ + protected char[] getReplacement() { + return REPLACEMENT; + } + + public static HideReturnsTransformationMethod getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new HideReturnsTransformationMethod(); + return sInstance; + } + + private static HideReturnsTransformationMethod sInstance; +} diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java new file mode 100644 index 0000000..05ab72d --- /dev/null +++ b/core/java/android/text/method/KeyListener.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; +import android.view.View; +import android.os.Message; +import android.text.*; +import android.widget.TextView; + +public interface KeyListener +{ + /** + * If the key listener wants to handle this key, return true, + * otherwise return false and the caller (i.e. the widget host) + * will handle the key. + */ + public boolean onKeyDown(View view, Editable text, + int keyCode, KeyEvent event); + + /** + * If the key listener wants to handle this key release, return true, + * otherwise return false and the caller (i.e. the widget host) + * will handle the key. + */ + public boolean onKeyUp(View view, Editable text, + int keyCode, KeyEvent event); +} diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java new file mode 100644 index 0000000..92ac531 --- /dev/null +++ b/core/java/android/text/method/LinkMovementMethod.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.content.Intent; +import android.net.Uri; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.text.*; +import android.text.style.*; +import android.view.View; +import android.widget.TextView; + +public class +LinkMovementMethod +extends ScrollingMovementMethod +{ + private static final int CLICK = 1; + private static final int UP = 2; + private static final int DOWN = 3; + + @Override + public boolean onKeyDown(TextView widget, Spannable buffer, + int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + if (event.getRepeatCount() == 0) { + if (action(CLICK, widget, buffer)) { + return true; + } + } + } + + return super.onKeyDown(widget, buffer, keyCode, event); + } + + @Override + protected boolean up(TextView widget, Spannable buffer) { + if (action(UP, widget, buffer)) { + return true; + } + + return super.up(widget, buffer); + } + + @Override + protected boolean down(TextView widget, Spannable buffer) { + if (action(DOWN, widget, buffer)) { + return true; + } + + return super.down(widget, buffer); + } + + @Override + protected boolean left(TextView widget, Spannable buffer) { + if (action(UP, widget, buffer)) { + return true; + } + + return super.left(widget, buffer); + } + + @Override + protected boolean right(TextView widget, Spannable buffer) { + if (action(DOWN, widget, buffer)) { + return true; + } + + return super.right(widget, buffer); + } + + private boolean action(int what, TextView widget, Spannable buffer) { + boolean handled = false; + + Layout layout = widget.getLayout(); + + int padding = widget.getTotalPaddingTop() + + widget.getTotalPaddingBottom(); + int areatop = widget.getScrollY(); + int areabot = areatop + widget.getHeight() - padding; + + int linetop = layout.getLineForVertical(areatop); + int linebot = layout.getLineForVertical(areabot); + + int first = layout.getLineStart(linetop); + int last = layout.getLineEnd(linebot); + + ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class); + + int a = Selection.getSelectionStart(buffer); + int b = Selection.getSelectionEnd(buffer); + + int selStart = Math.min(a, b); + int selEnd = Math.max(a, b); + + if (selStart < 0) { + if (buffer.getSpanStart(FROM_BELOW) >= 0) { + selStart = selEnd = buffer.length(); + } + } + + if (selStart > last) + selStart = selEnd = Integer.MAX_VALUE; + if (selEnd < first) + selStart = selEnd = -1; + + switch (what) { + case CLICK: + if (selStart == selEnd) { + return false; + } + + ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class); + + if (link.length != 1) + return false; + + link[0].onClick(widget); + break; + + case UP: + int beststart, bestend; + + beststart = -1; + bestend = -1; + + for (int i = 0; i < candidates.length; i++) { + int end = buffer.getSpanEnd(candidates[i]); + + if (end < selEnd || selStart == selEnd) { + if (end > bestend) { + beststart = buffer.getSpanStart(candidates[i]); + bestend = end; + } + } + } + + if (beststart >= 0) { + Selection.setSelection(buffer, bestend, beststart); + return true; + } + + break; + + case DOWN: + beststart = Integer.MAX_VALUE; + bestend = Integer.MAX_VALUE; + + for (int i = 0; i < candidates.length; i++) { + int start = buffer.getSpanStart(candidates[i]); + + if (start > selStart || selStart == selEnd) { + if (start < beststart) { + beststart = start; + bestend = buffer.getSpanEnd(candidates[i]); + } + } + } + + if (bestend < Integer.MAX_VALUE) { + Selection.setSelection(buffer, beststart, bestend); + return true; + } + + break; + } + + return false; + } + + public boolean onKeyUp(TextView widget, Spannable buffer, + int keyCode, KeyEvent event) { + return false; + } + + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, + MotionEvent event) { + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP || + action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + + if (link.length != 0) { + if (action == MotionEvent.ACTION_UP) { + link[0].onClick(widget); + } else if (action == MotionEvent.ACTION_DOWN) { + Selection.setSelection(buffer, + buffer.getSpanStart(link[0]), + buffer.getSpanEnd(link[0])); + } + + return true; + } else { + Selection.removeSelection(buffer); + } + } + + return super.onTouchEvent(widget, buffer, event); + } + + public void initialize(TextView widget, Spannable text) { + Selection.removeSelection(text); + text.removeSpan(FROM_BELOW); + } + + public void onTakeFocus(TextView view, Spannable text, int dir) { + Selection.removeSelection(text); + + if ((dir & View.FOCUS_BACKWARD) != 0) { + text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT); + } else { + text.removeSpan(FROM_BELOW); + } + } + + public static MovementMethod getInstance() { + if (sInstance == null) + sInstance = new LinkMovementMethod(); + + return sInstance; + } + + private static LinkMovementMethod sInstance; + private static Object FROM_BELOW = new Object(); +} diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java new file mode 100644 index 0000000..2d75b87 --- /dev/null +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; +import android.view.View; +import android.text.*; + +/** + * This base class encapsulates the behavior for handling the meta keys + * (caps, fn, sym). Key listener that care about meta state should + * inherit from it; you should not instantiate this class directly in a client. + */ + +public abstract class MetaKeyKeyListener { + public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON; + public static final int META_ALT_ON = KeyEvent.META_ALT_ON; + public static final int META_SYM_ON = KeyEvent.META_SYM_ON; + + public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << 8; + public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << 8; + public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << 8; + + private static final Object CAP = new Object(); + private static final Object ALT = new Object(); + private static final Object SYM = new Object(); + + /** + * Resets all meta state to inactive. + */ + public static void resetMetaState(Spannable text) { + text.removeSpan(CAP); + text.removeSpan(ALT); + text.removeSpan(SYM); + } + + /** + * Gets the state of the meta keys. + * + * @param text the buffer in which the meta key would have been pressed. + * + * @return an integer in which each bit set to one represents a pressed + * or locked meta key. + */ + public static final int getMetaState(CharSequence text) { + return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) | + getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) | + getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED); + } + + /** + * Gets the state of a particular meta key. + * + * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON + * @param text the buffer in which the meta key would have been pressed. + * + * @return 0 if inactive, 1 if active, 2 if locked. + */ + public static final int getMetaState(CharSequence text, int meta) { + switch (meta) { + case META_SHIFT_ON: + return getActive(text, CAP, 1, 2); + + case META_ALT_ON: + return getActive(text, ALT, 1, 2); + + case META_SYM_ON: + return getActive(text, SYM, 1, 2); + + default: + return 0; + } + } + + private static int getActive(CharSequence text, Object meta, + int on, int lock) { + if (!(text instanceof Spanned)) { + return 0; + } + + Spanned sp = (Spanned) text; + int flag = sp.getSpanFlags(meta); + + if (flag == LOCKED) { + return lock; + } else if (flag != 0) { + return on; + } else { + return 0; + } + } + + /** + * Call this method after you handle a keypress so that the meta + * state will be reset to unshifted (if it is not still down) + * or primed to be reset to unshifted (once it is released). + */ + public static void adjustMetaAfterKeypress(Spannable content) { + adjust(content, CAP); + adjust(content, ALT); + adjust(content, SYM); + } + + /** + * Returns true if this object is one that this class would use to + * keep track of meta state in the specified text. + */ + public static boolean isMetaTracker(CharSequence text, Object what) { + return what == CAP || what == ALT || what == SYM; + } + + private static void adjust(Spannable content, Object what) { + int current = content.getSpanFlags(what); + + if (current == PRESSED) + content.setSpan(what, 0, 0, USED); + else if (current == RELEASED) + content.removeSpan(what); + } + + /** + * Call this if you are a method that ignores the locked meta state + * (arrow keys, for example) and you handle a key. + */ + protected static void resetLockedMeta(Spannable content) { + resetLock(content, CAP); + resetLock(content, ALT); + resetLock(content, SYM); + } + + private static void resetLock(Spannable content, Object what) { + int current = content.getSpanFlags(what); + + if (current == LOCKED) + content.removeSpan(what); + } + + /** + * Handles presses of the meta keys. + */ + public boolean onKeyDown(View view, Editable content, + int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { + press(content, CAP); + return true; + } + + if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT + || keyCode == KeyEvent.KEYCODE_NUM) { + press(content, ALT); + return true; + } + + if (keyCode == KeyEvent.KEYCODE_SYM) { + press(content, SYM); + return true; + } + + return false; // no super to call through to + } + + private void press(Editable content, Object what) { + int state = content.getSpanFlags(what); + + if (state == PRESSED) + ; // repeat before use + else if (state == RELEASED) + content.setSpan(what, 0, 0, LOCKED); + else if (state == USED) + ; // repeat after use + else if (state == LOCKED) + content.removeSpan(what); + else + content.setSpan(what, 0, 0, PRESSED); + } + + /** + * Handles release of the meta keys. + */ + public boolean onKeyUp(View view, Editable content, int keyCode, + KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { + release(content, CAP); + return true; + } + + if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT + || keyCode == KeyEvent.KEYCODE_NUM) { + release(content, ALT); + return true; + } + + if (keyCode == KeyEvent.KEYCODE_SYM) { + release(content, SYM); + return true; + } + + return false; // no super to call through to + } + + private void release(Editable content, Object what) { + int current = content.getSpanFlags(what); + + if (current == USED) + content.removeSpan(what); + else if (current == PRESSED) + content.setSpan(what, 0, 0, RELEASED); + } + + /** + * The meta key has been pressed but has not yet been used. + */ + private static final int PRESSED = + Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT); + + /** + * The meta key has been pressed and released but has still + * not yet been used. + */ + private static final int RELEASED = + Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT); + + /** + * The meta key has been pressed and used but has not yet been released. + */ + private static final int USED = + Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT); + + /** + * The meta key has been pressed and released without use, and then + * pressed again; it may also have been released again. + */ + private static final int LOCKED = + Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT); +} + diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java new file mode 100644 index 0000000..9e37e59 --- /dev/null +++ b/core/java/android/text/method/MovementMethod.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.widget.TextView; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.text.*; + +public interface MovementMethod +{ + public void initialize(TextView widget, Spannable text); + public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event); + public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event); + public void onTakeFocus(TextView widget, Spannable text, int direction); + public boolean onTrackballEvent(TextView widget, Spannable text, + MotionEvent event); + public boolean onTouchEvent(TextView widget, Spannable text, + MotionEvent event); + + /** + * Returns true if this movement method allows arbitrary selection + * of any text; false if it has no selection (like a movement method + * that only scrolls) or a constrained selection (for example + * limited to links. The "Select All" menu item is disabled + * if arbitrary selection is not allowed. + */ + public boolean canSelectArbitrarily(); +} diff --git a/core/java/android/text/method/MultiTapKeyListener.java b/core/java/android/text/method/MultiTapKeyListener.java new file mode 100644 index 0000000..7137d40 --- /dev/null +++ b/core/java/android/text/method/MultiTapKeyListener.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; +import android.view.View; +import android.os.Message; +import android.os.Handler; +import android.os.SystemClock; +import android.text.*; +import android.text.method.TextKeyListener.Capitalize; +import android.widget.TextView; +import android.util.SparseArray; +import android.util.SparseIntArray; + +/** + * This is the standard key listener for alphabetic input on 12-key + * keyboards. You should generally not need to instantiate this yourself; + * TextKeyListener will do it for you. + */ +public class MultiTapKeyListener extends BaseKeyListener + implements SpanWatcher { + private static MultiTapKeyListener[] sInstance = + new MultiTapKeyListener[Capitalize.values().length * 2]; + + private static final SparseArray<String> sRecs = new SparseArray<String>(); + + private Capitalize mCapitalize; + private boolean mAutoText; + + static { + sRecs.put(KeyEvent.KEYCODE_1, ".,1!@#$%^&*:/?'=()"); + sRecs.put(KeyEvent.KEYCODE_2, "abc2ABC"); + sRecs.put(KeyEvent.KEYCODE_3, "def3DEF"); + sRecs.put(KeyEvent.KEYCODE_4, "ghi4GHI"); + sRecs.put(KeyEvent.KEYCODE_5, "jkl5JKL"); + sRecs.put(KeyEvent.KEYCODE_6, "mno6MNO"); + sRecs.put(KeyEvent.KEYCODE_7, "pqrs7PQRS"); + sRecs.put(KeyEvent.KEYCODE_8, "tuv8TUV"); + sRecs.put(KeyEvent.KEYCODE_9, "wxyz9WXYZ"); + sRecs.put(KeyEvent.KEYCODE_0, "0+"); + sRecs.put(KeyEvent.KEYCODE_POUND, " "); + }; + + public MultiTapKeyListener(Capitalize cap, + boolean autotext) { + mCapitalize = cap; + mAutoText = autotext; + } + + /** + * Returns a new or existing instance with the specified capitalization + * and correction properties. + */ + public static MultiTapKeyListener getInstance(boolean autotext, + Capitalize cap) { + int off = cap.ordinal() * 2 + (autotext ? 1 : 0); + + if (sInstance[off] == null) { + sInstance[off] = new MultiTapKeyListener(cap, autotext); + } + + return sInstance[off]; + } + + public boolean onKeyDown(View view, Editable content, + int keyCode, KeyEvent event) { + int selStart, selEnd; + int pref = 0; + + if (view != null) { + pref = TextKeyListener.getInstance().getPrefs(view.getContext()); + } + + { + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + selStart = Math.min(a, b); + selEnd = Math.max(a, b); + } + + int activeStart = content.getSpanStart(TextKeyListener.ACTIVE); + int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE); + + // now for the multitap cases... + + // Try to increment the character we were working on before + // if we have one and it's still the same key. + + int rec = (content.getSpanFlags(TextKeyListener.ACTIVE) + & Spannable.SPAN_USER) >>> Spannable.SPAN_USER_SHIFT; + + if (activeStart == selStart && activeEnd == selEnd && + selEnd - selStart == 1 && + rec >= 0 && rec < sRecs.size()) { + if (keyCode == KeyEvent.KEYCODE_STAR) { + char current = content.charAt(selStart); + + if (Character.isLowerCase(current)) { + content.replace(selStart, selEnd, + String.valueOf(current).toUpperCase()); + removeTimeouts(content); + Timeout t = new Timeout(content); + + return true; + } + if (Character.isUpperCase(current)) { + content.replace(selStart, selEnd, + String.valueOf(current).toLowerCase()); + removeTimeouts(content); + Timeout t = new Timeout(content); + + return true; + } + } + + if (sRecs.indexOfKey(keyCode) == rec) { + String val = sRecs.valueAt(rec); + char ch = content.charAt(selStart); + int ix = val.indexOf(ch); + + if (ix >= 0) { + ix = (ix + 1) % (val.length()); + + content.replace(selStart, selEnd, val, ix, ix + 1); + removeTimeouts(content); + Timeout t = new Timeout(content); + + return true; + } + } + + // Is this key one we know about at all? If so, acknowledge + // that the selection is our fault but the key has changed + // or the text no longer matches, so move the selection over + // so that it inserts instead of replaces. + + rec = sRecs.indexOfKey(keyCode); + + if (rec >= 0) { + Selection.setSelection(content, selEnd, selEnd); + selStart = selEnd; + } + } else { + rec = sRecs.indexOfKey(keyCode); + } + + if (rec >= 0) { + // We have a valid key. Replace the selection or insertion point + // with the first character for that key, and remember what + // record it came from for next time. + + String val = sRecs.valueAt(rec); + + int off = 0; + if ((pref & TextKeyListener.AUTO_CAP) != 0 && + TextKeyListener.shouldCap(mCapitalize, content, selStart)) { + for (int i = 0; i < val.length(); i++) { + if (Character.isUpperCase(val.charAt(i))) { + off = i; + break; + } + } + } + + if (selStart != selEnd) { + Selection.setSelection(content, selEnd); + } + + content.setSpan(OLD_SEL_START, selStart, selStart, + Spannable.SPAN_MARK_MARK); + + content.replace(selStart, selEnd, val, off, off + 1); + + int oldStart = content.getSpanStart(OLD_SEL_START); + selEnd = Selection.getSelectionEnd(content); + + if (selEnd != oldStart) { + Selection.setSelection(content, oldStart, selEnd); + + content.setSpan(TextKeyListener.LAST_TYPED, + oldStart, selEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + content.setSpan(TextKeyListener.ACTIVE, + oldStart, selEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + (rec << Spannable.SPAN_USER_SHIFT)); + + } + + removeTimeouts(content); + Timeout t = new Timeout(content); + + // Set up the callback so we can remove the timeout if the + // cursor moves. + + if (content.getSpanStart(this) < 0) { + KeyListener[] methods = content.getSpans(0, content.length(), + KeyListener.class); + for (Object method : methods) { + content.removeSpan(method); + } + content.setSpan(this, 0, content.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + + return true; + } + + return super.onKeyDown(view, content, keyCode, event); + } + + public void onSpanChanged(Spannable buf, + Object what, int s, int e, int start, int stop) { + if (what == Selection.SELECTION_END) { + buf.removeSpan(TextKeyListener.ACTIVE); + removeTimeouts(buf); + } + } + + private static void removeTimeouts(Spannable buf) { + Timeout[] timeout = buf.getSpans(0, buf.length(), Timeout.class); + + for (int i = 0; i < timeout.length; i++) { + Timeout t = timeout[i]; + + t.removeCallbacks(t); + t.mBuffer = null; + buf.removeSpan(t); + } + } + + private class Timeout + extends Handler + implements Runnable + { + public Timeout(Editable buffer) { + mBuffer = buffer; + mBuffer.setSpan(Timeout.this, 0, mBuffer.length(), + Spannable.SPAN_INCLUSIVE_INCLUSIVE); + + postAtTime(this, SystemClock.uptimeMillis() + 2000); + } + + public void run() { + Spannable buf = mBuffer; + + if (buf != null) { + int st = Selection.getSelectionStart(buf); + int en = Selection.getSelectionEnd(buf); + + int start = buf.getSpanStart(TextKeyListener.ACTIVE); + int end = buf.getSpanEnd(TextKeyListener.ACTIVE); + + if (st == start && en == end) { + Selection.setSelection(buf, Selection.getSelectionEnd(buf)); + } + + buf.removeSpan(Timeout.this); + } + } + + private Editable mBuffer; + } + + public void onSpanAdded(Spannable s, Object what, int start, int end) { } + public void onSpanRemoved(Spannable s, Object what, int start, int end) { } +} + diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java new file mode 100644 index 0000000..348b658 --- /dev/null +++ b/core/java/android/text/method/NumberKeyListener.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; +import android.view.View; +import android.text.Editable; +import android.text.InputFilter; +import android.text.Selection; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.util.SparseIntArray; + +/** + * For numeric text entry + */ +public abstract class NumberKeyListener extends BaseKeyListener + implements InputFilter +{ + /** + * You can say which characters you can accept. + */ + protected abstract char[] getAcceptedChars(); + + protected int lookup(KeyEvent event, Spannable content) { + return event.getMatch(getAcceptedChars(), getMetaState(content)); + } + + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + char[] accept = getAcceptedChars(); + boolean filter = false; + + int i; + for (i = start; i < end; i++) { + if (!ok(accept, source.charAt(i))) { + break; + } + } + + if (i == end) { + // It was all OK. + return null; + } + + if (end - start == 1) { + // It was not OK, and there is only one char, so nothing remains. + return ""; + } + + SpannableStringBuilder filtered = + new SpannableStringBuilder(source, start, end); + i -= start; + end -= start; + + int len = end - start; + // Only count down to i because the chars before that were all OK. + for (int j = end - 1; j >= i; j--) { + if (!ok(accept, source.charAt(j))) { + filtered.delete(j, j + 1); + } + } + + return filtered; + } + + protected static boolean ok(char[] accept, char c) { + for (int i = accept.length - 1; i >= 0; i--) { + if (accept[i] == c) { + return true; + } + } + + return false; + } + + @Override + public boolean onKeyDown(View view, Editable content, + int keyCode, KeyEvent event) { + int selStart, selEnd; + + { + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + selStart = Math.min(a, b); + selEnd = Math.max(a, b); + } + + int i = event != null ? lookup(event, content) : 0; + int repeatCount = event != null ? event.getRepeatCount() : 0; + if (repeatCount == 0) { + if (i != 0) { + if (selStart != selEnd) { + Selection.setSelection(content, selEnd); + } + + content.replace(selStart, selEnd, String.valueOf((char) i)); + + adjustMetaAfterKeypress(content); + return true; + } + } else if (i == '0' && repeatCount == 1) { + // Pretty hackish, it replaces the 0 with the + + + if (selStart == selEnd && selEnd > 0 && + content.charAt(selStart - 1) == '0') { + content.replace(selStart - 1, selEnd, String.valueOf('+')); + adjustMetaAfterKeypress(content); + return true; + } + } + + adjustMetaAfterKeypress(content); + return super.onKeyDown(view, content, keyCode, event); + } +} diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java new file mode 100644 index 0000000..edaa836 --- /dev/null +++ b/core/java/android/text/method/PasswordTransformationMethod.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.os.Handler; +import android.os.SystemClock; +import android.graphics.Rect; +import android.view.View; +import android.text.Editable; +import android.text.GetChars; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.Selection; +import android.text.Spanned; +import android.text.Spannable; +import android.text.style.UpdateLayout; + +import java.lang.ref.WeakReference; + +public class PasswordTransformationMethod +implements TransformationMethod, TextWatcher +{ + public CharSequence getTransformation(CharSequence source, View view) { + if (source instanceof Spannable) { + Spannable sp = (Spannable) source; + + /* + * Remove any references to other views that may still be + * attached. This will happen when you flip the screen + * while a password field is showing; there will still + * be references to the old EditText in the text. + */ + ViewReference[] vr = sp.getSpans(0, sp.length(), + ViewReference.class); + for (int i = 0; i < vr.length; i++) { + sp.removeSpan(vr[i]); + } + + sp.setSpan(new ViewReference(view), 0, 0, + Spannable.SPAN_POINT_POINT); + } + + return new PasswordCharSequence(source); + } + + public static PasswordTransformationMethod getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new PasswordTransformationMethod(); + return sInstance; + } + + public void beforeTextChanged(CharSequence s, int start, + int count, int after) { + // This callback isn't used. + } + + public void onTextChanged(CharSequence s, int start, + int before, int count) { + if (s instanceof Spannable) { + Spannable sp = (Spannable) s; + ViewReference[] vr = sp.getSpans(0, s.length(), + ViewReference.class); + if (vr.length == 0) { + return; + } + + /* + * There should generally only be one ViewReference in the text, + * but make sure to look through all of them if necessary in case + * something strange is going on. (We might still end up with + * multiple ViewReferences if someone moves text from one password + * field to another.) + */ + View v = null; + for (int i = 0; v == null && i < vr.length; i++) { + v = vr[i].get(); + } + + if (v == null) { + return; + } + + int pref = TextKeyListener.getInstance().getPrefs(v.getContext()); + if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) { + if (count > 0) { + Visible[] old = sp.getSpans(0, sp.length(), Visible.class); + for (int i = 0; i < old.length; i++) { + sp.removeSpan(old[i]); + } + + sp.setSpan(new Visible(sp, this), start, start + count, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + } + + public void afterTextChanged(Editable s) { + // This callback isn't used. + } + + public void onFocusChanged(View view, CharSequence sourceText, + boolean focused, int direction, + Rect previouslyFocusedRect) { + if (!focused) { + if (sourceText instanceof Spannable) { + Spannable sp = (Spannable) sourceText; + + Visible[] old = sp.getSpans(0, sp.length(), Visible.class); + for (int i = 0; i < old.length; i++) { + sp.removeSpan(old[i]); + } + } + } + } + + private static class PasswordCharSequence + implements CharSequence, GetChars + { + public PasswordCharSequence(CharSequence source) { + mSource = source; + } + + public int length() { + return mSource.length(); + } + + public char charAt(int i) { + if (mSource instanceof Spanned) { + Spanned sp = (Spanned) mSource; + + int st = sp.getSpanStart(TextKeyListener.ACTIVE); + int en = sp.getSpanEnd(TextKeyListener.ACTIVE); + + if (i >= st && i < en) { + return mSource.charAt(i); + } + + Visible[] visible = sp.getSpans(0, sp.length(), Visible.class); + + for (int a = 0; a < visible.length; a++) { + if (sp.getSpanStart(visible[a].mTransformer) >= 0) { + st = sp.getSpanStart(visible[a]); + en = sp.getSpanEnd(visible[a]); + + if (i >= st && i < en) { + return mSource.charAt(i); + } + } + } + } + + return DOT; + } + + public CharSequence subSequence(int start, int end) { + char[] buf = new char[end - start]; + + getChars(start, end, buf, 0); + return new String(buf); + } + + public String toString() { + return subSequence(0, length()).toString(); + } + + public void getChars(int start, int end, char[] dest, int off) { + TextUtils.getChars(mSource, start, end, dest, off); + + int st = -1, en = -1; + int nvisible = 0; + int[] starts = null, ends = null; + + if (mSource instanceof Spanned) { + Spanned sp = (Spanned) mSource; + + st = sp.getSpanStart(TextKeyListener.ACTIVE); + en = sp.getSpanEnd(TextKeyListener.ACTIVE); + + Visible[] visible = sp.getSpans(0, sp.length(), Visible.class); + nvisible = visible.length; + starts = new int[nvisible]; + ends = new int[nvisible]; + + for (int i = 0; i < nvisible; i++) { + if (sp.getSpanStart(visible[i].mTransformer) >= 0) { + starts[i] = sp.getSpanStart(visible[i]); + ends[i] = sp.getSpanEnd(visible[i]); + } + } + } + + for (int i = start; i < end; i++) { + if (! (i >= st && i < en)) { + boolean visible = false; + + for (int a = 0; a < nvisible; a++) { + if (i >= starts[a] && i < ends[a]) { + visible = true; + break; + } + } + + if (!visible) { + dest[i - start + off] = DOT; + } + } + } + } + + private CharSequence mSource; + } + + private static class Visible + extends Handler + implements UpdateLayout, Runnable + { + public Visible(Spannable sp, PasswordTransformationMethod ptm) { + mText = sp; + mTransformer = ptm; + postAtTime(this, SystemClock.uptimeMillis() + 1500); + } + + public void run() { + mText.removeSpan(this); + } + + private Spannable mText; + private PasswordTransformationMethod mTransformer; + } + + /** + * Used to stash a reference back to the View in the Editable so we + * can use it to check the settings. + */ + private static class ViewReference extends WeakReference<View> { + public ViewReference(View v) { + super(v); + } + } + + private static PasswordTransformationMethod sInstance; + private static char DOT = '\u2022'; +} diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java new file mode 100644 index 0000000..ae7ba8f --- /dev/null +++ b/core/java/android/text/method/QwertyKeyListener.java @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.os.Message; +import android.os.Handler; +import android.text.*; +import android.text.method.TextKeyListener.Capitalize; +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.widget.TextView; + +import java.util.HashMap; + +/** + * This is the standard key listener for alphabetic input on qwerty + * keyboards. You should generally not need to instantiate this yourself; + * TextKeyListener will do it for you. + */ +public class QwertyKeyListener extends BaseKeyListener { + private static QwertyKeyListener[] sInstance = + new QwertyKeyListener[Capitalize.values().length * 2]; + + public QwertyKeyListener(Capitalize cap, boolean autotext) { + mAutoCap = cap; + mAutoText = autotext; + } + + /** + * Returns a new or existing instance with the specified capitalization + * and correction properties. + */ + public static QwertyKeyListener getInstance(boolean autotext, + Capitalize cap) { + int off = cap.ordinal() * 2 + (autotext ? 1 : 0); + + if (sInstance[off] == null) { + sInstance[off] = new QwertyKeyListener(cap, autotext); + } + + return sInstance[off]; + } + + public boolean onKeyDown(View view, Editable content, + int keyCode, KeyEvent event) { + int selStart, selEnd; + int pref = 0; + + if (view != null) { + pref = TextKeyListener.getInstance().getPrefs(view.getContext()); + } + + { + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + selStart = Math.min(a, b); + selEnd = Math.max(a, b); + + if (selStart < 0 || selEnd < 0) { + selStart = selEnd = 0; + Selection.setSelection(content, 0, 0); + } + } + + int activeStart = content.getSpanStart(TextKeyListener.ACTIVE); + int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE); + + // QWERTY keyboard normal case + + int i = event.getUnicodeChar(getMetaState(content)); + + int count = event.getRepeatCount(); + if (count > 0 && selStart == selEnd && selStart > 0) { + char c = content.charAt(selStart - 1); + + if (c == i || c == Character.toUpperCase(i) && view != null) { + if (showCharacterPicker(view, content, c, false, count)) { + resetMetaState(content); + return true; + } + } + } + + if (i == KeyCharacterMap.PICKER_DIALOG_INPUT) { + if (view != null) { + showCharacterPicker(view, content, + KeyCharacterMap.PICKER_DIALOG_INPUT, true, 1); + } + resetMetaState(content); + return true; + } + + if (i == KeyCharacterMap.HEX_INPUT) { + int start; + + if (selStart == selEnd) { + start = selEnd; + + while (start > 0 && selEnd - start < 4 && + Character.digit(content.charAt(start - 1), 16) >= 0) { + start--; + } + } else { + start = selStart; + } + + int ch = -1; + try { + String hex = TextUtils.substring(content, start, selEnd); + ch = Integer.parseInt(hex, 16); + } catch (NumberFormatException nfe) { } + + if (ch >= 0) { + selStart = start; + Selection.setSelection(content, selStart, selEnd); + i = ch; + } else { + i = 0; + } + } + + if (i != 0) { + boolean dead = false; + + if ((i & KeyCharacterMap.COMBINING_ACCENT) != 0) { + dead = true; + i = i & KeyCharacterMap.COMBINING_ACCENT_MASK; + } + + if (activeStart == selStart && activeEnd == selEnd) { + boolean replace = false; + + if (selEnd - selStart - 1 == 0) { + char accent = content.charAt(selStart); + int composed = event.getDeadChar(accent, i); + + if (composed != 0) { + i = composed; + replace = true; + } + } + + if (!replace) { + Selection.setSelection(content, selEnd); + content.removeSpan(TextKeyListener.ACTIVE); + selStart = selEnd; + } + } + + if ((pref & TextKeyListener.AUTO_CAP) != 0 && + Character.isLowerCase(i) && + TextKeyListener.shouldCap(mAutoCap, content, selStart)) { + int where = content.getSpanEnd(TextKeyListener.CAPPED); + int flags = content.getSpanFlags(TextKeyListener.CAPPED); + + if (where == selStart && (((flags >> 16) & 0xFFFF) == i)) { + content.removeSpan(TextKeyListener.CAPPED); + } else { + flags = i << 16; + i = Character.toUpperCase(i); + + if (selStart == 0) + content.setSpan(TextKeyListener.CAPPED, 0, 0, + Spannable.SPAN_MARK_MARK | flags); + else + content.setSpan(TextKeyListener.CAPPED, + selStart - 1, selStart, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | + flags); + } + } + + if (selStart != selEnd) { + Selection.setSelection(content, selEnd); + } + content.setSpan(OLD_SEL_START, selStart, selStart, + Spannable.SPAN_MARK_MARK); + + content.replace(selStart, selEnd, String.valueOf((char) i)); + + int oldStart = content.getSpanStart(OLD_SEL_START); + selEnd = Selection.getSelectionEnd(content); + + if (oldStart < selEnd) { + content.setSpan(TextKeyListener.LAST_TYPED, + oldStart, selEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + if (dead) { + Selection.setSelection(content, oldStart, selEnd); + content.setSpan(TextKeyListener.ACTIVE, oldStart, selEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + adjustMetaAfterKeypress(content); + + // potentially do autotext replacement if the character + // that was typed was an autotext terminator + + if ((pref & TextKeyListener.AUTO_TEXT) != 0 && mAutoText && + (i == ' ' || i == '\t' || i == '\n' || + i == ',' || i == '.' || i == '!' || i == '?' || + i == '"' || i == ')' || i == ']') && + content.getSpanEnd(TextKeyListener.INHIBIT_REPLACEMENT) + != oldStart) { + int x; + + for (x = oldStart; x > 0; x--) { + char c = content.charAt(x - 1); + if (c != '\'' && !Character.isLetter(c)) { + break; + } + } + + String rep = getReplacement(content, x, oldStart, view); + + if (rep != null) { + Replaced[] repl = content.getSpans(0, content.length(), + Replaced.class); + for (int a = 0; a < repl.length; a++) + content.removeSpan(repl[a]); + + char[] orig = new char[oldStart - x]; + TextUtils.getChars(content, x, oldStart, orig, 0); + + content.setSpan(new Replaced(orig), x, oldStart, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + content.replace(x, oldStart, rep); + } + } + + // Replace two spaces by a period and a space. + + if ((pref & TextKeyListener.AUTO_PERIOD) != 0 && mAutoText) { + selEnd = Selection.getSelectionEnd(content); + if (selEnd - 3 >= 0) { + if (content.charAt(selEnd - 1) == ' ' && + content.charAt(selEnd - 2) == ' ') { + char c = content.charAt(selEnd - 3); + + if (Character.isLetter(c)) { + content.replace(selEnd - 2, selEnd - 1, "."); + } + } + } + } + + return true; + } else if (keyCode == KeyEvent.KEYCODE_DEL && selStart == selEnd) { + // special backspace case for undoing autotext + + int consider = 1; + + // if backspacing over the last typed character, + // it undoes the autotext prior to that character + // (unless the character typed was newline, in which + // case this behavior would be confusing) + + if (content.getSpanEnd(TextKeyListener.LAST_TYPED) == selStart) { + if (content.charAt(selStart - 1) != '\n') + consider = 2; + } + + Replaced[] repl = content.getSpans(selStart - consider, selStart, + Replaced.class); + + if (repl.length > 0) { + int st = content.getSpanStart(repl[0]); + int en = content.getSpanEnd(repl[0]); + String old = new String(repl[0].mText); + + content.removeSpan(repl[0]); + content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT, + en, en, Spannable.SPAN_POINT_POINT); + content.replace(st, en, old); + + en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT); + if (en - 1 >= 0) { + content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT, + en - 1, en, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT); + } + + adjustMetaAfterKeypress(content); + + return true; + } + } + + return super.onKeyDown(view, content, keyCode, event); + } + + private String getReplacement(CharSequence src, int start, int end, + View view) { + int len = end - start; + boolean changecase = false; + + String replacement = AutoText.get(src, start, end, view); + + if (replacement == null) { + String key = TextUtils.substring(src, start, end).toLowerCase(); + replacement = AutoText.get(key, 0, end - start, view); + changecase = true; + + if (replacement == null) + return null; + } + + int caps = 0; + + if (changecase) { + for (int j = start; j < end; j++) { + if (Character.isUpperCase(src.charAt(j))) + caps++; + } + } + + String out; + + if (caps == 0) + out = replacement; + else if (caps == 1) + out = toTitleCase(replacement); + else if (caps == len) + out = replacement.toUpperCase(); + else + out = toTitleCase(replacement); + + if (out.length() == len && + TextUtils.regionMatches(src, start, out, 0, len)) + return null; + + return out; + } + + /** + * Marks the specified region of <code>content</code> as having + * contained <code>original</code> prior to AutoText replacement. + * Call this method when you have done or are about to do an + * AutoText-style replacement on a region of text and want to let + * the same mechanism (the user pressing DEL immediately after the + * change) undo the replacement. + * + * @param content the Editable text where the replacement was made + * @param start the start of the replaced region + * @param end the end of the replaced region; the location of the cursor + * @param original the text to be restored if the user presses DEL + */ + public static void markAsReplaced(Spannable content, int start, int end, + String original) { + Replaced[] repl = content.getSpans(0, content.length(), Replaced.class); + for (int a = 0; a < repl.length; a++) { + content.removeSpan(repl[a]); + } + + int len = original.length(); + char[] orig = new char[len]; + original.getChars(0, len, orig, 0); + + content.setSpan(new Replaced(orig), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + private static SparseArray<String> PICKER_SETS = + new SparseArray<String>(); + static { + PICKER_SETS.put('!', "\u00A1"); + PICKER_SETS.put('<', "\u00AB"); + PICKER_SETS.put('>', "\u00BB"); + PICKER_SETS.put('?', "\u00BF"); + PICKER_SETS.put('A', "\u00C0\u00C1\u00C2\u00C4\u00C6\u00C3\u00C5"); + PICKER_SETS.put('C', "\u00C7"); + PICKER_SETS.put('E', "\u00C8\u00C9\u00CA\u00CB"); + PICKER_SETS.put('I', "\u00CC\u00CD\u00CE\u00CF"); + PICKER_SETS.put('N', "\u00D1"); + PICKER_SETS.put('O', "\u00D8\u0152\u00D5\u00D2\u00D3\u00D4\u00D6"); + PICKER_SETS.put('U', "\u00D9\u00DA\u00DB\u00DC"); + PICKER_SETS.put('Y', "\u00DD\u0178"); + PICKER_SETS.put('a', "\u00E0\u00E1\u00E2\u00E4\u00E6\u00E3\u00E5"); + PICKER_SETS.put('c', "\u00E7"); + PICKER_SETS.put('e', "\u00E8\u00E9\u00EA\u00EB"); + PICKER_SETS.put('i', "\u00EC\u00ED\u00EE\u00EF"); + PICKER_SETS.put('n', "\u00F1"); + PICKER_SETS.put('o', "\u00F8\u0153\u00F5\u00F2\u00F3\u00F4\u00F6"); + PICKER_SETS.put('s', "\u00A7\u00DF"); + PICKER_SETS.put('u', "\u00F9\u00FA\u00FB\u00FC"); + PICKER_SETS.put('y', "\u00FD\u00FF"); + PICKER_SETS.put(KeyCharacterMap.PICKER_DIALOG_INPUT, + "\u2026\u00A5\u2022\u00AE\u00A9\u00B1"); + }; + + private boolean showCharacterPicker(View view, Editable content, char c, + boolean insert, int count) { + String set = PICKER_SETS.get(c); + if (set == null) { + return false; + } + + if (count == 1) { + new CharacterPickerDialog(view.getContext(), + view, content, set, insert).show(); + } + + return true; + } + + private static String toTitleCase(String src) { + return Character.toUpperCase(src.charAt(0)) + src.substring(1); + } + + /* package */ static class Replaced + { + public Replaced(char[] text) { + mText = text; + } + + private char[] mText; + } + + private Capitalize mAutoCap; + private boolean mAutoText; +} + diff --git a/core/java/android/text/method/ReplacementTransformationMethod.java b/core/java/android/text/method/ReplacementTransformationMethod.java new file mode 100644 index 0000000..d6f879a --- /dev/null +++ b/core/java/android/text/method/ReplacementTransformationMethod.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.graphics.Rect; +import android.text.Editable; +import android.text.GetChars; +import android.text.Spannable; +import android.text.Spanned; +import android.text.SpannedString; +import android.text.TextUtils; +import android.view.View; + +/** + * This transformation method causes the characters in the {@link #getOriginal} + * array to be replaced by the corresponding characters in the + * {@link #getReplacement} array. + */ +public abstract class ReplacementTransformationMethod +implements TransformationMethod +{ + /** + * Returns the list of characters that are to be replaced by other + * characters when displayed. + */ + protected abstract char[] getOriginal(); + /** + * Returns a parallel array of replacement characters for the ones + * that are to be replaced. + */ + protected abstract char[] getReplacement(); + + /** + * Returns a CharSequence that will mirror the contents of the + * source CharSequence but with the characters in {@link #getOriginal} + * replaced by ones from {@link #getReplacement}. + */ + public CharSequence getTransformation(CharSequence source, View v) { + char[] original = getOriginal(); + char[] replacement = getReplacement(); + + /* + * Short circuit for faster display if the text will never change. + */ + if (!(source instanceof Editable)) { + /* + * Check whether the text does not contain any of the + * source characters so can be used unchanged. + */ + boolean doNothing = true; + int n = original.length; + for (int i = 0; i < n; i++) { + if (TextUtils.indexOf(source, original[i]) >= 0) { + doNothing = false; + break; + } + } + if (doNothing) { + return source; + } + + if (!(source instanceof Spannable)) { + /* + * The text contains some of the source characters, + * but they can be flattened out now instead of + * at display time. + */ + if (source instanceof Spanned) { + return new SpannedString(new SpannedReplacementCharSequence( + (Spanned) source, + original, replacement)); + } else { + return new ReplacementCharSequence(source, + original, + replacement).toString(); + } + } + } + + if (source instanceof Spanned) { + return new SpannedReplacementCharSequence((Spanned) source, + original, replacement); + } else { + return new ReplacementCharSequence(source, original, replacement); + } + } + + public void onFocusChanged(View view, CharSequence sourceText, + boolean focused, int direction, + Rect previouslyFocusedRect) { + // This callback isn't used. + } + + private static class ReplacementCharSequence + implements CharSequence, GetChars { + private char[] mOriginal, mReplacement; + + public ReplacementCharSequence(CharSequence source, char[] original, + char[] replacement) { + mSource = source; + mOriginal = original; + mReplacement = replacement; + } + + public int length() { + return mSource.length(); + } + + public char charAt(int i) { + char c = mSource.charAt(i); + + int n = mOriginal.length; + for (int j = 0; j < n; j++) { + if (c == mOriginal[j]) { + c = mReplacement[j]; + } + } + + return c; + } + + public CharSequence subSequence(int start, int end) { + char[] c = new char[end - start]; + + getChars(start, end, c, 0); + return new String(c); + } + + public String toString() { + char[] c = new char[length()]; + + getChars(0, length(), c, 0); + return new String(c); + } + + public void getChars(int start, int end, char[] dest, int off) { + TextUtils.getChars(mSource, start, end, dest, off); + int offend = end - start + off; + int n = mOriginal.length; + + for (int i = off; i < offend; i++) { + char c = dest[i]; + + for (int j = 0; j < n; j++) { + if (c == mOriginal[j]) { + dest[i] = mReplacement[j]; + } + } + } + } + + private CharSequence mSource; + } + + private static class SpannedReplacementCharSequence + extends ReplacementCharSequence + implements Spanned + { + public SpannedReplacementCharSequence(Spanned source, char[] original, + char[] replacement) { + super(source, original, replacement); + mSpanned = source; + } + + public CharSequence subSequence(int start, int end) { + return new SpannedString(this).subSequence(start, end); + } + + public <T> T[] getSpans(int start, int end, Class<T> type) { + return mSpanned.getSpans(start, end, type); + } + + public int getSpanStart(Object tag) { + return mSpanned.getSpanStart(tag); + } + + public int getSpanEnd(Object tag) { + return mSpanned.getSpanEnd(tag); + } + + public int getSpanFlags(Object tag) { + return mSpanned.getSpanFlags(tag); + } + + public int nextSpanTransition(int start, int end, Class type) { + return mSpanned.nextSpanTransition(start, end, type); + } + + private Spanned mSpanned; + } +} diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java new file mode 100644 index 0000000..0438e1e --- /dev/null +++ b/core/java/android/text/method/ScrollingMovementMethod.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.text.*; +import android.widget.TextView; +import android.view.View; + +public class +ScrollingMovementMethod +implements MovementMethod +{ + /** + * Scrolls the text to the left if possible. + */ + protected boolean left(TextView widget, Spannable buffer) { + Layout layout = widget.getLayout(); + + int scrolly = widget.getScrollY(); + int scr = widget.getScrollX(); + int em = Math.round(layout.getPaint().getFontSpacing()); + + int padding = widget.getTotalPaddingTop() + + widget.getTotalPaddingBottom(); + int top = layout.getLineForVertical(scrolly); + int bottom = layout.getLineForVertical(scrolly + widget.getHeight() - + padding); + int left = Integer.MAX_VALUE; + + for (int i = top; i <= bottom; i++) { + left = (int) Math.min(left, layout.getLineLeft(i)); + } + + if (scr > left) { + int s = Math.max(scr - em, left); + widget.scrollTo(s, widget.getScrollY()); + return true; + } + + return false; + } + + /** + * Scrolls the text to the right if possible. + */ + protected boolean right(TextView widget, Spannable buffer) { + Layout layout = widget.getLayout(); + + int scrolly = widget.getScrollY(); + int scr = widget.getScrollX(); + int em = Math.round(layout.getPaint().getFontSpacing()); + + int padding = widget.getTotalPaddingTop() + + widget.getTotalPaddingBottom(); + int top = layout.getLineForVertical(scrolly); + int bottom = layout.getLineForVertical(scrolly + widget.getHeight() - + padding); + int right = 0; + + for (int i = top; i <= bottom; i++) { + right = (int) Math.max(right, layout.getLineRight(i)); + } + + padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight(); + if (scr < right - (widget.getWidth() - padding)) { + int s = Math.min(scr + em, right - (widget.getWidth() - padding)); + widget.scrollTo(s, widget.getScrollY()); + return true; + } + + return false; + } + + /** + * Scrolls the text up if possible. + */ + protected boolean up(TextView widget, Spannable buffer) { + Layout layout = widget.getLayout(); + + int areatop = widget.getScrollY(); + int line = layout.getLineForVertical(areatop); + int linetop = layout.getLineTop(line); + + // If the top line is partially visible, bring it all the way + // into view; otherwise, bring the previous line into view. + if (areatop == linetop) + line--; + + if (line >= 0) { + Touch.scrollTo(widget, layout, + widget.getScrollX(), layout.getLineTop(line)); + return true; + } + + return false; + } + + /** + * Scrolls the text down if possible. + */ + protected boolean down(TextView widget, Spannable buffer) { + Layout layout = widget.getLayout(); + + int padding = widget.getTotalPaddingTop() + + widget.getTotalPaddingBottom(); + + int areabot = widget.getScrollY() + widget.getHeight() - padding; + int line = layout.getLineForVertical(areabot); + + if (layout.getLineTop(line+1) < areabot + 1) { + // Less than a pixel of this line is out of view, + // so we must have tried to make it entirely in view + // and now want the next line to be in view instead. + + line++; + } + + if (line <= layout.getLineCount() - 1) { + widget.scrollTo(widget.getScrollX(), layout.getLineTop(line+1) - + (widget.getHeight() - padding)); + Touch.scrollTo(widget, layout, + widget.getScrollX(), widget.getScrollY()); + return true; + } + + return false; + } + + public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { + boolean handled = false; + + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + handled |= left(widget, buffer); + break; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + handled |= right(widget, buffer); + break; + + case KeyEvent.KEYCODE_DPAD_UP: + handled |= up(widget, buffer); + break; + + case KeyEvent.KEYCODE_DPAD_DOWN: + handled |= down(widget, buffer); + break; + } + + return handled; + } + + public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { + return false; + } + + public boolean onTouchEvent(TextView widget, Spannable buffer, + MotionEvent event) { + return Touch.onTouchEvent(widget, buffer, event); + } + + public boolean onTrackballEvent(TextView widget, Spannable buffer, + MotionEvent event) { + boolean handled = false; + int x = (int) event.getX(); + int y = (int) event.getY(); + + for (; y < 0; y++) { + handled |= up(widget, buffer); + } + for (; y > 0; y--) { + handled |= down(widget, buffer); + } + + for (; x < 0; x++) { + handled |= left(widget, buffer); + } + for (; x > 0; x--) { + handled |= right(widget, buffer); + } + + return handled; + } + + public void initialize(TextView widget, Spannable text) { } + + public boolean canSelectArbitrarily() { + return false; + } + + public void onTakeFocus(TextView widget, Spannable text, int dir) { + Layout layout = widget.getLayout(); + + if (layout != null && (dir & View.FOCUS_FORWARD) != 0) { + widget.scrollTo(widget.getScrollX(), + layout.getLineTop(0)); + } + if (layout != null && (dir & View.FOCUS_BACKWARD) != 0) { + int padding = widget.getTotalPaddingTop() + + widget.getTotalPaddingBottom(); + int line = layout.getLineCount() - 1; + + widget.scrollTo(widget.getScrollX(), + layout.getLineTop(line+1) - + (widget.getHeight() - padding)); + } + } + + public static MovementMethod getInstance() { + if (sInstance == null) + sInstance = new ScrollingMovementMethod(); + + return sInstance; + } + + private static ScrollingMovementMethod sInstance; +} diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java new file mode 100644 index 0000000..a4fcf15 --- /dev/null +++ b/core/java/android/text/method/SingleLineTransformationMethod.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.graphics.Rect; +import android.text.Editable; +import android.text.GetChars; +import android.text.Spannable; +import android.text.Spanned; +import android.text.SpannedString; +import android.text.TextUtils; +import android.view.View; + +/** + * This transformation method causes any newline characters (\n) to be + * displayed as spaces instead of causing line breaks. + */ +public class SingleLineTransformationMethod +extends ReplacementTransformationMethod { + private static char[] ORIGINAL = new char[] { '\n' }; + private static char[] REPLACEMENT = new char[] { ' ' }; + + /** + * The character to be replaced is \n. + */ + protected char[] getOriginal() { + return ORIGINAL; + } + + /** + * The character \n is replaced with is space. + */ + protected char[] getReplacement() { + return REPLACEMENT; + } + + public static SingleLineTransformationMethod getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new SingleLineTransformationMethod(); + return sInstance; + } + + private static SingleLineTransformationMethod sInstance; +} diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java new file mode 100644 index 0000000..012e41d --- /dev/null +++ b/core/java/android/text/method/TextKeyListener.java @@ -0,0 +1,339 @@ +/* + * 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.text.method; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.provider.Settings.System; +import android.text.*; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; + +import java.lang.ref.WeakReference; + +/** + * This is the key listener for typing normal text. It delegates to + * other key listeners appropriate to the current keyboard and language. + */ +public class TextKeyListener extends BaseKeyListener implements SpanWatcher { + private static TextKeyListener[] sInstance = + new TextKeyListener[Capitalize.values().length * 2]; + + /* package */ static final Object ACTIVE = new Object(); + /* package */ static final Object CAPPED = new Object(); + /* package */ static final Object INHIBIT_REPLACEMENT = new Object(); + /* package */ static final Object LAST_TYPED = new Object(); + + private Capitalize mAutoCap; + private boolean mAutoText; + + private int mPrefs; + private boolean mPrefsInited; + + /* package */ static final int AUTO_CAP = 1; + /* package */ static final int AUTO_TEXT = 2; + /* package */ static final int AUTO_PERIOD = 4; + /* package */ static final int SHOW_PASSWORD = 8; + private WeakReference<ContentResolver> mResolver; + private TextKeyListener.SettingsObserver mObserver; + + /** + * Creates a new TextKeyListener with the specified capitalization + * and correction properties. + * + * @param cap when, if ever, to automatically capitalize. + * @param autotext whether to automatically do spelling corrections. + */ + public TextKeyListener(Capitalize cap, boolean autotext) { + mAutoCap = cap; + mAutoText = autotext; + } + + /** + * Returns a new or existing instance with the specified capitalization + * and correction properties. + * + * @param cap when, if ever, to automatically capitalize. + * @param autotext whether to automatically do spelling corrections. + */ + public static TextKeyListener getInstance(boolean autotext, + Capitalize cap) { + int off = cap.ordinal() * 2 + (autotext ? 1 : 0); + + if (sInstance[off] == null) { + sInstance[off] = new TextKeyListener(cap, autotext); + } + + return sInstance[off]; + } + + /** + * Returns a new or existing instance with no automatic capitalization + * or correction. + */ + public static TextKeyListener getInstance() { + return getInstance(false, Capitalize.NONE); + } + + /** + * Returns whether it makes sense to automatically capitalize at the + * specified position in the specified text, with the specified rules. + * + * @param cap the capitalization rules to consider. + * @param cs the text in which an insertion is being made. + * @param off the offset into that text where the insertion is being made. + * + * @return whether the character being inserted should be capitalized. + */ + public static boolean shouldCap(Capitalize cap, CharSequence cs, int off) { + int i; + char c; + + if (cap == Capitalize.NONE) { + return false; + } + if (cap == Capitalize.CHARACTERS) { + return true; + } + + // Back over allowed opening punctuation. + + for (i = off; i > 0; i--) { + c = cs.charAt(i - 1); + + if (c != '"' && c != '(' && c != '[' && c != '\'') { + break; + } + } + + // Start of paragraph, with optional whitespace. + + int j = i; + while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) { + j--; + } + if (j == 0 || cs.charAt(j - 1) == '\n') { + return true; + } + + // Or start of word if we are that style. + + if (cap == Capitalize.WORDS) { + return i != j; + } + + // There must be a space if not the start of paragraph. + + if (i == j) { + return false; + } + + // Back over allowed closing punctuation. + + for (; j > 0; j--) { + c = cs.charAt(j - 1); + + if (c != '"' && c != ')' && c != ']' && c != '\'') { + break; + } + } + + if (j > 0) { + c = cs.charAt(j - 1); + + if (c == '.' || c == '?' || c == '!') { + // Do not capitalize if the word ends with a period but + // also contains a period, in which case it is an abbreviation. + + if (c == '.') { + for (int k = j - 2; k >= 0; k--) { + c = cs.charAt(k); + + if (c == '.') { + return false; + } + + if (!Character.isLetter(c)) { + break; + } + } + } + + return true; + } + } + + return false; + } + + @Override + public boolean onKeyDown(View view, Editable content, + int keyCode, KeyEvent event) { + KeyListener im = getKeyListener(event); + + return im.onKeyDown(view, content, keyCode, event); + } + + @Override + public boolean onKeyUp(View view, Editable content, + int keyCode, KeyEvent event) { + KeyListener im = getKeyListener(event); + + return im.onKeyUp(view, content, keyCode, event); + } + + /** + * Clear all the input state (autotext, autocap, multitap, undo) + * from the specified Editable, going beyond Editable.clear(), which + * just clears the text but not the input state. + * + * @param e the buffer whose text and state are to be cleared. + */ + public static void clear(Editable e) { + e.clear(); + e.removeSpan(ACTIVE); + e.removeSpan(CAPPED); + e.removeSpan(INHIBIT_REPLACEMENT); + e.removeSpan(LAST_TYPED); + + QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(), + QwertyKeyListener.Replaced.class); + final int count = repl.length; + for (int i = 0; i < count; i++) { + e.removeSpan(repl[i]); + } + } + + public void onSpanAdded(Spannable s, Object what, int start, int end) { } + public void onSpanRemoved(Spannable s, Object what, int start, int end) { } + + public void onSpanChanged(Spannable s, Object what, int start, int end, + int st, int en) { + if (what == Selection.SELECTION_END) { + s.removeSpan(ACTIVE); + } + } + + private KeyListener getKeyListener(KeyEvent event) { + KeyCharacterMap kmap = KeyCharacterMap.load(event.getKeyboardDevice()); + int kind = kmap.getKeyboardType(); + + if (kind == KeyCharacterMap.ALPHA) { + return QwertyKeyListener.getInstance(mAutoText, mAutoCap); + } else if (kind == KeyCharacterMap.NUMERIC) { + return MultiTapKeyListener.getInstance(mAutoText, mAutoCap); + } + + return NullKeyListener.getInstance(); + } + + public enum Capitalize { + NONE, SENTENCES, WORDS, CHARACTERS, + } + + private static class NullKeyListener implements KeyListener + { + public boolean onKeyDown(View view, Editable content, + int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyUp(View view, Editable content, int keyCode, + KeyEvent event) { + return false; + } + + public static NullKeyListener getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new NullKeyListener(); + return sInstance; + } + + private static NullKeyListener sInstance; + } + + public void release() { + if (mResolver != null) { + final ContentResolver contentResolver = mResolver.get(); + if (contentResolver != null) { + contentResolver.unregisterContentObserver(mObserver); + mResolver.clear(); + } + mObserver = null; + mResolver = null; + mPrefsInited = false; + } + } + + private void initPrefs(Context context) { + final ContentResolver contentResolver = context.getContentResolver(); + mResolver = new WeakReference<ContentResolver>(contentResolver); + mObserver = new SettingsObserver(); + contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver); + + updatePrefs(contentResolver); + mPrefsInited = true; + } + + private class SettingsObserver extends ContentObserver { + public SettingsObserver() { + super(new Handler()); + } + + @Override + public void onChange(boolean selfChange) { + if (mResolver != null) { + final ContentResolver contentResolver = mResolver.get(); + if (contentResolver == null) { + mPrefsInited = false; + } else { + updatePrefs(contentResolver); + } + } else { + mPrefsInited = false; + } + } + } + + private void updatePrefs(ContentResolver resolver) { + boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0; + boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0; + boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0; + boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0; + + mPrefs = (cap ? AUTO_CAP : 0) | + (text ? AUTO_TEXT : 0) | + (period ? AUTO_PERIOD : 0) | + (pw ? SHOW_PASSWORD : 0); + } + + /* package */ int getPrefs(Context context) { + synchronized (this) { + if (!mPrefsInited || mResolver.get() == null) { + initPrefs(context); + } + } + + return mPrefs; + } +} diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java new file mode 100644 index 0000000..9ba1fe6 --- /dev/null +++ b/core/java/android/text/method/TimeKeyListener.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.view.KeyEvent; + +/** + * For entering times in a text field. + */ +public class TimeKeyListener extends NumberKeyListener +{ + @Override + protected char[] getAcceptedChars() + { + return CHARACTERS; + } + + public static TimeKeyListener getInstance() { + if (sInstance != null) + return sInstance; + + sInstance = new TimeKeyListener(); + return sInstance; + } + + /** + * The characters that are used. + * + * @see KeyEvent#getMatch + * @see #getAcceptedChars + */ + public static final char[] CHARACTERS = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'm', + 'p', ':' + }; + + private static TimeKeyListener sInstance; +} diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java new file mode 100644 index 0000000..bd01728 --- /dev/null +++ b/core/java/android/text/method/Touch.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2008 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.text.method; + +import android.text.Layout; +import android.text.Spannable; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.TextView; + +public class Touch { + private Touch() { } + + /** + * Scrolls the specified widget to the specified coordinates, except + * constrains the X scrolling position to the horizontal regions of + * the text that will be visible after scrolling to the specified + * Y position. + */ + public static void scrollTo(TextView widget, Layout layout, int x, int y) { + int padding = widget.getTotalPaddingTop() + + widget.getTotalPaddingBottom(); + int top = layout.getLineForVertical(y); + int bottom = layout.getLineForVertical(y + widget.getHeight() - + padding); + + int left = Integer.MAX_VALUE; + int right = 0; + + for (int i = top; i <= bottom; i++) { + left = (int) Math.min(left, layout.getLineLeft(i)); + right = (int) Math.max(right, layout.getLineRight(i)); + } + + padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight(); + x = Math.min(x, right - (widget.getWidth() - padding)); + x = Math.max(x, left); + + widget.scrollTo(x, y); + } + + /** + * Handles touch events for dragging. You may want to do other actions + * like moving the cursor on touch as well. + */ + public static boolean onTouchEvent(TextView widget, Spannable buffer, + MotionEvent event) { + DragState[] ds; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + buffer.setSpan(new DragState(event.getX(), event.getY()), + 0, 0, Spannable.SPAN_MARK_MARK); + return true; + + case MotionEvent.ACTION_UP: + ds = buffer.getSpans(0, buffer.length(), DragState.class); + + for (int i = 0; i < ds.length; i++) { + buffer.removeSpan(ds[i]); + } + + if (ds.length > 0 && ds[0].mUsed) { + return true; + } else { + return false; + } + + case MotionEvent.ACTION_MOVE: + ds = buffer.getSpans(0, buffer.length(), DragState.class); + + if (ds.length > 0) { + if (ds[0].mFarEnough == false) { + int slop = ViewConfiguration.getTouchSlop(); + + if (Math.abs(event.getX() - ds[0].mX) >= slop || + Math.abs(event.getY() - ds[0].mY) >= slop) { + ds[0].mFarEnough = true; + } + } + + if (ds[0].mFarEnough) { + ds[0].mUsed = true; + + float dx = ds[0].mX - event.getX(); + float dy = ds[0].mY - event.getY(); + + ds[0].mX = event.getX(); + ds[0].mY = event.getY(); + + int nx = widget.getScrollX() + (int) dx; + int ny = widget.getScrollY() + (int) dy; + + int padding = widget.getTotalPaddingTop() + + widget.getTotalPaddingBottom(); + Layout layout = widget.getLayout(); + + ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - + padding)); + ny = Math.max(ny, 0); + + scrollTo(widget, layout, nx, ny); + widget.cancelLongPress(); + return true; + } + } + } + + return false; + } + + private static class DragState { + public float mX; + public float mY; + public boolean mFarEnough; + public boolean mUsed; + + public DragState(float x, float y) { + mX = x; + mY = y; + } + } +} diff --git a/core/java/android/text/method/TransformationMethod.java b/core/java/android/text/method/TransformationMethod.java new file mode 100644 index 0000000..9f51c2a --- /dev/null +++ b/core/java/android/text/method/TransformationMethod.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2006 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.text.method; + +import android.graphics.Rect; +import android.view.View; +import android.widget.TextView; + +/** + * TextView uses TransformationMethods to do things like replacing the + * characters of passwords with dots, or keeping the newline characters + * from causing line breaks in single-line text fields. + */ +public interface TransformationMethod +{ + /** + * Returns a CharSequence that is a transformation of the source text -- + * for example, replacing each character with a dot in a password field. + * Beware that the returned text must be exactly the same length as + * the source text, and that if the source text is Editable, the returned + * text must mirror it dynamically instead of doing a one-time copy. + */ + public CharSequence getTransformation(CharSequence source, View view); + + /** + * This method is called when the TextView that uses this + * TransformationMethod gains or loses focus. + */ + public void onFocusChanged(View view, CharSequence sourceText, + boolean focused, int direction, + Rect previouslyFocusedRect); +} diff --git a/core/java/android/text/method/package.html b/core/java/android/text/method/package.html new file mode 100644 index 0000000..93698b8 --- /dev/null +++ b/core/java/android/text/method/package.html @@ -0,0 +1,21 @@ +<html> +<body> + +<p>Provides classes that monitor or modify keypad input.</p> +<p>You can use these classes to modify the type of keypad entry +for your application, or decipher the keypresses entered for your specific +entry method. For example:</p> +<pre> +// Set the text to password display style: +EditText txtView = (EditText)findViewById(R.id.text); +txtView.setTransformationMethod(PasswordTransformationMethod.getInstance()); + +//Set the input style to numbers, rather than qwerty keyboard style. +txtView.setInputMethod(DigitsInputMethod.getInstance()); + +// Find out whether the caps lock is on. +// 0 is no, 1 is yes, 2 is caps lock on. +int active = MultiTapInputMethod.getCapsActive(txtView.getText()); +</pre> +</body> +</html> |