diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 |
commit | f013e1afd1e68af5e3b868c26a653bbfb39538f8 (patch) | |
tree | 7ad6c8fd9c7b55f4b4017171dec1cb760bbd26bf /core/java/android/widget/TextView.java | |
parent | e70cfafe580c6f2994c4827cd8a534aabf3eb05c (diff) | |
download | frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.zip frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.gz frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.bz2 |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'core/java/android/widget/TextView.java')
-rw-r--r-- | core/java/android/widget/TextView.java | 1497 |
1 files changed, 1250 insertions, 247 deletions
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index bd5db33..9e5f019 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; @@ -27,10 +28,12 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import android.os.Message; import android.text.BoringLayout; import android.text.DynamicLayout; import android.text.Editable; @@ -49,12 +52,16 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.TextWatcher; +import android.text.method.DateKeyListener; +import android.text.method.DateTimeKeyListener; import android.text.method.DialerKeyListener; import android.text.method.DigitsKeyListener; import android.text.method.KeyListener; import android.text.method.LinkMovementMethod; import android.text.method.MetaKeyKeyListener; import android.text.method.MovementMethod; +import android.text.method.TimeKeyListener; + import android.text.method.PasswordTransformationMethod; import android.text.method.SingleLineTransformationMethod; import android.text.method.TextKeyListener; @@ -78,12 +85,22 @@ import android.view.ViewDebug; import android.view.ViewTreeObserver; import android.view.ViewGroup.LayoutParams; import android.view.animation.AnimationUtils; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.EditorInfo; import android.widget.RemoteViews.RemoteView; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import com.android.internal.util.FastMath; +import com.android.internal.widget.EditableInputConnection; + +import org.xmlpull.v1.XmlPullParserException; /** * Displays text to the user and optionally allows them to edit it. A TextView @@ -146,6 +163,7 @@ import com.android.internal.util.FastMath; * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier + * @attr ref android.R.styleable#TextView_marqueeRepeatLimit */ @RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { @@ -182,22 +200,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int SIGNED = 2; private static final int DECIMAL = 4; - private Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight; - private int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight; - private int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight; - private boolean mDrawables; - private int mDrawablePadding; + class Drawables { + final Rect mCompoundRect = new Rect(); + Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight; + int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight; + int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight; + int mDrawablePadding; + }; + private Drawables mDrawables; private CharSequence mError; private boolean mErrorWasChanged; private PopupWindow mPopup; private CharWrapper mCharWrapper = null; - private Rect mCompoundRect; private boolean mSelectionMoved = false; - /* + private Marquee mMarquee; + private boolean mRestartMarquee; + + private int mMarqueeRepeatLimit = 3; + + class InputContentType { + String privateContentType; + Bundle extras; + } + InputContentType mInputContentType; + + class InputMethodState { + Rect mCursorRectInWindow = new Rect(); + RectF mTmpRectF = new RectF(); + float[] mTmpOffset = new float[2]; + ExtractedTextRequest mExtracting; + final ExtractedText mTmpExtracted = new ExtractedText(); + } + InputMethodState mInputMethodState; + + /* * Kick-start the font cache for the zygote process (to pay the cost of * initializing freetype for our default font only once). */ @@ -221,7 +261,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mText = ""; mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); @@ -250,7 +289,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Look the appearance up without checking first if it exists because * almost every TextView has one and it greatly simplifies the logic * to be able to parse the appearance first and then let specific tags - * for this View override it. + * for this View override it. */ TypedArray appearance = null; int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1); @@ -317,6 +356,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int shadowcolor = 0; float dx = 0, dy = 0, r = 0; boolean password = false; + int contentType = EditorInfo.TYPE_NULL; int n = a.getIndexCount(); for (int i = 0; i < n; i++) { @@ -461,6 +501,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ellipsize = a.getInt(attr, ellipsize); break; + case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: + setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); + break; + case com.android.internal.R.styleable.TextView_includeFontPadding: if (!a.getBoolean(attr, true)) { setIncludeFontPadding(false); @@ -544,12 +588,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: mSpacingMult = a.getFloat(attr, mSpacingMult); break; + + case com.android.internal.R.styleable.TextView_inputType: + contentType = a.getInt(attr, mInputType); + break; + + case com.android.internal.R.styleable.TextView_editorPrivateContentType: + setPrivateContentType(a.getString(attr)); + break; + + case com.android.internal.R.styleable.TextView_editorExtras: + try { + setInputExtras(a.getResourceId(attr, 0)); + } catch (XmlPullParserException e) { + Log.w("TextView", "Failure reading input extras", e); + } catch (IOException e) { + Log.w("TextView", "Failure reading input extras", e); + } + break; } } a.recycle(); BufferType bufferType = BufferType.EDITABLE; + if ((contentType&(EditorInfo.TYPE_MASK_CLASS + |EditorInfo.TYPE_MASK_VARIATION)) + == (EditorInfo.TYPE_CLASS_TEXT + |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { + password = true; + } + if (inputMethod != null) { Class c; @@ -566,27 +635,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } + try { + mInputType = contentType != EditorInfo.TYPE_NULL + ? contentType + : mInput.getInputType(); + } catch (IncompatibleClassChangeError e) { + mInputType = EditorInfo.TYPE_CLASS_TEXT; + } } else if (digits != null) { mInput = DigitsKeyListener.getInstance(digits.toString()); + mInputType = contentType; + } else if (contentType != EditorInfo.TYPE_NULL) { + setInputType(contentType, true); + singleLine = (contentType&(EditorInfo.TYPE_MASK_CLASS + | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) != + (EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); } else if (phone) { mInput = DialerKeyListener.getInstance(); + contentType = EditorInfo.TYPE_CLASS_PHONE; } else if (numeric != 0) { mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, (numeric & DECIMAL) != 0); + contentType = EditorInfo.TYPE_CLASS_NUMBER; + if ((numeric & SIGNED) != 0) { + contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED; + } + if ((numeric & DECIMAL) != 0) { + contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; + } + mInputType = contentType; } else if (autotext || autocap != -1) { TextKeyListener.Capitalize cap; + contentType = EditorInfo.TYPE_CLASS_TEXT; + if (!singleLine) { + contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; + } + switch (autocap) { case 1: cap = TextKeyListener.Capitalize.SENTENCES; + contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; break; case 2: cap = TextKeyListener.Capitalize.WORDS; + contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; break; case 3: cap = TextKeyListener.Capitalize.CHARACTERS; + contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; break; default: @@ -595,8 +695,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } mInput = TextKeyListener.getInstance(autotext, cap); + mInputType = contentType; } else if (editable) { mInput = TextKeyListener.getInstance(); + mInputType = EditorInfo.TYPE_CLASS_TEXT; } else { mInput = null; @@ -611,6 +713,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener bufferType = BufferType.EDITABLE; break; } + mInputType = EditorInfo.TYPE_CLASS_TEXT; + } + + if (password) { + mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) + | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; } if (selectallonfocus) { @@ -642,6 +750,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case 3: setEllipsize(TextUtils.TruncateAt.END); break; + case 4: + setHorizontalFadingEdgeEnabled(true); + setEllipsize(TextUtils.TruncateAt.MARQUEE); + break; } setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); @@ -774,11 +886,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Return the text the TextView is displaying. If setText() was called - * with an argument of BufferType.SPANNABLE or BufferType.EDITABLE, - * you can cast the return value from this method to Spannable - * or Editable, respectively. + * Return the text the TextView is displaying. If setText() was called with + * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast + * the return value from this method to Spannable or Editable, respectively. + * + * Note: The content of the return value should not be modified. If you want + * a modifiable one, you should make your own copy first. */ + @ViewDebug.CapturedViewProperty public CharSequence getText() { return mText; } @@ -791,6 +906,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Return the text the TextView is displaying as an Editable object. If + * the text is not editable, null is returned. + * + * @see #getText + */ + public Editable getEditableText() { + return (mText instanceof Editable) ? (Editable)mText : null; + } + + /** * @return the height of one standard line in pixels. Note that markup * within the text can cause individual lines to be taller or shorter * than this height, and the layout may contain additional first- @@ -819,7 +944,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Sets the key listener to be used with this TextView. This can be null - * to disallow user input. + * to disallow user input. Note that this method has significant and + * subtle interactions with soft keyboards and other input method: + * see {@link KeyListener#getInputType() KeyListener.getContentType()} + * for important details. Calling this method will replace the current + * content type of the text view with the content type returned by the + * key listener. * <p> * Be warned that if you want a TextView with a key listener or movement * method not to be focusable, or if you want a TextView without a @@ -835,13 +965,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_autoText */ public void setKeyListener(KeyListener input) { - mInput = input; + setKeyListenerOnly(input); + fixFocusableAndClickableSettings(); + + if (input != null) { + try { + mInputType = mInput.getInputType(); + } catch (IncompatibleClassChangeError e) { + mInputType = EditorInfo.TYPE_CLASS_TEXT; + } + if ((mInputType&EditorInfo.TYPE_MASK_CLASS) + == EditorInfo.TYPE_CLASS_TEXT) { + if (mSingleLine) { + mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; + } else { + mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; + } + } + } else { + mInputType = EditorInfo.TYPE_NULL; + } + + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) imm.restartInput(this); + } + private void setKeyListenerOnly(KeyListener input) { + mInput = input; if (mInput != null && !(mText instanceof Editable)) setText(mText); setFilters((Editable) mText, mFilters); - fixFocusableAndClickableSettings(); } /** @@ -873,7 +1027,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void fixFocusableAndClickableSettings() { - if (mMovement != null || mInput != null) { + if ((mMovement != null) || mInput != null) { setFocusable(true); setClickable(true); setLongClickable(true); @@ -917,10 +1071,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Drawable if any. */ public int getCompoundPaddingTop() { - if (mDrawableTop == null) { + final Drawables dr = mDrawables; + if (dr == null || dr.mDrawableTop == null) { return mPaddingTop; } else { - return mPaddingTop + mDrawablePadding + mDrawableSizeTop; + return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; } } @@ -929,10 +1084,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Drawable if any. */ public int getCompoundPaddingBottom() { - if (mDrawableBottom == null) { + final Drawables dr = mDrawables; + if (dr == null || dr.mDrawableBottom == null) { return mPaddingBottom; } else { - return mPaddingBottom + mDrawablePadding + mDrawableSizeBottom; + return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; } } @@ -941,10 +1097,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Drawable if any. */ public int getCompoundPaddingLeft() { - if (mDrawableLeft == null) { + final Drawables dr = mDrawables; + if (dr == null || dr.mDrawableLeft == null) { return mPaddingLeft; } else { - return mPaddingLeft + mDrawablePadding + mDrawableSizeLeft; + return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; } } @@ -953,10 +1110,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Drawable if any. */ public int getCompoundPaddingRight() { - if (mDrawableRight == null) { + final Drawables dr = mDrawables; + if (dr == null || dr.mDrawableRight == null) { return mPaddingRight; } else { - return mPaddingRight + mDrawablePadding + mDrawableSizeRight; + return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; } } @@ -1043,7 +1201,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns the total top padding of the view, including the top + * Returns the total top padding of the view, including the top * Drawable if any, the extra space to keep more than maxLines * from showing, and the vertical offset for gravity, if any. */ @@ -1073,62 +1231,79 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { - mDrawableLeft = left; - mDrawableTop = top; - mDrawableRight = right; - mDrawableBottom = bottom; + Drawables dr = mDrawables; - mDrawables = mDrawableLeft != null - || mDrawableRight != null - || mDrawableTop != null - || mDrawableBottom != null; + final boolean drawables = left != null || top != null + || right != null || bottom != null; - if (mCompoundRect == null && - (left != null || top != null || right != null || bottom != null)) { - mCompoundRect = new Rect(); - } + if (!drawables) { + // Clearing drawables... can we free the data structure? + if (dr != null) { + if (dr.mDrawablePadding == 0) { + mDrawables = null; + } else { + // We need to retain the last set padding, so just clear + // out all of the fields in the existing structure. + dr.mDrawableLeft = null; + dr.mDrawableTop = null; + dr.mDrawableRight = null; + dr.mDrawableBottom = null; + dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; + dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; + dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; + dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; + } + } + } else { + if (dr == null) { + mDrawables = dr = new Drawables(); + } + + dr.mDrawableLeft = left; + dr.mDrawableTop = top; + dr.mDrawableRight = right; + dr.mDrawableBottom = bottom; + + final Rect compoundRect = dr.mCompoundRect; + int[] state = null; - final Rect compoundRect = mCompoundRect; - int[] state = null; - - if (mDrawables) { state = getDrawableState(); - } - - if (mDrawableLeft != null) { - mDrawableLeft.setState(state); - mDrawableLeft.copyBounds(compoundRect); - mDrawableSizeLeft = compoundRect.width(); - mDrawableHeightLeft = compoundRect.height(); - } else { - mDrawableSizeLeft = mDrawableHeightLeft = 0; - } - if (mDrawableRight != null) { - mDrawableRight.setState(state); - mDrawableRight.copyBounds(compoundRect); - mDrawableSizeRight = compoundRect.width(); - mDrawableHeightRight = compoundRect.height(); - } else { - mDrawableSizeRight = mDrawableHeightRight = 0; - } + if (left != null) { + left.setState(state); + left.copyBounds(compoundRect); + dr.mDrawableSizeLeft = compoundRect.width(); + dr.mDrawableHeightLeft = compoundRect.height(); + } else { + dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; + } - if (mDrawableTop != null) { - mDrawableTop.setState(state); - mDrawableTop.copyBounds(compoundRect); - mDrawableSizeTop = compoundRect.height(); - mDrawableWidthTop = compoundRect.width(); - } else { - mDrawableSizeTop = mDrawableWidthTop = 0; - } + if (right != null) { + right.setState(state); + right.copyBounds(compoundRect); + dr.mDrawableSizeRight = compoundRect.width(); + dr.mDrawableHeightRight = compoundRect.height(); + } else { + dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; + } - if (mDrawableBottom != null) { - mDrawableBottom.setState(state); - mDrawableBottom.copyBounds(compoundRect); - mDrawableSizeBottom = compoundRect.height(); - mDrawableWidthBottom = compoundRect.width(); - } else { - mDrawableSizeBottom = mDrawableWidthBottom = 0; + if (top != null) { + top.setState(state); + top.copyBounds(compoundRect); + dr.mDrawableSizeTop = compoundRect.height(); + dr.mDrawableWidthTop = compoundRect.width(); + } else { + dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; + } + + if (bottom != null) { + bottom.setState(state); + bottom.copyBounds(compoundRect); + dr.mDrawableSizeBottom = compoundRect.height(); + dr.mDrawableWidthBottom = compoundRect.width(); + } else { + dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; + } } invalidate(); @@ -1137,8 +1312,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Sets the Drawables (if any) to appear to the left of, above, + * to the right of, and below the text. Use 0 if you do not + * want a Drawable there. The Drawables' bounds will be set to + * their intrinsic bounds. + * + * @param left Resource identifier of the left Drawable. + * @param top Resource identifier of the top Drawable. + * @param right Resource identifier of the right Drawable. + * @param bottom Resource identifier of the bottom Drawable. + * + * @attr ref android.R.styleable#TextView_drawableLeft + * @attr ref android.R.styleable#TextView_drawableTop + * @attr ref android.R.styleable#TextView_drawableRight + * @attr ref android.R.styleable#TextView_drawableBottom + */ + public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { + final Resources resources = getContext().getResources(); + setCompoundDrawables(left != 0 ? resources.getDrawable(left) : null, + top != 0 ? resources.getDrawable(top) : null, + right != 0 ? resources.getDrawable(right) : null, + bottom != 0 ? resources.getDrawable(bottom) : null); + } + + /** + * Sets the Drawables (if any) to appear to the left of, above, * to the right of, and below the text. Use null if you do not - * want a Drawable there. The Drawables' bounds will be set to + * want a Drawable there. The Drawables' bounds will be set to * their intrinsic bounds. * * @attr ref android.R.styleable#TextView_drawableLeft @@ -1146,24 +1345,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */ - public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, - Drawable top, - Drawable right, Drawable bottom) { + public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, + Drawable right, Drawable bottom) { + if (left != null) { - left.setBounds(0, 0, - left.getIntrinsicWidth(), left.getIntrinsicHeight()); + left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); } if (right != null) { - right.setBounds(0, 0, - right.getIntrinsicWidth(), right.getIntrinsicHeight()); + right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); } if (top != null) { - top.setBounds(0, 0, - top.getIntrinsicWidth(), top.getIntrinsicHeight()); + top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); } if (bottom != null) { - bottom.setBounds(0, 0, - bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); + bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); } setCompoundDrawables(left, top, right, bottom); } @@ -1172,9 +1367,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Returns drawables for the left, top, right, and bottom borders. */ public Drawable[] getCompoundDrawables() { - return new Drawable[] { - mDrawableLeft, mDrawableTop, mDrawableRight, mDrawableBottom - }; + final Drawables dr = mDrawables; + if (dr != null) { + return new Drawable[] { + dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom + }; + } else { + return new Drawable[] { null, null, null, null }; + } } /** @@ -1184,7 +1384,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_drawablePadding */ public void setCompoundDrawablePadding(int pad) { - mDrawablePadding = pad; + Drawables dr = mDrawables; + if (pad == 0) { + if (dr != null) { + dr.mDrawablePadding = pad; + } + } else { + if (dr == null) { + mDrawables = dr = new Drawables(); + } + dr.mDrawablePadding = pad; + } invalidate(); requestLayout(); @@ -1194,7 +1404,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Returns the padding between the compound drawables and the text. */ public int getCompoundDrawablePadding() { - return mDrawablePadding; + final Drawables dr = mDrawables; + return dr != null ? dr.mDrawablePadding : 0; } @Override @@ -1910,18 +2121,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener updateTextColors(); } - int[] state = getDrawableState(); - if (mDrawableTop != null && mDrawableTop.isStateful()) { - mDrawableTop.setState(state); - } - if (mDrawableBottom != null && mDrawableBottom.isStateful()) { - mDrawableBottom.setState(state); - } - if (mDrawableLeft != null && mDrawableLeft.isStateful()) { - mDrawableLeft.setState(state); - } - if (mDrawableRight != null && mDrawableRight.isStateful()) { - mDrawableRight.setState(state); + final Drawables dr = mDrawables; + if (dr != null) { + int[] state = getDrawableState(); + if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { + dr.mDrawableTop.setState(state); + } + if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { + dr.mDrawableBottom.setState(state); + } + if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { + dr.mDrawableLeft.setState(state); + } + if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { + dr.mDrawableRight.setState(state); + } } } @@ -1982,12 +2196,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); - + // Save state if we are forced to boolean save = mFreezesText; int start = 0; int end = 0; - + if (mText != null) { start = Selection.getSelectionStart(mText); end = Selection.getSelectionEnd(mText); @@ -1996,7 +2210,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener save = true; } } - + if (save) { SavedState ss = new SavedState(superState); // XXX Should also save the current scroll position! @@ -2030,15 +2244,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return ss; } - - return null; + + return superState; } @Override public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + SavedState ss = (SavedState)state; super.onRestoreInstanceState(ss.getSuperState()); - + // XXX restore buffer type too, as well as lots of other stuff if (ss.text != null) { setText(ss.text); @@ -2165,6 +2384,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener text = ""; } + if (text instanceof Spanned && + ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { + setHorizontalFadingEdgeEnabled(true); + setEllipsize(TextUtils.TruncateAt.MARQUEE); + } + int n = mFilters.length; for (int i = 0; i < n; i++) { CharSequence out = mFilters[i].filter(text, 0, text.length(), @@ -2183,11 +2408,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - if (type == BufferType.EDITABLE || mInput != null) { + boolean needEditableForNotification = false; + + if (mListeners != null && mListeners.size() != 0) { + needEditableForNotification = true; + } + + if (type == BufferType.EDITABLE || mInput != null || + needEditableForNotification) { Editable t = mEditableFactory.newEditable(text); text = t; - setFilters(t, mFilters); + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); } else if (!(text instanceof CharWrapper)) { @@ -2256,7 +2489,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (mMovement != null) { - mMovement.initialize(this, (Spannable) text); + mMovement.initialize(this, (Spannable) text); /* * Initializing the movement method will have set the @@ -2273,6 +2506,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(text, 0, oldlen, textLength); onTextChanged(text, 0, oldlen, textLength); + + if (needEditableForNotification) { + sendAfterTextChanged((Editable) text); + } } /** @@ -2401,8 +2638,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Sets the text to be displayed when the text of the TextView is empty. - * Null means to use the normal empty text. The hint does not - * currently participate in determining the size of the view. + * Null means to use the normal empty text. The hint does not currently + * participate in determining the size of the view. + * + * This method is deprecated. Use {link #setHint(int, String)} or + * {link #setHint(CharSequence, String)} instead. * * @attr ref android.R.styleable#TextView_hint */ @@ -2421,6 +2661,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Sets the text to be displayed when the text of the TextView is empty, * from a resource. * + * This method is deprecated. Use {link #setHint(int, String)} or + * {link #setHint(CharSequence, String)} instead. + * * @attr ref android.R.styleable#TextView_hint */ public final void setHint(int resid) { @@ -2433,11 +2676,177 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @attr ref android.R.styleable#TextView_hint */ + @ViewDebug.CapturedViewProperty public CharSequence getHint() { return mHint; } /** + * Set the type of the content with a constant as defined for + * {@link EditorInfo#inputType}. This will take care of changing + * the key listener, by calling {@link #setKeyListener(KeyListener)}, to + * match the given content type. If the given content type is + * {@link EditorInfo#TYPE_NULL} then a soft keyboard will + * not be displayed for this text view. + * + * @see #getInputType() + * @see #setRawInputType(int) + * @see android.text.InputType + * @attr ref android.R.styleable#TextView_inputType + */ + public void setInputType(int type) { + setInputType(type, false); + if ((type&(EditorInfo.TYPE_MASK_CLASS + |EditorInfo.TYPE_MASK_VARIATION)) + == (EditorInfo.TYPE_CLASS_TEXT + |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { + setTransformationMethod(PasswordTransformationMethod.getInstance()); + setTypefaceByIndex(MONOSPACE, 0); + } + boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS + | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == + (EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); + if (mSingleLine == multiLine) { + setSingleLine(!multiLine); + } + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) imm.restartInput(this); + } + + /** + * Directly change the content type integer of the text view, without + * modifying any other state. + * @see #setContentType + * @see android.text.InputType + * @attr ref android.R.styleable#TextView_inputType + */ + public void setRawInputType(int type) { + mInputType = type; + } + + private void setInputType(int type, boolean direct) { + final int cls = type & EditorInfo.TYPE_MASK_CLASS; + KeyListener input; + if (cls == EditorInfo.TYPE_CLASS_TEXT) { + boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) + != 0; + TextKeyListener.Capitalize cap; + if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { + cap = TextKeyListener.Capitalize.CHARACTERS; + } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { + cap = TextKeyListener.Capitalize.WORDS; + } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { + cap = TextKeyListener.Capitalize.SENTENCES; + } else { + cap = TextKeyListener.Capitalize.NONE; + } + input = TextKeyListener.getInstance(autotext, cap); + } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { + input = DigitsKeyListener.getInstance( + (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, + (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); + } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { + switch (type & EditorInfo.TYPE_MASK_VARIATION) { + case EditorInfo.TYPE_DATETIME_VARIATION_DATE: + input = DateKeyListener.getInstance(); + break; + case EditorInfo.TYPE_DATETIME_VARIATION_TIME: + input = TimeKeyListener.getInstance(); + break; + default: + input = DateTimeKeyListener.getInstance(); + break; + } + } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { + input = DialerKeyListener.getInstance(); + } else { + input = TextKeyListener.getInstance(); + } + mInputType = type; + if (direct) mInput = input; + else { + setKeyListenerOnly(input); + } + } + + /** + * Get the type of the content. + * + * @see #setInputType(int) + * @see android.text.InputType + */ + public int getInputType() { + return mInputType; + } + + /** + * Set the private content type of the text, which is the + * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType} + * field that will be filled in when creating an input connection. + * + * @see #getPrivateContentType() + * @see EditorInfo#privateContentType + * @attr ref android.R.styleable#TextView_editorPrivateContentType + */ + public void setPrivateContentType(String type) { + if (mInputContentType == null) mInputContentType = new InputContentType(); + mInputContentType.privateContentType = type; + } + + /** + * Get the private type of the content. + * + * @see #setPrivateContentType(String) + * @see EditorInfo#privateContentType + */ + public String getPrivateContentType() { + return mInputContentType != null + ? mInputContentType.privateContentType : null; + } + + /** + * Set the extra input data of the text, which is the + * {@link EditorInfo#extras TextBoxAttribute.extras} + * Bundle that will be filled in when creating an input connection. The + * given integer is the resource ID of an XML resource holding an + * {@link android.R.styleable#InputExtras <input-extras>} XML tree. + * + * @see #getInputExtras() + * @see EditorInfo#extras + * @attr ref android.R.styleable#TextView_editorExtras + */ + public void setInputExtras(int xmlResId) + throws XmlPullParserException, IOException { + XmlResourceParser parser = getResources().getXml(xmlResId); + if (mInputContentType == null) mInputContentType = new InputContentType(); + mInputContentType.extras = new Bundle(); + getResources().parseBundleExtras(parser, mInputContentType.extras); + } + + /** + * Retrieve the input extras currently associated with the text view, which + * can be viewed as well as modified. + * + * @param create If true, the extras will be created if they don't already + * exist. Otherwise, null will be returned if none have been created. + * @see #setInputExtras(int) + * @see EditorInfo#extras + * @attr ref android.R.styleable#TextView_editorExtras + */ + public Bundle getInputExtras(boolean create) { + if (mInputContentType == null) { + if (!create) return null; + mInputContentType = new InputContentType(); + } + if (mInputContentType.extras == null) { + if (!create) return null; + mInputContentType.extras = new Bundle(); + } + return mInputContentType.extras; + } + + /** * Returns the error message that was set to be displayed with * {@link #setError}, or <code>null</code> if no error was set * or if it the error was cleared by the widget after user input. @@ -2481,8 +2890,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mError = error; mErrorWasChanged = true; - setCompoundDrawables(mDrawableLeft, mDrawableTop, - icon, mDrawableBottom); + final Drawables dr = mDrawables; + if (dr != null) { + setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, + icon, dr.mDrawableBottom); + } else { + setCompoundDrawables(null, null, icon, null); + } if (error == null) { if (mPopup != null) { @@ -2525,9 +2939,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * The "25" is the distance between the point and the right edge * of the background */ - + + final Drawables dr = mDrawables; return getWidth() - mPopup.getWidth() - - getPaddingRight() - mDrawableSizeRight / 2 + 25; + - getPaddingRight() + - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25; } /** @@ -2542,17 +2958,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int vspace = mBottom - mTop - getCompoundPaddingBottom() - getCompoundPaddingTop(); - int icontop = getCompoundPaddingTop() + - (vspace - mDrawableHeightRight) / 2; + final Drawables dr = mDrawables; + int icontop = getCompoundPaddingTop() + + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2; /* * The "2" is the distance between the point and the top edge * of the background. */ - return icontop + mDrawableHeightRight - getHeight() - 2; + return icontop + (dr != null ? dr.mDrawableHeightRight : 0) + - getHeight() - 2; } - + private void hideError() { if (mPopup != null) { if (mPopup.isShowing()) { @@ -2599,6 +3017,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mPopup.update(this, getErrorX(), getErrorY(), -1, -1); } + if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { + mRestartMarquee = false; + startMarquee(); + } + return result; } @@ -2659,10 +3082,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int boxht; if (l == mHintLayout) { - boxht = getMeasuredHeight() - getCompoundPaddingTop() - + boxht = getMeasuredHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); } else { - boxht = getMeasuredHeight() - getExtendedPaddingTop() - + boxht = getMeasuredHeight() - getExtendedPaddingTop() - getExtendedPaddingBottom(); } int textht = l.getHeight(); @@ -2690,10 +3113,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int boxht; if (l == mHintLayout) { - boxht = getMeasuredHeight() - getCompoundPaddingTop() - + boxht = getMeasuredHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); } else { - boxht = getMeasuredHeight() - getExtendedPaddingTop() - + boxht = getMeasuredHeight() - getExtendedPaddingTop() - getExtendedPaddingBottom(); } int textht = l.getHeight(); @@ -2713,15 +3136,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener invalidateCursor(); } else { synchronized (sTempRect) { + /* + * The reason for this concern about the thickness of the + * cursor and doing the floor/ceil on the coordinates is that + * some EditTexts (notably textfields in the Browser) have + * anti-aliased text where not all the characters are + * necessarily at integer-multiple locations. This should + * make sure the entire cursor gets invalidated instead of + * sometimes missing half a pixel. + */ + + float thick = FloatMath.ceil(mTextPaint.getStrokeWidth()); + if (thick < 1.0f) { + thick = 1.0f; + } + + thick /= 2; + mHighlightPath.computeBounds(sTempRect, false); int left = getCompoundPaddingLeft(); int top = getExtendedPaddingTop() + getVerticalOffset(true); - invalidate((int) sTempRect.left + left, - (int) sTempRect.top + top, - (int) sTempRect.right + left + 1, - (int) sTempRect.bottom + top + 1); + invalidate((int) FloatMath.floor(left + sTempRect.left - thick), + (int) FloatMath.floor(top + sTempRect.top - thick), + (int) FloatMath.ceil(left + sTempRect.right + thick), + (int) FloatMath.ceil(top + sTempRect.bottom + thick)); } } } @@ -2837,6 +3277,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mPreDrawState = PREDRAW_NOT_REGISTERED; } } + + if (mError != null) { + hideError(); + } + } + + @Override + protected boolean isPaddingOffsetRequired() { + return mShadowRadius != 0; + } + + @Override + protected int getLeftPaddingOffset() { + return (int) Math.min(0, mShadowDx - mShadowRadius); + } + + @Override + protected int getTopPaddingOffset() { + return (int) Math.min(0, mShadowDy - mShadowRadius); + } + + @Override + protected int getBottomPaddingOffset() { + return (int) Math.max(0, mShadowDy + mShadowRadius); + } + + @Override + protected int getRightPaddingOffset() { + return (int) Math.max(0, mShadowDx + mShadowRadius); } @Override @@ -2855,7 +3324,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int bottom = mBottom; final int top = mTop; - if (mDrawables) { + final Drawables dr = mDrawables; + if (dr != null) { /* * Compound, not extended, because the icon is not clipped * if the text height is smaller. @@ -2864,37 +3334,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; - if (mDrawableLeft != null) { + if (dr.mDrawableLeft != null) { canvas.save(); canvas.translate(scrollX + mPaddingLeft, scrollY + compoundPaddingTop + - (vspace - mDrawableHeightLeft) / 2); - mDrawableLeft.draw(canvas); + (vspace - dr.mDrawableHeightLeft) / 2); + dr.mDrawableLeft.draw(canvas); canvas.restore(); } - if (mDrawableRight != null) { + if (dr.mDrawableRight != null) { canvas.save(); - canvas.translate(scrollX + right - left - mPaddingRight - mDrawableSizeRight, - scrollY + compoundPaddingTop + (vspace - mDrawableHeightRight) / 2); - mDrawableRight.draw(canvas); + canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight, + scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); + dr.mDrawableRight.draw(canvas); canvas.restore(); } - if (mDrawableTop != null) { + if (dr.mDrawableTop != null) { canvas.save(); - canvas.translate(scrollX + compoundPaddingLeft + (hspace - mDrawableWidthTop) / 2, + canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); - mDrawableTop.draw(canvas); + dr.mDrawableTop.draw(canvas); canvas.restore(); } - if (mDrawableBottom != null) { + if (dr.mDrawableBottom != null) { canvas.save(); canvas.translate(scrollX + compoundPaddingLeft + - (hspace - mDrawableWidthBottom) / 2, - scrollY + bottom - top - mPaddingBottom - mDrawableSizeBottom); - mDrawableBottom.draw(canvas); + (hspace - dr.mDrawableWidthBottom) / 2, + scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); + dr.mDrawableBottom.draw(canvas); canvas.restore(); } } @@ -2929,7 +3399,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener canvas.save(); /* Would be faster if we didn't have to do this. Can we chop the - (displayable) text so that we don't need to do this ever? + (displayable) text so that we don't need to do this ever? */ int extendedPaddingTop = getExtendedPaddingTop(); @@ -2963,7 +3433,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); } + if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { + if (!mSingleLine && getLineCount() == 1 && canMarquee() && + (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { + canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft - + getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f); + } + + if (mMarquee != null && mMarquee.isRunning()) { + canvas.translate(-mMarquee.mScroll, 0.0f); + } + } + Path highlight = null; + int selStart = -1, selEnd = -1; // If there is no movement method, then there can be no selection. // Check that first and attempt to skip everything having to do with @@ -2971,19 +3454,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // XXX This is not strictly true -- a program could set the // selection manually if it really wanted to. if (mMovement != null && (isFocused() || isPressed())) { - int start = Selection.getSelectionStart(mText); - int end = Selection.getSelectionEnd(mText); + selStart = Selection.getSelectionStart(mText); + selEnd = Selection.getSelectionEnd(mText); - if (mCursorVisible && start >= 0 && isEnabled()) { + if (mCursorVisible && selStart >= 0 && isEnabled()) { if (mHighlightPath == null) mHighlightPath = new Path(); - if (start == end) { + if (selStart == selEnd) { if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) { if (mHighlightPathBogus) { mHighlightPath.reset(); - mLayout.getCursorPath(start, mHighlightPath, mText); + mLayout.getCursorPath(selStart, mHighlightPath, mText); mHighlightPathBogus = false; } @@ -2996,7 +3479,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { if (mHighlightPathBogus) { mHighlightPath.reset(); - mLayout.getSelectionPath(start, end, mHighlightPath); + mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); mHighlightPathBogus = false; } @@ -3020,6 +3503,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } */ + InputMethodManager imm = InputMethodManager.peekInstance(); + if (highlight != null && mInputMethodState != null && imm != null) { + imm.updateSelection(this, selStart, selEnd); + + if (imm.isWatchingCursor(this)) { + final InputMethodState ims = mInputMethodState; + highlight.computeBounds(ims.mTmpRectF, true); + ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0; + + canvas.getMatrix().mapPoints(ims.mTmpOffset); + ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); + + ims.mTmpRectF.offset(0, voffsetCursor - voffsetText); + + ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), + (int)(ims.mTmpRectF.top + 0.5), + (int)(ims.mTmpRectF.right + 0.5), + (int)(ims.mTmpRectF.bottom + 0.5)); + + imm.updateCursor(this, + ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top, + ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom); + } + } + layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); /* Comment out until we decide what to do about animations @@ -3050,6 +3558,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener r.left = (int) mLayout.getPrimaryHorizontal(sel); r.right = r.left + 1; + + // Adjust for padding and gravity. + int paddingLeft = getCompoundPaddingLeft(); + int paddingTop = getExtendedPaddingTop(); + if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { + paddingTop += getVerticalOffset(false); + } + r.offset(paddingLeft, paddingTop); } /** @@ -3106,20 +3622,67 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (!isEnabled()) { + int which = doKeyDown(keyCode, event); + if (which == 0) { + // Go through default dispatching. return super.onKeyDown(keyCode, event); } + return true; + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); + + int which = doKeyDown(keyCode, down); + if (which == 0) { + // Go through default dispatching. + return super.onKeyMultiple(keyCode, repeatCount, event); + } + + // We are going to dispatch the remaining events to either the input + // or movement method. To do this, we will just send a repeated stream + // of down and up events until we have done the complete repeatCount. + // It would be nice if those interfaces had an onKeyMultiple() method, + // but adding that is a more complicated change. + KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); + if (which == 1) { + mInput.onKeyUp(this, (Editable)mText, keyCode, up); + while (--repeatCount > 0) { + mInput.onKeyDown(this, (Editable)mText, keyCode, down); + mInput.onKeyUp(this, (Editable)mText, keyCode, up); + } + if (mError != null && !mErrorWasChanged) { + setError(null, null); + } + + } else if (which == 2) { + mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); + while (--repeatCount > 0) { + mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); + mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); + } + } + + return true; + } + + private int doKeyDown(int keyCode, KeyEvent event) { + if (!isEnabled()) { + return 0; + } + switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: if (mSingleLine && mInput != null) { - return super.onKeyDown(keyCode, event); + return 0; } } if (mInput != null) { - /* + /* * Keep track of what the error was before doing the input * so that if an input filter changed the error, we leave * that error showing. Otherwise, we take down whatever @@ -3131,7 +3694,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mError != null && !mErrorWasChanged) { setError(null, null); } - return true; + return 1; } } @@ -3140,9 +3703,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mMovement != null && mLayout != null) if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) - return true; + return 2; - return super.onKeyDown(keyCode, event); + return 0; } @Override @@ -3199,6 +3762,108 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } + @Override public InputConnection createInputConnection(EditorInfo outAttrs) { + if (mInputType != EditorInfo.TYPE_NULL) { + if (mInputMethodState == null) { + mInputMethodState = new InputMethodState(); + } + outAttrs.inputType = mInputType; + outAttrs.hintText = mHint; + if (mInputContentType != null) { + outAttrs.privateContentType = mInputContentType.privateContentType; + outAttrs.extras = mInputContentType.extras; + } + if (mText instanceof Editable) { + InputConnection ic = new EditableInputConnection(this); + outAttrs.initialSelStart = Selection.getSelectionStart(mText); + outAttrs.initialSelEnd = Selection.getSelectionEnd(mText); + outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType); + return ic; + } + } + return null; + } + + /** + * If this TextView contains editable content, extract a portion of it + * based on the information in <var>request</var> in to <var>outText</var>. + * @return Returns true if the text is editable and was successfully + * extracted, else false. + */ + public boolean extractText(ExtractedTextRequest request, + ExtractedText outText) { + Editable content = getEditableText(); + if (content != null) { + outText.text = content.subSequence(0, content.length()); + outText.startOffset = 0; + outText.selectionStart = Selection.getSelectionStart(content); + outText.selectionEnd = Selection.getSelectionEnd(content); + return true; + } + return false; + } + + void reportExtractedText() { + if (mInputMethodState != null) { + final ExtractedTextRequest req = mInputMethodState.mExtracting; + if (req != null) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + if (extractText(req, mInputMethodState.mTmpExtracted)) { + imm.updateExtractedText(this, req.token, + mInputMethodState.mTmpExtracted); + } + } + } + } + } + + /** + * Apply to this text view the given extracted text, as previously + * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. + */ + public void setExtractedText(ExtractedText text) { + setText(text.text, TextView.BufferType.EDITABLE); + Selection.setSelection((Spannable)getText(), + text.selectionStart, text.selectionEnd); + } + + /** + * @hide + */ + public void setExtracting(ExtractedTextRequest req) { + if (mInputMethodState != null) { + mInputMethodState.mExtracting = req; + } + } + + /** + * Called by the framework in response to a text completion from + * the current input method, provided by it calling + * {@link InputConnection#commitCompletion + * InputConnection.commitCompletion()}. The default implementation does + * nothing; text views that are supporting auto-completion should override + * this to do their desired behavior. + * + * @param text The auto complete text the user has selected. + */ + public void onCommitCompletion(CompletionInfo text) { + } + + /** + * Called by the framework in response to a private command from the + * current method, provided by it calling + * {@link InputConnection#performPrivateCommand + * InputConnection.performPrivateCommand()}. + * + * @param action The action name of the command. + * @param data Any additional data for the command. This may be null. + * @return Return true if you handled the command, else false. + */ + public boolean onPrivateIMECommand(String action, Bundle data) { + return false; + } + private void nullLayouts() { if (mLayout instanceof BoringLayout && mSavedLayout == null) { mSavedLayout = (BoringLayout) mLayout; @@ -3240,6 +3905,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView) { + stopMarquee(); + mHighlightPathBogus = true; if (w < 0) { @@ -3371,6 +4038,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (bringIntoView) { registerForPreDraw(); } + + if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { + final int height = mLayoutParams.height; + // If the size of the view does not depend on the size of the text, try to + // start the marquee immediately + if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) { + startMarquee(); + } else { + // Defer the start of the marquee until we know our width (see setFrame()) + mRestartMarquee = true; + } + } } private static int desired(Layout layout) { @@ -3458,8 +4137,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener width = boring.width; } - width = Math.max(width, mDrawableWidthTop); - width = Math.max(width, mDrawableWidthBottom); + final Drawables dr = mDrawables; + if (dr != null) { + width = Math.max(width, dr.mDrawableWidthTop); + width = Math.max(width, dr.mDrawableWidthBottom); + } if (mHint != null) { int hintDes = -1; @@ -3509,7 +4191,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Check against our minimum width width = Math.max(width, getSuggestedMinimumWidth()); - + if (widthMode == MeasureSpec.AT_MOST) { width = Math.min(widthSize, width); } @@ -3596,8 +4278,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); int desired = layout.getLineTop(linecount); - desired = Math.max(desired, mDrawableHeightLeft); - desired = Math.max(desired, mDrawableHeightRight); + final Drawables dr = mDrawables; + if (dr != null) { + desired = Math.max(desired, dr.mDrawableHeightLeft); + desired = Math.max(desired, dr.mDrawableHeightRight); + } desired += pad; @@ -3611,8 +4296,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener desired = layout.getLineTop(mMaximum) + layout.getBottomPadding(); - desired = Math.max(desired, mDrawableHeightLeft); - desired = Math.max(desired, mDrawableHeightRight); + if (dr != null) { + desired = Math.max(desired, dr.mDrawableHeightLeft); + desired = Math.max(desired, dr.mDrawableHeightRight); + } desired += pad; linecount = mMaximum; @@ -3632,7 +4319,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Check against our minimum height desired = Math.max(desired, getSuggestedMinimumHeight()); - + return desired; } @@ -3978,8 +4665,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void getInterestingRect(Rect r, int h, int top, int bottom, int line) { - top += getExtendedPaddingTop(); - bottom += getExtendedPaddingTop(); + int paddingTop = getExtendedPaddingTop(); + if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { + paddingTop += getVerticalOffset(false); + } + top += paddingTop; + bottom += paddingTop; h += getCompoundPaddingLeft(); if (line == 0) @@ -3987,7 +4678,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (line == mLayout.getLineCount() - 1) bottom += getExtendedPaddingBottom(); - r.set(h, top, h, bottom); + r.set(h, top, h+1, bottom); r.offset(-mScrollX, -mScrollY); } @@ -4056,6 +4747,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setSingleLine(boolean singleLine) { mSingleLine = singleLine; + if ((mInputType&EditorInfo.TYPE_MASK_CLASS) + == EditorInfo.TYPE_CLASS_TEXT) { + if (singleLine) { + mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; + } else { + mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; + } + } if (singleLine) { setLines(1); @@ -4089,9 +4788,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Sets how many times to repeat the marquee animation. Only applied if the + * TextView has marquee enabled. Set to -1 to repeat indefinitely. + * + * @attr ref android.R.styleable#TextView_marqueeRepeatLimit + */ + public void setMarqueeRepeatLimit(int marqueeLimit) { + mMarqueeRepeatLimit = marqueeLimit; + } + + /** * Returns where, if anywhere, words that are longer than the view * is wide should be ellipsized. */ + @ViewDebug.ExportedProperty public TextUtils.TruncateAt getEllipsize() { return mEllipsize; } @@ -4126,6 +4836,145 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + private boolean canMarquee() { + int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); + return width > 0 && mLayout.getLineWidth(0) > width; + } + + private void startMarquee() { + if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && + getLineCount() == 1 && canMarquee()) { + if (mMarquee == null) mMarquee = new Marquee(this); + mMarquee.start(mMarqueeRepeatLimit); + } + } + + private void stopMarquee() { + if (mMarquee != null && !mMarquee.isStopped()) { + mMarquee.stop(); + } + } + + private void startStopMarquee(boolean start) { + if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { + if (start) { + startMarquee(); + } else { + stopMarquee(); + } + } + } + + private static final class Marquee extends Handler { + // TODO: Add an option to configure this + private static final int MARQUEE_DELAY = 1200; + private static final int MARQUEE_RESTART_DELAY = 1200; + private static final int MARQUEE_RESOLUTION = 1000 / 30; + private static final int MARQUEE_PIXELS_PER_SECOND = 30; + + private static final byte MARQUEE_STOPPED = 0x0; + private static final byte MARQUEE_STARTING = 0x1; + private static final byte MARQUEE_RUNNING = 0x2; + + private static final int MESSAGE_START = 0x1; + private static final int MESSAGE_TICK = 0x2; + private static final int MESSAGE_RESTART = 0x3; + + private final WeakReference<TextView> mView; + + private byte mStatus = MARQUEE_STOPPED; + private float mScrollUnit; + private float mMaxScroll; + private int mRepeatLimit; + + float mScroll; + + Marquee(TextView v) { + mView = new WeakReference<TextView>(v); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_START: + mStatus = MARQUEE_RUNNING; + tick(); + break; + case MESSAGE_TICK: + tick(); + break; + case MESSAGE_RESTART: + if (mStatus == MARQUEE_RUNNING) { + if (mRepeatLimit >= 0) { + mRepeatLimit--; + } + start(mRepeatLimit); + } + break; + } + } + + void tick() { + if (mStatus != MARQUEE_RUNNING) { + return; + } + + removeMessages(MESSAGE_TICK); + + final TextView textView = mView.get(); + if (textView != null && (textView.isFocused() || textView.isSelected())) { + mScroll += mScrollUnit; + if (mScroll > mMaxScroll) { + mScroll = mMaxScroll; + sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY); + } else { + sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION); + } + textView.invalidate(); + } + } + + void stop() { + mStatus = MARQUEE_STOPPED; + removeMessages(MESSAGE_START); + removeMessages(MESSAGE_RESTART); + removeMessages(MESSAGE_TICK); + resetScroll(); + } + + private void resetScroll() { + mScroll = 0.0f; + final TextView textView = mView.get(); + if (textView != null) textView.invalidate(); + } + + void start(int repeatLimit) { + if (repeatLimit == 0) { + stop(); + return; + } + mRepeatLimit = repeatLimit; + final TextView textView = mView.get(); + if (textView != null && textView.mLayout != null) { + mStatus = MARQUEE_STARTING; + mScroll = 0.0f; + mScrollUnit = MARQUEE_PIXELS_PER_SECOND / (float) MARQUEE_RESOLUTION; + mMaxScroll = textView.mLayout.getLineWidth(0) - (textView.getWidth() - + textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight()); + textView.invalidate(); + sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY); + } + } + + boolean isRunning() { + return mStatus == MARQUEE_RUNNING; + } + + boolean isStopped() { + return mStatus == MARQUEE_STOPPED; + } + } + /** * This method is called when the text is changed, in case any * subclasses would like to know. @@ -4151,6 +5000,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Adds a TextWatcher to the list of those whose methods are called * whenever this TextView's text changes. + * <p> + * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously + * not called after {@link #setText} calls. Now, doing {@link #setText} + * if there are any text changed listeners forces the buffer type to + * Editable if it would not otherwise be and does call this method. */ public void addTextChangedListener(TextWatcher watcher) { if (mListeners == null) { @@ -4186,7 +5040,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void sendOnTextChanged(CharSequence text, int start, int before, + /** + * Not private so it can be called from an inner class without going + * through a thunk. + */ + void sendOnTextChanged(CharSequence text, int start, int before, int after) { if (mListeners != null) { final ArrayList<TextWatcher> list = mListeners; @@ -4197,7 +5055,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void sendAfterTextChanged(Editable text) { + /** + * Not private so it can be called from an inner class without going + * through a thunk. + */ + void sendAfterTextChanged(Editable text) { if (mListeners != null) { final ArrayList<TextWatcher> list = mListeners; final int count = list.size(); @@ -4207,105 +5069,118 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private class ChangeWatcher - extends Handler - implements TextWatcher, SpanWatcher { - public void beforeTextChanged(CharSequence buffer, int start, - int before, int after) { - TextView.this.sendBeforeTextChanged(buffer, start, before, after); + /** + * Not private so it can be called from an inner class without going + * through a thunk. + */ + void handleTextChanged(CharSequence buffer, int start, + int before, int after) { + invalidate(); + + int curs = Selection.getSelectionStart(buffer); + + if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == + Gravity.BOTTOM) { + registerForPreDraw(); } - public void onTextChanged(CharSequence buffer, int start, - int before, int after) { - invalidate(); + if (curs >= 0) { + mHighlightPathBogus = true; - int curs = Selection.getSelectionStart(buffer); + if (isFocused()) { + mShowCursor = SystemClock.uptimeMillis(); + makeBlink(); + } + } - if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == - Gravity.BOTTOM) { - registerForPreDraw(); + checkForResize(); + + sendOnTextChanged(buffer, start, before, after); + onTextChanged(buffer, start, before, after); + } + + /** + * Not private so it can be called from an inner class without going + * through a thunk. + */ + void spanChange(Spanned buf, Object what, int o, int n) { + // XXX Make the start and end move together if this ends up + // spending too much time invalidating. + + if (what == Selection.SELECTION_END) { + mHighlightPathBogus = true; + + if (!isFocused()) { + mSelectionMoved = true; } - if (curs >= 0) { - mHighlightPathBogus = true; + if (o >= 0 || n >= 0) { + invalidateCursor(Selection.getSelectionStart(buf), o, n); + registerForPreDraw(); if (isFocused()) { mShowCursor = SystemClock.uptimeMillis(); makeBlink(); } } - - checkForResize(); - - TextView.this.sendOnTextChanged(buffer, start, before, after); - TextView.this.onTextChanged(buffer, start, before, after); } - public void afterTextChanged(Editable buffer) { - TextView.this.sendAfterTextChanged(buffer); - } - - private void spanChange(Spanned buf, Object what, int o, int n) { - // XXX Make the start and end move together if this ends up - // spending too much time invalidating. + if (what == Selection.SELECTION_START) { + mHighlightPathBogus = true; - if (what == Selection.SELECTION_END) { - mHighlightPathBogus = true; - - if (!isFocused()) { - mSelectionMoved = true; - } - - if (o >= 0 || n >= 0) { - invalidateCursor(Selection.getSelectionStart(buf), o, n); - registerForPreDraw(); + if (!isFocused()) { + mSelectionMoved = true; + } - if (isFocused()) { - mShowCursor = SystemClock.uptimeMillis(); - makeBlink(); - } - } + if (o >= 0 || n >= 0) { + invalidateCursor(Selection.getSelectionEnd(buf), o, n); } + } - if (what == Selection.SELECTION_START) { - mHighlightPathBogus = true; + if (what instanceof UpdateLayout || + what instanceof ParagraphStyle) { + invalidate(); + mHighlightPathBogus = true; + checkForResize(); + } - if (!isFocused()) { - mSelectionMoved = true; - } + if (MetaKeyKeyListener.isMetaTracker(buf, what)) { + mHighlightPathBogus = true; - if (o >= 0 || n >= 0) { - invalidateCursor(Selection.getSelectionEnd(buf), o, n); - } + if (Selection.getSelectionStart(buf) >= 0) { + invalidateCursor(); } + } + } - if (what instanceof UpdateLayout || - what instanceof ParagraphStyle) { - invalidate(); - mHighlightPathBogus = true; - checkForResize(); - } + private class ChangeWatcher + implements TextWatcher, SpanWatcher { + public void beforeTextChanged(CharSequence buffer, int start, + int before, int after) { + TextView.this.sendBeforeTextChanged(buffer, start, before, after); + } - if (MetaKeyKeyListener.isMetaTracker(buf, what)) { - mHighlightPathBogus = true; + public void onTextChanged(CharSequence buffer, int start, + int before, int after) { + TextView.this.handleTextChanged(buffer, start, before, after); + } - if (Selection.getSelectionStart(buf) >= 0) { - invalidateCursor(); - } - } + public void afterTextChanged(Editable buffer) { + TextView.this.sendAfterTextChanged(buffer); + TextView.this.reportExtractedText(); } public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { - spanChange(buf, what, s, st); + TextView.this.spanChange(buf, what, s, st); } public void onSpanAdded(Spannable buf, Object what, int s, int e) { - spanChange(buf, what, -1, s); + TextView.this.spanChange(buf, what, -1, s); } public void onSpanRemoved(Spannable buf, Object what, int s, int e) { - spanChange(buf, what, s, -1); + TextView.this.spanChange(buf, what, s, -1); } } @@ -4378,6 +5253,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + startStopMarquee(focused); + if (mTransformation != null) { mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); @@ -4386,6 +5263,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener super.onFocusChanged(focused, direction, previouslyFocusedRect); } + @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); @@ -4403,6 +5281,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mBlink.cancel(); } } + + startStopMarquee(hasWindowFocus); + } + + @Override + public void setSelected(boolean selected) { + boolean wasSelected = isSelected(); + + super.setSelected(selected); + + if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { + if (selected) { + startMarquee(); + } else { + stopMarquee(); + } + } } @Override @@ -4421,7 +5316,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mMovement != null && mText instanceof Spannable && mLayout != null) { - if (mMovement.onTouchEvent(this, (Spannable) mText, event)) { + boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event); + + if (mText instanceof Editable + && mInputType != EditorInfo.TYPE_NULL) { + if (event.getAction() == MotionEvent.ACTION_UP && isFocused()) { + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(this); + } + } + + if (moved) { return true; } } @@ -4490,6 +5396,52 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override + protected float getLeftFadingEdgeStrength() { + if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { + if (mMarquee != null && !mMarquee.isStopped()) { + final Marquee marquee = mMarquee; + return marquee.mScroll / getHorizontalFadingEdgeLength(); + } else if (getLineCount() == 1) { + switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.LEFT: + return 0.0f; + case Gravity.RIGHT: + return (mLayout.getLineRight(0) - (mRight - mLeft) - + getCompoundPaddingLeft() - getCompoundPaddingRight() - + mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); + case Gravity.CENTER_HORIZONTAL: + return 0.0f; + } + } + } + return super.getLeftFadingEdgeStrength(); + } + + @Override + protected float getRightFadingEdgeStrength() { + if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { + if (mMarquee != null && !mMarquee.isStopped()) { + final Marquee marquee = mMarquee; + return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); + } else if (getLineCount() == 1) { + switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.LEFT: + return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) - + getCompoundPaddingLeft() - getCompoundPaddingRight()) / + getHorizontalFadingEdgeLength(); + case Gravity.RIGHT: + return 0.0f; + case Gravity.CENTER_HORIZONTAL: + return (mLayout.getLineWidth(0) - ((mRight - mLeft) - + getCompoundPaddingLeft() - getCompoundPaddingRight())) / + getHorizontalFadingEdgeLength(); + } + } + } + return super.getRightFadingEdgeStrength(); + } + + @Override protected int computeHorizontalScrollRange() { if (mLayout != null) return mLayout.getWidth(); @@ -4599,6 +5551,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private boolean canCut() { + if (mTransformation instanceof PasswordTransformationMethod) { + return false; + } + if (mText.length() > 0 && getSelectionStart() >= 0) { if (mText instanceof Editable && mInput != null) { return true; @@ -4609,6 +5565,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private boolean canCopy() { + if (mTransformation instanceof PasswordTransformationMethod) { + return false; + } + if (mText.length() > 0 && getSelectionStart() >= 0) { return true; } @@ -4632,8 +5592,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override protected void onCreateContextMenu(ContextMenu menu) { super.onCreateContextMenu(menu); + boolean added = false; if (!isFocused()) { + if (isFocusable() && mInput != null) { + if (canCopy()) { + MenuHandler handler = new MenuHandler(); + int name = com.android.internal.R.string.copyAll; + + menu.add(0, ID_COPY, 0, name). + setOnMenuItemClickListener(handler). + setAlphabeticShortcut('c'); + menu.setHeaderTitle(com.android.internal.R.string. + editTextMenuTitle); + } + } + return; } @@ -4644,6 +5618,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener com.android.internal.R.string.selectAll). setOnMenuItemClickListener(handler). setAlphabeticShortcut('a'); + added = true; } boolean selection = getSelectionStart() != getSelectionEnd(); @@ -4659,6 +5634,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener menu.add(0, ID_CUT, 0, name). setOnMenuItemClickListener(handler). setAlphabeticShortcut('x'); + added = true; } if (canCopy()) { @@ -4672,12 +5648,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener menu.add(0, ID_COPY, 0, name). setOnMenuItemClickListener(handler). setAlphabeticShortcut('c'); + added = true; } if (canPaste()) { menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). setOnMenuItemClickListener(handler). setAlphabeticShortcut('v'); + added = true; } if (mText instanceof Spanned) { @@ -4693,15 +5671,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl). setOnMenuItemClickListener(handler); + added = true; } } + + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && imm.isActive(this)) { + menu.add(1, ID_SWITCH_IME, 0, com.android.internal.R.string.inputMethod). + setOnMenuItemClickListener(handler); + added = true; + } + + if (added) { + menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle); + } } - private static final int ID_SELECT_ALL = 101; - private static final int ID_CUT = 102; - private static final int ID_COPY = 103; - private static final int ID_PASTE = 104; - private static final int ID_COPY_URL = 105; + private static final int ID_SELECT_ALL = com.android.internal.R.id.selectAll; + private static final int ID_CUT = com.android.internal.R.id.cut; + private static final int ID_COPY = com.android.internal.R.id.copy; + private static final int ID_PASTE = com.android.internal.R.id.paste; + private static final int ID_COPY_URL = com.android.internal.R.id.copyUrl; + private static final int ID_SWITCH_IME = com.android.internal.R.id.inputMethod; private class MenuHandler implements MenuItem.OnMenuItemClickListener { public boolean onMenuItemClick(MenuItem item) { @@ -4713,6 +5704,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); + if (!isFocused()) { + selStart = 0; + selEnd = mText.length(); + } + int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); @@ -4769,7 +5765,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } return true; - } + + case ID_SWITCH_IME: + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.showInputMethodPicker(); + } + return true; + } return false; } @@ -4790,10 +5793,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private CharSequence mTransformed; private BufferType mBufferType = BufferType.NORMAL; + private int mInputType = EditorInfo.TYPE_NULL; private CharSequence mHint; private Layout mHintLayout; private KeyListener mInput; + private MovementMethod mMovement; private TransformationMethod mTransformation; private ChangeWatcher mChangeWatcher; @@ -4842,7 +5847,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // tmp primitives, so we don't alloc them on each draw private Path mHighlightPath; private boolean mHighlightPathBogus = true; - private static RectF sTempRect = new RectF(); + private static final RectF sTempRect = new RectF(); // XXX should be much larger private static final int VERY_WIDE = 16384; @@ -4858,8 +5863,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private BoringLayout mSavedLayout, mSavedHintLayout; - - private static final InputFilter[] NO_FILTERS = new InputFilter[0]; private InputFilter[] mFilters = NO_FILTERS; private static final Spanned EMPTY_SPANNED = new SpannedString(""); |