blob: 86b6b1d5d797ea7e638975b336edf190d0522ce5 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
svetoslavganov75986cf2009-05-14 22:28:01 -070019import com.android.internal.util.FastMath;
20import com.android.internal.widget.EditableInputConnection;
21
Gilles Debunne27113f82010-08-23 12:09:14 -070022import org.xmlpull.v1.XmlPullParserException;
23
Gilles Debunne78996c92010-10-12 16:01:47 -070024import android.R;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070025import android.content.ClipData;
Gilles Debunnef170a342010-11-11 11:08:59 -080026import android.content.ClipData.Item;
Gilles Debunne64e54a62010-09-07 19:07:17 -070027import android.content.ClipboardManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.Context;
Gilles Debunnef788a9f2010-07-22 10:17:23 -070029import android.content.pm.PackageManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.content.res.ColorStateList;
31import android.content.res.Resources;
32import android.content.res.TypedArray;
33import android.content.res.XmlResourceParser;
34import android.graphics.Canvas;
Gilles Debunne12d91ce2010-12-10 11:36:29 -080035import android.graphics.Color;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.graphics.Paint;
37import android.graphics.Path;
38import android.graphics.Rect;
39import android.graphics.RectF;
40import android.graphics.Typeface;
41import android.graphics.drawable.Drawable;
Gilles Debunne64e54a62010-09-07 19:07:17 -070042import android.inputmethodservice.ExtractEditText;
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070043import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.os.Bundle;
45import android.os.Handler;
svetoslavganov75986cf2009-05-14 22:28:01 -070046import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.os.Parcel;
48import android.os.Parcelable;
49import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.text.BoringLayout;
51import android.text.DynamicLayout;
52import android.text.Editable;
53import android.text.GetChars;
54import android.text.GraphicsOperations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055import android.text.InputFilter;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070056import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057import android.text.Layout;
58import android.text.ParcelableSpan;
59import android.text.Selection;
60import android.text.SpanWatcher;
61import android.text.Spannable;
svetoslavganov75986cf2009-05-14 22:28:01 -070062import android.text.SpannableString;
Gilles Debunne214a8622011-04-26 15:44:37 -070063import android.text.SpannableStringBuilder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064import android.text.Spanned;
65import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066import android.text.StaticLayout;
67import android.text.TextPaint;
68import android.text.TextUtils;
69import android.text.TextWatcher;
Gilles Debunne86b9c782010-11-11 10:43:48 -080070import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071import android.text.method.DateKeyListener;
72import android.text.method.DateTimeKeyListener;
73import android.text.method.DialerKeyListener;
74import android.text.method.DigitsKeyListener;
75import android.text.method.KeyListener;
76import android.text.method.LinkMovementMethod;
77import android.text.method.MetaKeyKeyListener;
78import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079import android.text.method.PasswordTransformationMethod;
80import android.text.method.SingleLineTransformationMethod;
81import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070082import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083import android.text.method.TransformationMethod;
Gilles Debunne214a8622011-04-26 15:44:37 -070084import android.text.method.WordIterator;
Gilles Debunnef3895ed2010-12-21 12:53:58 -080085import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086import android.text.style.ParagraphStyle;
Gilles Debunne2037b822011-04-22 13:07:33 -070087import android.text.style.SuggestionSpan;
Gilles Debunne214a8622011-04-26 15:44:37 -070088import android.text.style.TextAppearanceSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089import android.text.style.URLSpan;
Gilles Debunne214a8622011-04-26 15:44:37 -070090import android.text.style.UnderlineSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091import android.text.style.UpdateAppearance;
92import android.text.util.Linkify;
93import android.util.AttributeSet;
Gilles Debunne69340442011-03-31 13:37:51 -070094import android.util.DisplayMetrics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095import android.util.FloatMath;
svetoslavganov75986cf2009-05-14 22:28:01 -070096import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097import android.util.TypedValue;
Gilles Debunne27113f82010-08-23 12:09:14 -070098import android.view.ActionMode;
Gilles Debunnef4dceb12010-12-01 15:54:20 -080099import android.view.ActionMode.Callback;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100import android.view.ContextMenu;
Gilles Debunnef170a342010-11-11 11:08:59 -0800101import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700103import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800104import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105import android.view.KeyEvent;
106import android.view.LayoutInflater;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700107import android.view.Menu;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108import android.view.MenuItem;
109import android.view.MotionEvent;
110import android.view.View;
Gilles Debunne65f60412010-10-15 16:18:35 -0700111import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112import android.view.ViewDebug;
Adam Powell8c8293b2010-10-12 14:45:12 -0700113import android.view.ViewGroup;
Gilles Debunne27113f82010-08-23 12:09:14 -0700114import android.view.ViewGroup.LayoutParams;
Adam Powellabcbb1a2010-10-04 21:12:19 -0700115import android.view.ViewParent;
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700116import android.view.ViewAncestor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117import android.view.ViewTreeObserver;
Gilles Debunnecbfbb522010-10-07 16:57:31 -0700118import android.view.WindowManager;
svetoslavganov75986cf2009-05-14 22:28:01 -0700119import android.view.accessibility.AccessibilityEvent;
120import android.view.accessibility.AccessibilityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121import android.view.animation.AnimationUtils;
122import android.view.inputmethod.BaseInputConnection;
123import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800124import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700125import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126import android.view.inputmethod.ExtractedText;
127import android.view.inputmethod.ExtractedTextRequest;
128import android.view.inputmethod.InputConnection;
129import android.view.inputmethod.InputMethodManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130import android.widget.RemoteViews.RemoteView;
131
Gilles Debunne27113f82010-08-23 12:09:14 -0700132import java.io.IOException;
133import java.lang.ref.WeakReference;
Gilles Debunne214a8622011-04-26 15:44:37 -0700134import java.text.BreakIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -0700135import java.util.ArrayList;
136
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137/**
138 * Displays text to the user and optionally allows them to edit it. A TextView
139 * is a complete text editor, however the basic class is configured to not
140 * allow editing; see {@link EditText} for a subclass that configures the text
141 * view for editing.
142 *
143 * <p>
144 * <b>XML attributes</b>
145 * <p>
146 * See {@link android.R.styleable#TextView TextView Attributes},
147 * {@link android.R.styleable#View View Attributes}
148 *
149 * @attr ref android.R.styleable#TextView_text
150 * @attr ref android.R.styleable#TextView_bufferType
151 * @attr ref android.R.styleable#TextView_hint
152 * @attr ref android.R.styleable#TextView_textColor
153 * @attr ref android.R.styleable#TextView_textColorHighlight
154 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700155 * @attr ref android.R.styleable#TextView_textAppearance
156 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 * @attr ref android.R.styleable#TextView_textSize
158 * @attr ref android.R.styleable#TextView_textScaleX
159 * @attr ref android.R.styleable#TextView_typeface
160 * @attr ref android.R.styleable#TextView_textStyle
161 * @attr ref android.R.styleable#TextView_cursorVisible
162 * @attr ref android.R.styleable#TextView_maxLines
163 * @attr ref android.R.styleable#TextView_maxHeight
164 * @attr ref android.R.styleable#TextView_lines
165 * @attr ref android.R.styleable#TextView_height
166 * @attr ref android.R.styleable#TextView_minLines
167 * @attr ref android.R.styleable#TextView_minHeight
168 * @attr ref android.R.styleable#TextView_maxEms
169 * @attr ref android.R.styleable#TextView_maxWidth
170 * @attr ref android.R.styleable#TextView_ems
171 * @attr ref android.R.styleable#TextView_width
172 * @attr ref android.R.styleable#TextView_minEms
173 * @attr ref android.R.styleable#TextView_minWidth
174 * @attr ref android.R.styleable#TextView_gravity
175 * @attr ref android.R.styleable#TextView_scrollHorizontally
176 * @attr ref android.R.styleable#TextView_password
177 * @attr ref android.R.styleable#TextView_singleLine
178 * @attr ref android.R.styleable#TextView_selectAllOnFocus
179 * @attr ref android.R.styleable#TextView_includeFontPadding
180 * @attr ref android.R.styleable#TextView_maxLength
181 * @attr ref android.R.styleable#TextView_shadowColor
182 * @attr ref android.R.styleable#TextView_shadowDx
183 * @attr ref android.R.styleable#TextView_shadowDy
184 * @attr ref android.R.styleable#TextView_shadowRadius
185 * @attr ref android.R.styleable#TextView_autoLink
186 * @attr ref android.R.styleable#TextView_linksClickable
187 * @attr ref android.R.styleable#TextView_numeric
188 * @attr ref android.R.styleable#TextView_digits
189 * @attr ref android.R.styleable#TextView_phoneNumber
190 * @attr ref android.R.styleable#TextView_inputMethod
191 * @attr ref android.R.styleable#TextView_capitalize
192 * @attr ref android.R.styleable#TextView_autoText
193 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700194 * @attr ref android.R.styleable#TextView_freezesText
195 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 * @attr ref android.R.styleable#TextView_drawableTop
197 * @attr ref android.R.styleable#TextView_drawableBottom
198 * @attr ref android.R.styleable#TextView_drawableRight
199 * @attr ref android.R.styleable#TextView_drawableLeft
Romain Guyd6a463a2009-05-21 23:10:10 -0700200 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 * @attr ref android.R.styleable#TextView_lineSpacingExtra
202 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
203 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700204 * @attr ref android.R.styleable#TextView_inputType
205 * @attr ref android.R.styleable#TextView_imeOptions
206 * @attr ref android.R.styleable#TextView_privateImeOptions
207 * @attr ref android.R.styleable#TextView_imeActionLabel
208 * @attr ref android.R.styleable#TextView_imeActionId
209 * @attr ref android.R.styleable#TextView_editorExtras
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 */
211@RemoteView
212public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700213 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800215
Romain Guy8b55f372010-08-18 17:10:07 -0700216 private static final int PRIORITY = 100;
Gilles Debunne3dbf55c2010-12-16 10:31:51 -0800217 private int mCurrentAlpha = 255;
Adam Powell879fb6b2010-09-20 11:23:56 -0700218
Adam Powellabcbb1a2010-10-04 21:12:19 -0700219 final int[] mTempCoords = new int[2];
220 Rect mTempRect;
Adam Powell879fb6b2010-09-20 11:23:56 -0700221
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 private ColorStateList mTextColor;
223 private int mCurTextColor;
224 private ColorStateList mHintTextColor;
225 private ColorStateList mLinkTextColor;
226 private int mCurHintTextColor;
227 private boolean mFreezesText;
228 private boolean mFrozenWithFocus;
229 private boolean mTemporaryDetach;
Romain Guya440b002010-02-24 15:57:54 -0800230 private boolean mDispatchTemporaryDetach;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800232 private boolean mDiscardNextActionUp = false;
233 private boolean mIgnoreActionUpEvent = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234
235 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
236 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
237
238 private float mShadowRadius, mShadowDx, mShadowDy;
239
240 private static final int PREDRAW_NOT_REGISTERED = 0;
241 private static final int PREDRAW_PENDING = 1;
242 private static final int PREDRAW_DONE = 2;
243 private int mPreDrawState = PREDRAW_NOT_REGISTERED;
244
245 private TextUtils.TruncateAt mEllipsize = null;
246
247 // Enum for the "typeface" XML parameter.
248 // TODO: How can we get this from the XML instead of hardcoding it here?
249 private static final int SANS = 1;
250 private static final int SERIF = 2;
251 private static final int MONOSPACE = 3;
252
253 // Bitfield for the "numeric" XML parameter.
254 // TODO: How can we get this from the XML instead of hardcoding it here?
255 private static final int SIGNED = 2;
256 private static final int DECIMAL = 4;
257
258 class Drawables {
259 final Rect mCompoundRect = new Rect();
260 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
261 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
262 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
263 int mDrawablePadding;
264 }
265 private Drawables mDrawables;
266
267 private CharSequence mError;
268 private boolean mErrorWasChanged;
The Android Open Source Project10592532009-03-18 17:39:46 -0700269 private ErrorPopup mPopup;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270 /**
271 * This flag is set if the TextView tries to display an error before it
272 * is attached to the window (so its position is still unknown).
273 * It causes the error to be shown later, when onAttachedToWindow()
274 * is called.
275 */
276 private boolean mShowErrorAfterAttach;
277
278 private CharWrapper mCharWrapper = null;
279
280 private boolean mSelectionMoved = false;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700281 private boolean mTouchFocusSelected = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282
283 private Marquee mMarquee;
284 private boolean mRestartMarquee;
285
286 private int mMarqueeRepeatLimit = 3;
287
288 class InputContentType {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700289 int imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 String privateImeOptions;
291 CharSequence imeActionLabel;
292 int imeActionId;
293 Bundle extras;
294 OnEditorActionListener onEditorActionListener;
295 boolean enterDown;
296 }
297 InputContentType mInputContentType;
298
299 class InputMethodState {
300 Rect mCursorRectInWindow = new Rect();
301 RectF mTmpRectF = new RectF();
302 float[] mTmpOffset = new float[2];
303 ExtractedTextRequest mExtracting;
304 final ExtractedText mTmpExtracted = new ExtractedText();
305 int mBatchEditNesting;
306 boolean mCursorChanged;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700307 boolean mSelectionModeChanged;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 boolean mContentChanged;
309 int mChangedStart, mChangedEnd, mChangedDelta;
310 }
311 InputMethodState mInputMethodState;
312
Gilles Debunnef75c97e2011-02-10 16:09:53 -0800313 private int mTextSelectHandleLeftRes;
314 private int mTextSelectHandleRightRes;
315 private int mTextSelectHandleRes;
316 private int mTextEditPasteWindowLayout, mTextEditSidePasteWindowLayout;
317 private int mTextEditNoPasteWindowLayout, mTextEditSideNoPasteWindowLayout;
Adam Powellfbb3b472010-10-06 21:04:35 -0700318
Gilles Debunne69340442011-03-31 13:37:51 -0700319 private int mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout;
320 private int mTextEditSuggestionItemLayout;
321 private SuggestionsPopupWindow mSuggestionsPopupWindow;
Gilles Debunne214a8622011-04-26 15:44:37 -0700322 private SuggestionRangeSpan mSuggestionRangeSpan;
Gilles Debunne69340442011-03-31 13:37:51 -0700323
Gilles Debunnef75c97e2011-02-10 16:09:53 -0800324 private int mCursorDrawableRes;
325 private final Drawable[] mCursorDrawable = new Drawable[2];
326 private int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2
327
328 private Drawable mSelectHandleLeft;
329 private Drawable mSelectHandleRight;
330 private Drawable mSelectHandleCenter;
Adam Powellb08013c2010-09-16 16:28:11 -0700331
Gilles Debunne9948ad72010-11-24 14:00:46 -0800332 private int mLastDownPositionX, mLastDownPositionY;
Gilles Debunnef4dceb12010-12-01 15:54:20 -0800333 private Callback mCustomSelectionActionModeCallback;
Gilles Debunne9948ad72010-11-24 14:00:46 -0800334
Gilles Debunneaa85a4c2010-12-06 18:27:17 -0800335 private final int mSquaredTouchSlopDistance;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -0800336 // Set when this TextView gained focus with some text selected. Will start selection mode.
337 private boolean mCreatedWithASelection = false;
Gilles Debunneaa85a4c2010-12-06 18:27:17 -0800338
Gilles Debunnec59269f2011-04-22 11:46:09 -0700339 private WordIterator mWordIterator;
340
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 /*
342 * Kick-start the font cache for the zygote process (to pay the cost of
343 * initializing freetype for our default font only once).
344 */
345 static {
346 Paint p = new Paint();
347 p.setAntiAlias(true);
348 // We don't care about the result, just the side-effect of measuring.
349 p.measureText("H");
350 }
351
352 /**
353 * Interface definition for a callback to be invoked when an action is
354 * performed on the editor.
355 */
356 public interface OnEditorActionListener {
357 /**
358 * Called when an action is being performed.
359 *
360 * @param v The view that was clicked.
361 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700362 * identifier you supplied, or {@link EditorInfo#IME_NULL
363 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 * being pressed.
365 * @param event If triggered by an enter key, this is the event;
366 * otherwise, this is null.
367 * @return Return true if you have consumed the action, else false.
368 */
369 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
370 }
371
372 public TextView(Context context) {
373 this(context, null);
374 }
375
376 public TextView(Context context,
377 AttributeSet attrs) {
378 this(context, attrs, com.android.internal.R.attr.textViewStyle);
379 }
380
Gilles Debunnee15b3582010-06-16 15:17:21 -0700381 @SuppressWarnings("deprecation")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 public TextView(Context context,
383 AttributeSet attrs,
384 int defStyle) {
385 super(context, attrs, defStyle);
386 mText = "";
387
388 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700389 mTextPaint.density = getResources().getDisplayMetrics().density;
Dianne Hackbornafa78962009-09-28 17:33:54 -0700390 mTextPaint.setCompatibilityScaling(
391 getResources().getCompatibilityInfo().applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 // If we get the paint from the skin, we should set it to left, since
394 // the layout always wants it to be left.
395 // mTextPaint.setTextAlign(Paint.Align.LEFT);
396
397 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Dianne Hackbornafa78962009-09-28 17:33:54 -0700398 mHighlightPaint.setCompatibilityScaling(
399 getResources().getCompatibilityInfo().applicationScale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400
401 mMovement = getDefaultMovementMethod();
402 mTransformation = null;
403
404 TypedArray a =
405 context.obtainStyledAttributes(
406 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
407
408 int textColorHighlight = 0;
409 ColorStateList textColor = null;
410 ColorStateList textColorHint = null;
411 ColorStateList textColorLink = null;
412 int textSize = 15;
413 int typefaceIndex = -1;
414 int styleIndex = -1;
415
416 /*
417 * Look the appearance up without checking first if it exists because
418 * almost every TextView has one and it greatly simplifies the logic
419 * to be able to parse the appearance first and then let specific tags
420 * for this View override it.
421 */
422 TypedArray appearance = null;
423 int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
424 if (ap != -1) {
425 appearance = context.obtainStyledAttributes(ap,
426 com.android.internal.R.styleable.
427 TextAppearance);
428 }
429 if (appearance != null) {
430 int n = appearance.getIndexCount();
431 for (int i = 0; i < n; i++) {
432 int attr = appearance.getIndex(i);
433
434 switch (attr) {
435 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
436 textColorHighlight = appearance.getColor(attr, textColorHighlight);
437 break;
438
439 case com.android.internal.R.styleable.TextAppearance_textColor:
440 textColor = appearance.getColorStateList(attr);
441 break;
442
443 case com.android.internal.R.styleable.TextAppearance_textColorHint:
444 textColorHint = appearance.getColorStateList(attr);
445 break;
446
447 case com.android.internal.R.styleable.TextAppearance_textColorLink:
448 textColorLink = appearance.getColorStateList(attr);
449 break;
450
451 case com.android.internal.R.styleable.TextAppearance_textSize:
452 textSize = appearance.getDimensionPixelSize(attr, textSize);
453 break;
454
455 case com.android.internal.R.styleable.TextAppearance_typeface:
456 typefaceIndex = appearance.getInt(attr, -1);
457 break;
458
459 case com.android.internal.R.styleable.TextAppearance_textStyle:
460 styleIndex = appearance.getInt(attr, -1);
461 break;
462 }
463 }
464
465 appearance.recycle();
466 }
467
468 boolean editable = getDefaultEditable();
469 CharSequence inputMethod = null;
470 int numeric = 0;
471 CharSequence digits = null;
472 boolean phone = false;
473 boolean autotext = false;
474 int autocap = -1;
475 int buffertype = 0;
476 boolean selectallonfocus = false;
477 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
478 drawableBottom = null;
479 int drawablePadding = 0;
480 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700481 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482 int maxlength = -1;
483 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700484 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 int shadowcolor = 0;
486 float dx = 0, dy = 0, r = 0;
487 boolean password = false;
488 int inputType = EditorInfo.TYPE_NULL;
489
490 int n = a.getIndexCount();
491 for (int i = 0; i < n; i++) {
492 int attr = a.getIndex(i);
493
494 switch (attr) {
495 case com.android.internal.R.styleable.TextView_editable:
496 editable = a.getBoolean(attr, editable);
497 break;
498
499 case com.android.internal.R.styleable.TextView_inputMethod:
500 inputMethod = a.getText(attr);
501 break;
502
503 case com.android.internal.R.styleable.TextView_numeric:
504 numeric = a.getInt(attr, numeric);
505 break;
506
507 case com.android.internal.R.styleable.TextView_digits:
508 digits = a.getText(attr);
509 break;
510
511 case com.android.internal.R.styleable.TextView_phoneNumber:
512 phone = a.getBoolean(attr, phone);
513 break;
514
515 case com.android.internal.R.styleable.TextView_autoText:
516 autotext = a.getBoolean(attr, autotext);
517 break;
518
519 case com.android.internal.R.styleable.TextView_capitalize:
520 autocap = a.getInt(attr, autocap);
521 break;
522
523 case com.android.internal.R.styleable.TextView_bufferType:
524 buffertype = a.getInt(attr, buffertype);
525 break;
526
527 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
528 selectallonfocus = a.getBoolean(attr, selectallonfocus);
529 break;
530
531 case com.android.internal.R.styleable.TextView_autoLink:
532 mAutoLinkMask = a.getInt(attr, 0);
533 break;
534
535 case com.android.internal.R.styleable.TextView_linksClickable:
536 mLinksClickable = a.getBoolean(attr, true);
537 break;
538
539 case com.android.internal.R.styleable.TextView_drawableLeft:
540 drawableLeft = a.getDrawable(attr);
541 break;
542
543 case com.android.internal.R.styleable.TextView_drawableTop:
544 drawableTop = a.getDrawable(attr);
545 break;
546
547 case com.android.internal.R.styleable.TextView_drawableRight:
548 drawableRight = a.getDrawable(attr);
549 break;
550
551 case com.android.internal.R.styleable.TextView_drawableBottom:
552 drawableBottom = a.getDrawable(attr);
553 break;
554
555 case com.android.internal.R.styleable.TextView_drawablePadding:
556 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
557 break;
558
559 case com.android.internal.R.styleable.TextView_maxLines:
560 setMaxLines(a.getInt(attr, -1));
561 break;
562
563 case com.android.internal.R.styleable.TextView_maxHeight:
564 setMaxHeight(a.getDimensionPixelSize(attr, -1));
565 break;
566
567 case com.android.internal.R.styleable.TextView_lines:
568 setLines(a.getInt(attr, -1));
569 break;
570
571 case com.android.internal.R.styleable.TextView_height:
572 setHeight(a.getDimensionPixelSize(attr, -1));
573 break;
574
575 case com.android.internal.R.styleable.TextView_minLines:
576 setMinLines(a.getInt(attr, -1));
577 break;
578
579 case com.android.internal.R.styleable.TextView_minHeight:
580 setMinHeight(a.getDimensionPixelSize(attr, -1));
581 break;
582
583 case com.android.internal.R.styleable.TextView_maxEms:
584 setMaxEms(a.getInt(attr, -1));
585 break;
586
587 case com.android.internal.R.styleable.TextView_maxWidth:
588 setMaxWidth(a.getDimensionPixelSize(attr, -1));
589 break;
590
591 case com.android.internal.R.styleable.TextView_ems:
592 setEms(a.getInt(attr, -1));
593 break;
594
595 case com.android.internal.R.styleable.TextView_width:
596 setWidth(a.getDimensionPixelSize(attr, -1));
597 break;
598
599 case com.android.internal.R.styleable.TextView_minEms:
600 setMinEms(a.getInt(attr, -1));
601 break;
602
603 case com.android.internal.R.styleable.TextView_minWidth:
604 setMinWidth(a.getDimensionPixelSize(attr, -1));
605 break;
606
607 case com.android.internal.R.styleable.TextView_gravity:
608 setGravity(a.getInt(attr, -1));
609 break;
610
611 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700612 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 break;
614
615 case com.android.internal.R.styleable.TextView_text:
616 text = a.getText(attr);
617 break;
618
619 case com.android.internal.R.styleable.TextView_scrollHorizontally:
620 if (a.getBoolean(attr, false)) {
621 setHorizontallyScrolling(true);
622 }
623 break;
624
625 case com.android.internal.R.styleable.TextView_singleLine:
626 singleLine = a.getBoolean(attr, singleLine);
627 break;
628
629 case com.android.internal.R.styleable.TextView_ellipsize:
630 ellipsize = a.getInt(attr, ellipsize);
631 break;
632
633 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
634 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
635 break;
636
637 case com.android.internal.R.styleable.TextView_includeFontPadding:
638 if (!a.getBoolean(attr, true)) {
639 setIncludeFontPadding(false);
640 }
641 break;
642
643 case com.android.internal.R.styleable.TextView_cursorVisible:
644 if (!a.getBoolean(attr, true)) {
645 setCursorVisible(false);
646 }
647 break;
648
649 case com.android.internal.R.styleable.TextView_maxLength:
650 maxlength = a.getInt(attr, -1);
651 break;
652
653 case com.android.internal.R.styleable.TextView_textScaleX:
654 setTextScaleX(a.getFloat(attr, 1.0f));
655 break;
656
657 case com.android.internal.R.styleable.TextView_freezesText:
658 mFreezesText = a.getBoolean(attr, false);
659 break;
660
661 case com.android.internal.R.styleable.TextView_shadowColor:
662 shadowcolor = a.getInt(attr, 0);
663 break;
664
665 case com.android.internal.R.styleable.TextView_shadowDx:
666 dx = a.getFloat(attr, 0);
667 break;
668
669 case com.android.internal.R.styleable.TextView_shadowDy:
670 dy = a.getFloat(attr, 0);
671 break;
672
673 case com.android.internal.R.styleable.TextView_shadowRadius:
674 r = a.getFloat(attr, 0);
675 break;
676
677 case com.android.internal.R.styleable.TextView_enabled:
678 setEnabled(a.getBoolean(attr, isEnabled()));
679 break;
680
681 case com.android.internal.R.styleable.TextView_textColorHighlight:
682 textColorHighlight = a.getColor(attr, textColorHighlight);
683 break;
684
685 case com.android.internal.R.styleable.TextView_textColor:
686 textColor = a.getColorStateList(attr);
687 break;
688
689 case com.android.internal.R.styleable.TextView_textColorHint:
690 textColorHint = a.getColorStateList(attr);
691 break;
692
693 case com.android.internal.R.styleable.TextView_textColorLink:
694 textColorLink = a.getColorStateList(attr);
695 break;
696
697 case com.android.internal.R.styleable.TextView_textSize:
698 textSize = a.getDimensionPixelSize(attr, textSize);
699 break;
700
701 case com.android.internal.R.styleable.TextView_typeface:
702 typefaceIndex = a.getInt(attr, typefaceIndex);
703 break;
704
705 case com.android.internal.R.styleable.TextView_textStyle:
706 styleIndex = a.getInt(attr, styleIndex);
707 break;
708
709 case com.android.internal.R.styleable.TextView_password:
710 password = a.getBoolean(attr, password);
711 break;
712
713 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
714 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
715 break;
716
717 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
718 mSpacingMult = a.getFloat(attr, mSpacingMult);
719 break;
720
721 case com.android.internal.R.styleable.TextView_inputType:
722 inputType = a.getInt(attr, mInputType);
723 break;
724
725 case com.android.internal.R.styleable.TextView_imeOptions:
726 if (mInputContentType == null) {
727 mInputContentType = new InputContentType();
728 }
729 mInputContentType.imeOptions = a.getInt(attr,
730 mInputContentType.imeOptions);
731 break;
732
733 case com.android.internal.R.styleable.TextView_imeActionLabel:
734 if (mInputContentType == null) {
735 mInputContentType = new InputContentType();
736 }
737 mInputContentType.imeActionLabel = a.getText(attr);
738 break;
739
740 case com.android.internal.R.styleable.TextView_imeActionId:
741 if (mInputContentType == null) {
742 mInputContentType = new InputContentType();
743 }
744 mInputContentType.imeActionId = a.getInt(attr,
745 mInputContentType.imeActionId);
746 break;
747
748 case com.android.internal.R.styleable.TextView_privateImeOptions:
749 setPrivateImeOptions(a.getString(attr));
750 break;
751
752 case com.android.internal.R.styleable.TextView_editorExtras:
753 try {
754 setInputExtras(a.getResourceId(attr, 0));
755 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700756 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700758 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800759 }
760 break;
Adam Powellb08013c2010-09-16 16:28:11 -0700761
Gilles Debunnef75c97e2011-02-10 16:09:53 -0800762 case com.android.internal.R.styleable.TextView_textCursorDrawable:
763 mCursorDrawableRes = a.getResourceId(attr, 0);
764 break;
765
Adam Powellb08013c2010-09-16 16:28:11 -0700766 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
767 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
768 break;
769
770 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
771 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
772 break;
773
774 case com.android.internal.R.styleable.TextView_textSelectHandle:
775 mTextSelectHandleRes = a.getResourceId(attr, 0);
776 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -0700777
778 case com.android.internal.R.styleable.TextView_textEditPasteWindowLayout:
779 mTextEditPasteWindowLayout = a.getResourceId(attr, 0);
780 break;
781
782 case com.android.internal.R.styleable.TextView_textEditNoPasteWindowLayout:
783 mTextEditNoPasteWindowLayout = a.getResourceId(attr, 0);
784 break;
785
Gilles Debunnee60e1e52011-01-20 12:19:44 -0800786 case com.android.internal.R.styleable.TextView_textEditSidePasteWindowLayout:
787 mTextEditSidePasteWindowLayout = a.getResourceId(attr, 0);
788 break;
789
790 case com.android.internal.R.styleable.TextView_textEditSideNoPasteWindowLayout:
791 mTextEditSideNoPasteWindowLayout = a.getResourceId(attr, 0);
792 break;
793
Gilles Debunne69340442011-03-31 13:37:51 -0700794 case com.android.internal.R.styleable.TextView_textEditSuggestionsBottomWindowLayout:
795 mTextEditSuggestionsBottomWindowLayout = a.getResourceId(attr, 0);
796 break;
797
798 case com.android.internal.R.styleable.TextView_textEditSuggestionsTopWindowLayout:
799 mTextEditSuggestionsTopWindowLayout = a.getResourceId(attr, 0);
800 break;
801
802 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
803 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
804 break;
805
Gilles Debunne86b9c782010-11-11 10:43:48 -0800806 case com.android.internal.R.styleable.TextView_textIsSelectable:
807 mTextIsSelectable = a.getBoolean(attr, false);
808 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 }
810 }
811 a.recycle();
812
813 BufferType bufferType = BufferType.EDITABLE;
814
Gilles Debunned7483bf2010-11-10 10:47:45 -0800815 final int variation =
816 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
817 final boolean passwordInputType = variation
818 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
819 final boolean webPasswordInputType = variation
820 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +0900821 final boolean numberPasswordInputType = variation
822 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -0800823
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800824 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -0700825 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826
827 try {
828 c = Class.forName(inputMethod.toString());
829 } catch (ClassNotFoundException ex) {
830 throw new RuntimeException(ex);
831 }
832
833 try {
834 mInput = (KeyListener) c.newInstance();
835 } catch (InstantiationException ex) {
836 throw new RuntimeException(ex);
837 } catch (IllegalAccessException ex) {
838 throw new RuntimeException(ex);
839 }
840 try {
841 mInputType = inputType != EditorInfo.TYPE_NULL
842 ? inputType
843 : mInput.getInputType();
844 } catch (IncompatibleClassChangeError e) {
845 mInputType = EditorInfo.TYPE_CLASS_TEXT;
846 }
847 } else if (digits != null) {
848 mInput = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -0700849 // If no input type was specified, we will default to generic
850 // text, since we can't tell the IME about the set of digits
851 // that was selected.
852 mInputType = inputType != EditorInfo.TYPE_NULL
853 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 } else if (inputType != EditorInfo.TYPE_NULL) {
855 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -0800856 // If set, the input type overrides what was set using the deprecated singleLine flag.
857 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858 } else if (phone) {
859 mInput = DialerKeyListener.getInstance();
Dianne Hackborn49a1a9b52009-03-24 20:06:11 -0700860 mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 } else if (numeric != 0) {
862 mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
863 (numeric & DECIMAL) != 0);
864 inputType = EditorInfo.TYPE_CLASS_NUMBER;
865 if ((numeric & SIGNED) != 0) {
866 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
867 }
868 if ((numeric & DECIMAL) != 0) {
869 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
870 }
871 mInputType = inputType;
872 } else if (autotext || autocap != -1) {
873 TextKeyListener.Capitalize cap;
874
875 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700876
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 switch (autocap) {
878 case 1:
879 cap = TextKeyListener.Capitalize.SENTENCES;
880 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
881 break;
882
883 case 2:
884 cap = TextKeyListener.Capitalize.WORDS;
885 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
886 break;
887
888 case 3:
889 cap = TextKeyListener.Capitalize.CHARACTERS;
890 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
891 break;
892
893 default:
894 cap = TextKeyListener.Capitalize.NONE;
895 break;
896 }
897
898 mInput = TextKeyListener.getInstance(autotext, cap);
899 mInputType = inputType;
Gilles Debunne86b9c782010-11-11 10:43:48 -0800900 } else if (mTextIsSelectable) {
901 // Prevent text changes from keyboard.
902 mInputType = EditorInfo.TYPE_NULL;
903 mInput = null;
904 bufferType = BufferType.SPANNABLE;
Gilles Debunnecbcb3452010-12-17 15:31:02 -0800905 // Required to request focus while in touch mode.
906 setFocusableInTouchMode(true);
Gilles Debunne86b9c782010-11-11 10:43:48 -0800907 // So that selection can be changed using arrow keys and touch is handled.
908 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909 } else if (editable) {
910 mInput = TextKeyListener.getInstance();
911 mInputType = EditorInfo.TYPE_CLASS_TEXT;
912 } else {
913 mInput = null;
914
915 switch (buffertype) {
916 case 0:
917 bufferType = BufferType.NORMAL;
918 break;
919 case 1:
920 bufferType = BufferType.SPANNABLE;
921 break;
922 case 2:
923 bufferType = BufferType.EDITABLE;
924 break;
925 }
926 }
927
Gilles Debunned7483bf2010-11-10 10:47:45 -0800928 // mInputType has been set from inputType, possibly modified by mInputMethod.
929 // Specialize mInputType to [web]password if we have a text class and the original input
930 // type was a password.
931 if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
932 if (password || passwordInputType) {
Leon Scrogginsb5ce0e02010-11-01 13:20:24 -0400933 mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -0800934 | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
Leon Scrogginsb5ce0e02010-11-01 13:20:24 -0400935 }
Gilles Debunned7483bf2010-11-10 10:47:45 -0800936 if (webPasswordInputType) {
937 mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
938 | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
939 }
Ken Wakasa82d731a2010-12-24 23:42:41 +0900940 } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
941 if (numberPasswordInputType) {
942 mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
943 | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
944 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 }
946
947 if (selectallonfocus) {
948 mSelectAllOnFocus = true;
949
950 if (bufferType == BufferType.NORMAL)
951 bufferType = BufferType.SPANNABLE;
952 }
953
954 setCompoundDrawablesWithIntrinsicBounds(
955 drawableLeft, drawableTop, drawableRight, drawableBottom);
956 setCompoundDrawablePadding(drawablePadding);
957
Gilles Debunnea3ae4a02010-12-13 17:22:39 -0800958 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -0800959 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -0800960 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -0800961 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -0800962
Gilles Debunne91a08cf2010-11-08 17:34:49 -0800963 if (singleLine && mInput == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800965 }
966
967 switch (ellipsize) {
968 case 1:
969 setEllipsize(TextUtils.TruncateAt.START);
970 break;
971 case 2:
972 setEllipsize(TextUtils.TruncateAt.MIDDLE);
973 break;
974 case 3:
975 setEllipsize(TextUtils.TruncateAt.END);
976 break;
977 case 4:
978 setHorizontalFadingEdgeEnabled(true);
979 setEllipsize(TextUtils.TruncateAt.MARQUEE);
980 break;
981 }
982
983 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
984 setHintTextColor(textColorHint);
985 setLinkTextColor(textColorLink);
986 if (textColorHighlight != 0) {
987 setHighlightColor(textColorHighlight);
988 }
989 setRawTextSize(textSize);
990
Ken Wakasa82d731a2010-12-24 23:42:41 +0900991 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992 setTransformationMethod(PasswordTransformationMethod.getInstance());
993 typefaceIndex = MONOSPACE;
Gilles Debunned7483bf2010-11-10 10:47:45 -0800994 } else if ((mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
995 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -0800996 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800997 }
998
999 setTypefaceByIndex(typefaceIndex, styleIndex);
1000
1001 if (shadowcolor != 0) {
1002 setShadowLayer(r, dx, dy, shadowcolor);
1003 }
1004
1005 if (maxlength >= 0) {
1006 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1007 } else {
1008 setFilters(NO_FILTERS);
1009 }
1010
1011 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001012 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001013
1014 /*
1015 * Views are not normally focusable unless specified to be.
1016 * However, TextViews that have input or movement methods *are*
1017 * focusable by default.
1018 */
1019 a = context.obtainStyledAttributes(attrs,
1020 com.android.internal.R.styleable.View,
1021 defStyle, 0);
1022
1023 boolean focusable = mMovement != null || mInput != null;
1024 boolean clickable = focusable;
1025 boolean longClickable = focusable;
1026
1027 n = a.getIndexCount();
1028 for (int i = 0; i < n; i++) {
1029 int attr = a.getIndex(i);
1030
1031 switch (attr) {
1032 case com.android.internal.R.styleable.View_focusable:
1033 focusable = a.getBoolean(attr, focusable);
1034 break;
1035
1036 case com.android.internal.R.styleable.View_clickable:
1037 clickable = a.getBoolean(attr, clickable);
1038 break;
1039
1040 case com.android.internal.R.styleable.View_longClickable:
1041 longClickable = a.getBoolean(attr, longClickable);
1042 break;
1043 }
1044 }
1045 a.recycle();
1046
1047 setFocusable(focusable);
1048 setClickable(clickable);
1049 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001050
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001051 prepareCursorControllers();
Gilles Debunneaa85a4c2010-12-06 18:27:17 -08001052
1053 final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
1054 final int touchSlop = viewConfiguration.getScaledTouchSlop();
1055 mSquaredTouchSlopDistance = touchSlop * touchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056 }
1057
1058 private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
1059 Typeface tf = null;
1060 switch (typefaceIndex) {
1061 case SANS:
1062 tf = Typeface.SANS_SERIF;
1063 break;
1064
1065 case SERIF:
1066 tf = Typeface.SERIF;
1067 break;
1068
1069 case MONOSPACE:
1070 tf = Typeface.MONOSPACE;
1071 break;
1072 }
1073
1074 setTypeface(tf, styleIndex);
1075 }
1076
Janos Levai042856c2010-10-15 02:53:58 +03001077 @Override
1078 public void setEnabled(boolean enabled) {
1079 if (enabled == isEnabled()) {
1080 return;
1081 }
1082
1083 if (!enabled) {
1084 // Hide the soft input if the currently active TextView is disabled
1085 InputMethodManager imm = InputMethodManager.peekInstance();
1086 if (imm != null && imm.isActive(this)) {
1087 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1088 }
1089 }
1090 super.setEnabled(enabled);
1091 }
1092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001093 /**
1094 * Sets the typeface and style in which the text should be displayed,
1095 * and turns on the fake bold and italic bits in the Paint if the
1096 * Typeface that you provided does not have all the bits in the
1097 * style that you specified.
1098 *
1099 * @attr ref android.R.styleable#TextView_typeface
1100 * @attr ref android.R.styleable#TextView_textStyle
1101 */
1102 public void setTypeface(Typeface tf, int style) {
1103 if (style > 0) {
1104 if (tf == null) {
1105 tf = Typeface.defaultFromStyle(style);
1106 } else {
1107 tf = Typeface.create(tf, style);
1108 }
1109
1110 setTypeface(tf);
1111 // now compute what (if any) algorithmic styling is needed
1112 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1113 int need = style & ~typefaceStyle;
1114 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1115 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1116 } else {
1117 mTextPaint.setFakeBoldText(false);
1118 mTextPaint.setTextSkewX(0);
1119 setTypeface(tf);
1120 }
1121 }
1122
1123 /**
1124 * Subclasses override this to specify that they have a KeyListener
1125 * by default even if not specifically called for in the XML options.
1126 */
1127 protected boolean getDefaultEditable() {
1128 return false;
1129 }
1130
1131 /**
1132 * Subclasses override this to specify a default movement method.
1133 */
1134 protected MovementMethod getDefaultMovementMethod() {
1135 return null;
1136 }
1137
1138 /**
1139 * Return the text the TextView is displaying. If setText() was called with
1140 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1141 * the return value from this method to Spannable or Editable, respectively.
1142 *
1143 * Note: The content of the return value should not be modified. If you want
1144 * a modifiable one, you should make your own copy first.
1145 */
1146 @ViewDebug.CapturedViewProperty
1147 public CharSequence getText() {
1148 return mText;
1149 }
1150
1151 /**
1152 * Returns the length, in characters, of the text managed by this TextView
1153 */
1154 public int length() {
1155 return mText.length();
1156 }
1157
1158 /**
1159 * Return the text the TextView is displaying as an Editable object. If
1160 * the text is not editable, null is returned.
1161 *
1162 * @see #getText
1163 */
1164 public Editable getEditableText() {
1165 return (mText instanceof Editable) ? (Editable)mText : null;
1166 }
1167
1168 /**
1169 * @return the height of one standard line in pixels. Note that markup
1170 * within the text can cause individual lines to be taller or shorter
1171 * than this height, and the layout may contain additional first-
1172 * or last-line padding.
1173 */
1174 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001175 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 }
1177
1178 /**
1179 * @return the Layout that is currently being used to display the text.
1180 * This can be null if the text or width has recently changes.
1181 */
1182 public final Layout getLayout() {
1183 return mLayout;
1184 }
1185
1186 /**
1187 * @return the current key listener for this TextView.
1188 * This will frequently be null for non-EditText TextViews.
1189 */
1190 public final KeyListener getKeyListener() {
1191 return mInput;
1192 }
1193
1194 /**
1195 * Sets the key listener to be used with this TextView. This can be null
1196 * to disallow user input. Note that this method has significant and
1197 * subtle interactions with soft keyboards and other input method:
1198 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1199 * for important details. Calling this method will replace the current
1200 * content type of the text view with the content type returned by the
1201 * key listener.
1202 * <p>
1203 * Be warned that if you want a TextView with a key listener or movement
1204 * method not to be focusable, or if you want a TextView without a
1205 * key listener or movement method to be focusable, you must call
1206 * {@link #setFocusable} again after calling this to get the focusability
1207 * back the way you want it.
1208 *
1209 * @attr ref android.R.styleable#TextView_numeric
1210 * @attr ref android.R.styleable#TextView_digits
1211 * @attr ref android.R.styleable#TextView_phoneNumber
1212 * @attr ref android.R.styleable#TextView_inputMethod
1213 * @attr ref android.R.styleable#TextView_capitalize
1214 * @attr ref android.R.styleable#TextView_autoText
1215 */
1216 public void setKeyListener(KeyListener input) {
1217 setKeyListenerOnly(input);
1218 fixFocusableAndClickableSettings();
1219
1220 if (input != null) {
1221 try {
1222 mInputType = mInput.getInputType();
1223 } catch (IncompatibleClassChangeError e) {
1224 mInputType = EditorInfo.TYPE_CLASS_TEXT;
1225 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001226 // Change inputType, without affecting transformation.
1227 // No need to applySingleLine since mSingleLine is unchanged.
1228 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229 } else {
1230 mInputType = EditorInfo.TYPE_NULL;
1231 }
1232
1233 InputMethodManager imm = InputMethodManager.peekInstance();
1234 if (imm != null) imm.restartInput(this);
1235 }
1236
1237 private void setKeyListenerOnly(KeyListener input) {
1238 mInput = input;
1239 if (mInput != null && !(mText instanceof Editable))
1240 setText(mText);
1241
1242 setFilters((Editable) mText, mFilters);
1243 }
1244
1245 /**
1246 * @return the movement method being used for this TextView.
1247 * This will frequently be null for non-EditText TextViews.
1248 */
1249 public final MovementMethod getMovementMethod() {
1250 return mMovement;
1251 }
1252
1253 /**
1254 * Sets the movement method (arrow key handler) to be used for
1255 * this TextView. This can be null to disallow using the arrow keys
1256 * to move the cursor or scroll the view.
1257 * <p>
1258 * Be warned that if you want a TextView with a key listener or movement
1259 * method not to be focusable, or if you want a TextView without a
1260 * key listener or movement method to be focusable, you must call
1261 * {@link #setFocusable} again after calling this to get the focusability
1262 * back the way you want it.
1263 */
1264 public final void setMovementMethod(MovementMethod movement) {
1265 mMovement = movement;
1266
1267 if (mMovement != null && !(mText instanceof Spannable))
1268 setText(mText);
1269
1270 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001271
Gilles Debunnebaaace52010-10-01 15:47:13 -07001272 // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001273 prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 }
1275
1276 private void fixFocusableAndClickableSettings() {
1277 if ((mMovement != null) || mInput != null) {
1278 setFocusable(true);
1279 setClickable(true);
1280 setLongClickable(true);
1281 } else {
1282 setFocusable(false);
1283 setClickable(false);
1284 setLongClickable(false);
1285 }
1286 }
1287
1288 /**
1289 * @return the current transformation method for this TextView.
1290 * This will frequently be null except for single-line and password
1291 * fields.
1292 */
1293 public final TransformationMethod getTransformationMethod() {
1294 return mTransformation;
1295 }
1296
1297 /**
1298 * Sets the transformation that is applied to the text that this
1299 * TextView is displaying.
1300 *
1301 * @attr ref android.R.styleable#TextView_password
1302 * @attr ref android.R.styleable#TextView_singleLine
1303 */
1304 public final void setTransformationMethod(TransformationMethod method) {
1305 if (method == mTransformation) {
1306 // Avoid the setText() below if the transformation is
1307 // the same.
1308 return;
1309 }
1310 if (mTransformation != null) {
1311 if (mText instanceof Spannable) {
1312 ((Spannable) mText).removeSpan(mTransformation);
1313 }
1314 }
1315
1316 mTransformation = method;
1317
1318 setText(mText);
1319 }
1320
1321 /**
1322 * Returns the top padding of the view, plus space for the top
1323 * Drawable if any.
1324 */
1325 public int getCompoundPaddingTop() {
1326 final Drawables dr = mDrawables;
1327 if (dr == null || dr.mDrawableTop == null) {
1328 return mPaddingTop;
1329 } else {
1330 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1331 }
1332 }
1333
1334 /**
1335 * Returns the bottom padding of the view, plus space for the bottom
1336 * Drawable if any.
1337 */
1338 public int getCompoundPaddingBottom() {
1339 final Drawables dr = mDrawables;
1340 if (dr == null || dr.mDrawableBottom == null) {
1341 return mPaddingBottom;
1342 } else {
1343 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1344 }
1345 }
1346
1347 /**
1348 * Returns the left padding of the view, plus space for the left
1349 * Drawable if any.
1350 */
1351 public int getCompoundPaddingLeft() {
1352 final Drawables dr = mDrawables;
1353 if (dr == null || dr.mDrawableLeft == null) {
1354 return mPaddingLeft;
1355 } else {
1356 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1357 }
1358 }
1359
1360 /**
1361 * Returns the right padding of the view, plus space for the right
1362 * Drawable if any.
1363 */
1364 public int getCompoundPaddingRight() {
1365 final Drawables dr = mDrawables;
1366 if (dr == null || dr.mDrawableRight == null) {
1367 return mPaddingRight;
1368 } else {
1369 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1370 }
1371 }
1372
1373 /**
1374 * Returns the extended top padding of the view, including both the
1375 * top Drawable if any and any extra space to keep more than maxLines
1376 * of text from showing. It is only valid to call this after measuring.
1377 */
1378 public int getExtendedPaddingTop() {
1379 if (mMaxMode != LINES) {
1380 return getCompoundPaddingTop();
1381 }
1382
1383 if (mLayout.getLineCount() <= mMaximum) {
1384 return getCompoundPaddingTop();
1385 }
1386
1387 int top = getCompoundPaddingTop();
1388 int bottom = getCompoundPaddingBottom();
1389 int viewht = getHeight() - top - bottom;
1390 int layoutht = mLayout.getLineTop(mMaximum);
1391
1392 if (layoutht >= viewht) {
1393 return top;
1394 }
1395
1396 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1397 if (gravity == Gravity.TOP) {
1398 return top;
1399 } else if (gravity == Gravity.BOTTOM) {
1400 return top + viewht - layoutht;
1401 } else { // (gravity == Gravity.CENTER_VERTICAL)
1402 return top + (viewht - layoutht) / 2;
1403 }
1404 }
1405
1406 /**
1407 * Returns the extended bottom padding of the view, including both the
1408 * bottom Drawable if any and any extra space to keep more than maxLines
1409 * of text from showing. It is only valid to call this after measuring.
1410 */
1411 public int getExtendedPaddingBottom() {
1412 if (mMaxMode != LINES) {
1413 return getCompoundPaddingBottom();
1414 }
1415
1416 if (mLayout.getLineCount() <= mMaximum) {
1417 return getCompoundPaddingBottom();
1418 }
1419
1420 int top = getCompoundPaddingTop();
1421 int bottom = getCompoundPaddingBottom();
1422 int viewht = getHeight() - top - bottom;
1423 int layoutht = mLayout.getLineTop(mMaximum);
1424
1425 if (layoutht >= viewht) {
1426 return bottom;
1427 }
1428
1429 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1430 if (gravity == Gravity.TOP) {
1431 return bottom + viewht - layoutht;
1432 } else if (gravity == Gravity.BOTTOM) {
1433 return bottom;
1434 } else { // (gravity == Gravity.CENTER_VERTICAL)
1435 return bottom + (viewht - layoutht) / 2;
1436 }
1437 }
1438
1439 /**
1440 * Returns the total left padding of the view, including the left
1441 * Drawable if any.
1442 */
1443 public int getTotalPaddingLeft() {
1444 return getCompoundPaddingLeft();
1445 }
1446
1447 /**
1448 * Returns the total right padding of the view, including the right
1449 * Drawable if any.
1450 */
1451 public int getTotalPaddingRight() {
1452 return getCompoundPaddingRight();
1453 }
1454
1455 /**
1456 * Returns the total top padding of the view, including the top
1457 * Drawable if any, the extra space to keep more than maxLines
1458 * from showing, and the vertical offset for gravity, if any.
1459 */
1460 public int getTotalPaddingTop() {
1461 return getExtendedPaddingTop() + getVerticalOffset(true);
1462 }
1463
1464 /**
1465 * Returns the total bottom padding of the view, including the bottom
1466 * Drawable if any, the extra space to keep more than maxLines
1467 * from showing, and the vertical offset for gravity, if any.
1468 */
1469 public int getTotalPaddingBottom() {
1470 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1471 }
1472
1473 /**
1474 * Sets the Drawables (if any) to appear to the left of, above,
1475 * to the right of, and below the text. Use null if you do not
1476 * want a Drawable there. The Drawables must already have had
1477 * {@link Drawable#setBounds} called.
1478 *
1479 * @attr ref android.R.styleable#TextView_drawableLeft
1480 * @attr ref android.R.styleable#TextView_drawableTop
1481 * @attr ref android.R.styleable#TextView_drawableRight
1482 * @attr ref android.R.styleable#TextView_drawableBottom
1483 */
1484 public void setCompoundDrawables(Drawable left, Drawable top,
1485 Drawable right, Drawable bottom) {
1486 Drawables dr = mDrawables;
1487
1488 final boolean drawables = left != null || top != null
1489 || right != null || bottom != null;
1490
1491 if (!drawables) {
1492 // Clearing drawables... can we free the data structure?
1493 if (dr != null) {
1494 if (dr.mDrawablePadding == 0) {
1495 mDrawables = null;
1496 } else {
1497 // We need to retain the last set padding, so just clear
1498 // out all of the fields in the existing structure.
Romain Guy48540eb2009-05-19 16:44:57 -07001499 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 dr.mDrawableLeft = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001501 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001502 dr.mDrawableTop = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001503 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001504 dr.mDrawableRight = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001505 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001506 dr.mDrawableBottom = null;
1507 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1508 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1509 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1510 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1511 }
1512 }
1513 } else {
1514 if (dr == null) {
1515 mDrawables = dr = new Drawables();
1516 }
1517
Romain Guy48540eb2009-05-19 16:44:57 -07001518 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1519 dr.mDrawableLeft.setCallback(null);
1520 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521 dr.mDrawableLeft = left;
Romain Guy8e618e52010-03-08 12:18:20 -08001522
1523 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001524 dr.mDrawableTop.setCallback(null);
1525 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001526 dr.mDrawableTop = top;
Romain Guy8e618e52010-03-08 12:18:20 -08001527
1528 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001529 dr.mDrawableRight.setCallback(null);
1530 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001531 dr.mDrawableRight = right;
Romain Guy8e618e52010-03-08 12:18:20 -08001532
1533 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001534 dr.mDrawableBottom.setCallback(null);
1535 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001536 dr.mDrawableBottom = bottom;
1537
1538 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07001539 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001540
1541 state = getDrawableState();
1542
1543 if (left != null) {
1544 left.setState(state);
1545 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001546 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001547 dr.mDrawableSizeLeft = compoundRect.width();
1548 dr.mDrawableHeightLeft = compoundRect.height();
1549 } else {
1550 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1551 }
1552
1553 if (right != null) {
1554 right.setState(state);
1555 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001556 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001557 dr.mDrawableSizeRight = compoundRect.width();
1558 dr.mDrawableHeightRight = compoundRect.height();
1559 } else {
1560 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1561 }
1562
1563 if (top != null) {
1564 top.setState(state);
1565 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001566 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001567 dr.mDrawableSizeTop = compoundRect.height();
1568 dr.mDrawableWidthTop = compoundRect.width();
1569 } else {
1570 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1571 }
1572
1573 if (bottom != null) {
1574 bottom.setState(state);
1575 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07001576 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001577 dr.mDrawableSizeBottom = compoundRect.height();
1578 dr.mDrawableWidthBottom = compoundRect.width();
1579 } else {
1580 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1581 }
1582 }
1583
1584 invalidate();
1585 requestLayout();
1586 }
1587
1588 /**
1589 * Sets the Drawables (if any) to appear to the left of, above,
1590 * to the right of, and below the text. Use 0 if you do not
1591 * want a Drawable there. The Drawables' bounds will be set to
1592 * their intrinsic bounds.
1593 *
1594 * @param left Resource identifier of the left Drawable.
1595 * @param top Resource identifier of the top Drawable.
1596 * @param right Resource identifier of the right Drawable.
1597 * @param bottom Resource identifier of the bottom Drawable.
1598 *
1599 * @attr ref android.R.styleable#TextView_drawableLeft
1600 * @attr ref android.R.styleable#TextView_drawableTop
1601 * @attr ref android.R.styleable#TextView_drawableRight
1602 * @attr ref android.R.styleable#TextView_drawableBottom
1603 */
1604 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
1605 final Resources resources = getContext().getResources();
1606 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
1607 top != 0 ? resources.getDrawable(top) : null,
1608 right != 0 ? resources.getDrawable(right) : null,
1609 bottom != 0 ? resources.getDrawable(bottom) : null);
1610 }
1611
1612 /**
1613 * Sets the Drawables (if any) to appear to the left of, above,
1614 * to the right of, and below the text. Use null if you do not
1615 * want a Drawable there. The Drawables' bounds will be set to
1616 * their intrinsic bounds.
1617 *
1618 * @attr ref android.R.styleable#TextView_drawableLeft
1619 * @attr ref android.R.styleable#TextView_drawableTop
1620 * @attr ref android.R.styleable#TextView_drawableRight
1621 * @attr ref android.R.styleable#TextView_drawableBottom
1622 */
1623 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
1624 Drawable right, Drawable bottom) {
1625
1626 if (left != null) {
1627 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
1628 }
1629 if (right != null) {
1630 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
1631 }
1632 if (top != null) {
1633 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1634 }
1635 if (bottom != null) {
1636 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1637 }
1638 setCompoundDrawables(left, top, right, bottom);
1639 }
1640
1641 /**
1642 * Returns drawables for the left, top, right, and bottom borders.
1643 */
1644 public Drawable[] getCompoundDrawables() {
1645 final Drawables dr = mDrawables;
1646 if (dr != null) {
1647 return new Drawable[] {
1648 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
1649 };
1650 } else {
1651 return new Drawable[] { null, null, null, null };
1652 }
1653 }
1654
1655 /**
1656 * Sets the size of the padding between the compound drawables and
1657 * the text.
1658 *
1659 * @attr ref android.R.styleable#TextView_drawablePadding
1660 */
1661 public void setCompoundDrawablePadding(int pad) {
1662 Drawables dr = mDrawables;
1663 if (pad == 0) {
1664 if (dr != null) {
1665 dr.mDrawablePadding = pad;
1666 }
1667 } else {
1668 if (dr == null) {
1669 mDrawables = dr = new Drawables();
1670 }
1671 dr.mDrawablePadding = pad;
1672 }
1673
1674 invalidate();
1675 requestLayout();
1676 }
1677
1678 /**
1679 * Returns the padding between the compound drawables and the text.
1680 */
1681 public int getCompoundDrawablePadding() {
1682 final Drawables dr = mDrawables;
1683 return dr != null ? dr.mDrawablePadding : 0;
1684 }
1685
1686 @Override
1687 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001688 if (left != mPaddingLeft ||
1689 right != mPaddingRight ||
1690 top != mPaddingTop ||
1691 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001692 nullLayouts();
1693 }
1694
1695 // the super call will requestLayout()
1696 super.setPadding(left, top, right, bottom);
1697 invalidate();
1698 }
1699
1700 /**
1701 * Gets the autolink mask of the text. See {@link
1702 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1703 * possible values.
1704 *
1705 * @attr ref android.R.styleable#TextView_autoLink
1706 */
1707 public final int getAutoLinkMask() {
1708 return mAutoLinkMask;
1709 }
1710
1711 /**
1712 * Sets the text color, size, style, hint color, and highlight color
1713 * from the specified TextAppearance resource.
1714 */
1715 public void setTextAppearance(Context context, int resid) {
1716 TypedArray appearance =
1717 context.obtainStyledAttributes(resid,
1718 com.android.internal.R.styleable.TextAppearance);
1719
1720 int color;
1721 ColorStateList colors;
1722 int ts;
1723
1724 color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
1725 if (color != 0) {
1726 setHighlightColor(color);
1727 }
1728
1729 colors = appearance.getColorStateList(com.android.internal.R.styleable.
1730 TextAppearance_textColor);
1731 if (colors != null) {
1732 setTextColor(colors);
1733 }
1734
1735 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
1736 TextAppearance_textSize, 0);
1737 if (ts != 0) {
1738 setRawTextSize(ts);
1739 }
1740
1741 colors = appearance.getColorStateList(com.android.internal.R.styleable.
1742 TextAppearance_textColorHint);
1743 if (colors != null) {
1744 setHintTextColor(colors);
1745 }
1746
1747 colors = appearance.getColorStateList(com.android.internal.R.styleable.
1748 TextAppearance_textColorLink);
1749 if (colors != null) {
1750 setLinkTextColor(colors);
1751 }
1752
1753 int typefaceIndex, styleIndex;
1754
1755 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
1756 TextAppearance_typeface, -1);
1757 styleIndex = appearance.getInt(com.android.internal.R.styleable.
1758 TextAppearance_textStyle, -1);
1759
1760 setTypefaceByIndex(typefaceIndex, styleIndex);
Adam Powellbe4d68e2010-10-08 18:16:34 -07001761
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001762 appearance.recycle();
1763 }
1764
1765 /**
1766 * @return the size (in pixels) of the default text size in this TextView.
1767 */
1768 public float getTextSize() {
1769 return mTextPaint.getTextSize();
1770 }
1771
1772 /**
1773 * Set the default text size to the given value, interpreted as "scaled
1774 * pixel" units. This size is adjusted based on the current density and
1775 * user font size preference.
1776 *
1777 * @param size The scaled pixel size.
1778 *
1779 * @attr ref android.R.styleable#TextView_textSize
1780 */
1781 @android.view.RemotableViewMethod
1782 public void setTextSize(float size) {
1783 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
1784 }
1785
1786 /**
1787 * Set the default text size to a given unit and value. See {@link
1788 * TypedValue} for the possible dimension units.
1789 *
1790 * @param unit The desired dimension unit.
1791 * @param size The desired size in the given units.
1792 *
1793 * @attr ref android.R.styleable#TextView_textSize
1794 */
1795 public void setTextSize(int unit, float size) {
1796 Context c = getContext();
1797 Resources r;
1798
1799 if (c == null)
1800 r = Resources.getSystem();
1801 else
1802 r = c.getResources();
1803
1804 setRawTextSize(TypedValue.applyDimension(
1805 unit, size, r.getDisplayMetrics()));
1806 }
1807
1808 private void setRawTextSize(float size) {
1809 if (size != mTextPaint.getTextSize()) {
1810 mTextPaint.setTextSize(size);
1811
1812 if (mLayout != null) {
1813 nullLayouts();
1814 requestLayout();
1815 invalidate();
1816 }
1817 }
1818 }
1819
1820 /**
1821 * @return the extent by which text is currently being stretched
1822 * horizontally. This will usually be 1.
1823 */
1824 public float getTextScaleX() {
1825 return mTextPaint.getTextScaleX();
1826 }
1827
1828 /**
1829 * Sets the extent by which text should be stretched horizontally.
1830 *
1831 * @attr ref android.R.styleable#TextView_textScaleX
1832 */
1833 @android.view.RemotableViewMethod
1834 public void setTextScaleX(float size) {
1835 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07001836 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001837 mTextPaint.setTextScaleX(size);
1838
1839 if (mLayout != null) {
1840 nullLayouts();
1841 requestLayout();
1842 invalidate();
1843 }
1844 }
1845 }
1846
1847 /**
1848 * Sets the typeface and style in which the text should be displayed.
1849 * Note that not all Typeface families actually have bold and italic
1850 * variants, so you may need to use
1851 * {@link #setTypeface(Typeface, int)} to get the appearance
1852 * that you actually want.
1853 *
1854 * @attr ref android.R.styleable#TextView_typeface
1855 * @attr ref android.R.styleable#TextView_textStyle
1856 */
1857 public void setTypeface(Typeface tf) {
1858 if (mTextPaint.getTypeface() != tf) {
1859 mTextPaint.setTypeface(tf);
1860
1861 if (mLayout != null) {
1862 nullLayouts();
1863 requestLayout();
1864 invalidate();
1865 }
1866 }
1867 }
1868
1869 /**
1870 * @return the current typeface and style in which the text is being
1871 * displayed.
1872 */
1873 public Typeface getTypeface() {
1874 return mTextPaint.getTypeface();
1875 }
1876
1877 /**
1878 * Sets the text color for all the states (normal, selected,
1879 * focused) to be this color.
1880 *
1881 * @attr ref android.R.styleable#TextView_textColor
1882 */
1883 @android.view.RemotableViewMethod
1884 public void setTextColor(int color) {
1885 mTextColor = ColorStateList.valueOf(color);
1886 updateTextColors();
1887 }
1888
1889 /**
1890 * Sets the text color.
1891 *
1892 * @attr ref android.R.styleable#TextView_textColor
1893 */
1894 public void setTextColor(ColorStateList colors) {
1895 if (colors == null) {
1896 throw new NullPointerException();
1897 }
1898
1899 mTextColor = colors;
1900 updateTextColors();
1901 }
1902
1903 /**
1904 * Return the set of text colors.
1905 *
1906 * @return Returns the set of text colors.
1907 */
1908 public final ColorStateList getTextColors() {
1909 return mTextColor;
1910 }
1911
1912 /**
1913 * <p>Return the current color selected for normal text.</p>
1914 *
1915 * @return Returns the current text color.
1916 */
1917 public final int getCurrentTextColor() {
1918 return mCurTextColor;
1919 }
1920
1921 /**
1922 * Sets the color used to display the selection highlight.
1923 *
1924 * @attr ref android.R.styleable#TextView_textColorHighlight
1925 */
1926 @android.view.RemotableViewMethod
1927 public void setHighlightColor(int color) {
1928 if (mHighlightColor != color) {
1929 mHighlightColor = color;
1930 invalidate();
1931 }
1932 }
1933
1934 /**
1935 * Gives the text a shadow of the specified radius and color, the specified
1936 * distance from its normal position.
1937 *
1938 * @attr ref android.R.styleable#TextView_shadowColor
1939 * @attr ref android.R.styleable#TextView_shadowDx
1940 * @attr ref android.R.styleable#TextView_shadowDy
1941 * @attr ref android.R.styleable#TextView_shadowRadius
1942 */
1943 public void setShadowLayer(float radius, float dx, float dy, int color) {
1944 mTextPaint.setShadowLayer(radius, dx, dy, color);
1945
1946 mShadowRadius = radius;
1947 mShadowDx = dx;
1948 mShadowDy = dy;
1949
1950 invalidate();
1951 }
1952
1953 /**
1954 * @return the base paint used for the text. Please use this only to
1955 * consult the Paint's properties and not to change them.
1956 */
1957 public TextPaint getPaint() {
1958 return mTextPaint;
1959 }
1960
1961 /**
1962 * Sets the autolink mask of the text. See {@link
1963 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1964 * possible values.
1965 *
1966 * @attr ref android.R.styleable#TextView_autoLink
1967 */
1968 @android.view.RemotableViewMethod
1969 public final void setAutoLinkMask(int mask) {
1970 mAutoLinkMask = mask;
1971 }
1972
1973 /**
1974 * Sets whether the movement method will automatically be set to
1975 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1976 * set to nonzero and links are detected in {@link #setText}.
1977 * The default is true.
1978 *
1979 * @attr ref android.R.styleable#TextView_linksClickable
1980 */
1981 @android.view.RemotableViewMethod
1982 public final void setLinksClickable(boolean whether) {
1983 mLinksClickable = whether;
1984 }
1985
1986 /**
1987 * Returns whether the movement method will automatically be set to
1988 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1989 * set to nonzero and links are detected in {@link #setText}.
1990 * The default is true.
1991 *
1992 * @attr ref android.R.styleable#TextView_linksClickable
1993 */
1994 public final boolean getLinksClickable() {
1995 return mLinksClickable;
1996 }
1997
1998 /**
1999 * Returns the list of URLSpans attached to the text
2000 * (by {@link Linkify} or otherwise) if any. You can call
2001 * {@link URLSpan#getURL} on them to find where they link to
2002 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2003 * to find the region of the text they are attached to.
2004 */
2005 public URLSpan[] getUrls() {
2006 if (mText instanceof Spanned) {
2007 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2008 } else {
2009 return new URLSpan[0];
2010 }
2011 }
2012
2013 /**
2014 * Sets the color of the hint text.
2015 *
2016 * @attr ref android.R.styleable#TextView_textColorHint
2017 */
2018 @android.view.RemotableViewMethod
2019 public final void setHintTextColor(int color) {
2020 mHintTextColor = ColorStateList.valueOf(color);
2021 updateTextColors();
2022 }
2023
2024 /**
2025 * Sets the color of the hint text.
2026 *
2027 * @attr ref android.R.styleable#TextView_textColorHint
2028 */
2029 public final void setHintTextColor(ColorStateList colors) {
2030 mHintTextColor = colors;
2031 updateTextColors();
2032 }
2033
2034 /**
2035 * <p>Return the color used to paint the hint text.</p>
2036 *
2037 * @return Returns the list of hint text colors.
2038 */
2039 public final ColorStateList getHintTextColors() {
2040 return mHintTextColor;
2041 }
2042
2043 /**
2044 * <p>Return the current color selected to paint the hint text.</p>
2045 *
2046 * @return Returns the current hint text color.
2047 */
2048 public final int getCurrentHintTextColor() {
2049 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2050 }
2051
2052 /**
2053 * Sets the color of links in the text.
2054 *
2055 * @attr ref android.R.styleable#TextView_textColorLink
2056 */
2057 @android.view.RemotableViewMethod
2058 public final void setLinkTextColor(int color) {
2059 mLinkTextColor = ColorStateList.valueOf(color);
2060 updateTextColors();
2061 }
2062
2063 /**
2064 * Sets the color of links in the text.
2065 *
2066 * @attr ref android.R.styleable#TextView_textColorLink
2067 */
2068 public final void setLinkTextColor(ColorStateList colors) {
2069 mLinkTextColor = colors;
2070 updateTextColors();
2071 }
2072
2073 /**
2074 * <p>Returns the color used to paint links in the text.</p>
2075 *
2076 * @return Returns the list of link text colors.
2077 */
2078 public final ColorStateList getLinkTextColors() {
2079 return mLinkTextColor;
2080 }
2081
2082 /**
2083 * Sets the horizontal alignment of the text and the
2084 * vertical gravity that will be used when there is extra space
2085 * in the TextView beyond what is required for the text itself.
2086 *
2087 * @see android.view.Gravity
2088 * @attr ref android.R.styleable#TextView_gravity
2089 */
2090 public void setGravity(int gravity) {
2091 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
2092 gravity |= Gravity.LEFT;
2093 }
2094 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2095 gravity |= Gravity.TOP;
2096 }
2097
2098 boolean newLayout = false;
2099
2100 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
2101 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
2102 newLayout = true;
2103 }
2104
2105 if (gravity != mGravity) {
2106 invalidate();
2107 }
2108
2109 mGravity = gravity;
2110
2111 if (mLayout != null && newLayout) {
2112 // XXX this is heavy-handed because no actual content changes.
2113 int want = mLayout.getWidth();
2114 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2115
2116 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2117 mRight - mLeft - getCompoundPaddingLeft() -
2118 getCompoundPaddingRight(), true);
2119 }
2120 }
2121
2122 /**
2123 * Returns the horizontal and vertical alignment of this TextView.
2124 *
2125 * @see android.view.Gravity
2126 * @attr ref android.R.styleable#TextView_gravity
2127 */
2128 public int getGravity() {
2129 return mGravity;
2130 }
2131
2132 /**
2133 * @return the flags on the Paint being used to display the text.
2134 * @see Paint#getFlags
2135 */
2136 public int getPaintFlags() {
2137 return mTextPaint.getFlags();
2138 }
2139
2140 /**
2141 * Sets flags on the Paint being used to display the text and
2142 * reflows the text if they are different from the old flags.
2143 * @see Paint#setFlags
2144 */
2145 @android.view.RemotableViewMethod
2146 public void setPaintFlags(int flags) {
2147 if (mTextPaint.getFlags() != flags) {
2148 mTextPaint.setFlags(flags);
2149
2150 if (mLayout != null) {
2151 nullLayouts();
2152 requestLayout();
2153 invalidate();
2154 }
2155 }
2156 }
2157
2158 /**
2159 * Sets whether the text should be allowed to be wider than the
2160 * View is. If false, it will be wrapped to the width of the View.
2161 *
2162 * @attr ref android.R.styleable#TextView_scrollHorizontally
2163 */
2164 public void setHorizontallyScrolling(boolean whether) {
2165 mHorizontallyScrolling = whether;
2166
2167 if (mLayout != null) {
2168 nullLayouts();
2169 requestLayout();
2170 invalidate();
2171 }
2172 }
2173
2174 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002175 * Makes the TextView at least this many lines tall.
2176 *
2177 * Setting this value overrides any other (minimum) height setting. A single line TextView will
2178 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002179 *
2180 * @attr ref android.R.styleable#TextView_minLines
2181 */
2182 @android.view.RemotableViewMethod
2183 public void setMinLines(int minlines) {
2184 mMinimum = minlines;
2185 mMinMode = LINES;
2186
2187 requestLayout();
2188 invalidate();
2189 }
2190
2191 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002192 * Makes the TextView at least this many pixels tall.
2193 *
2194 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002195 *
2196 * @attr ref android.R.styleable#TextView_minHeight
2197 */
2198 @android.view.RemotableViewMethod
2199 public void setMinHeight(int minHeight) {
2200 mMinimum = minHeight;
2201 mMinMode = PIXELS;
2202
2203 requestLayout();
2204 invalidate();
2205 }
2206
2207 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002208 * Makes the TextView at most this many lines tall.
2209 *
2210 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002211 *
2212 * @attr ref android.R.styleable#TextView_maxLines
2213 */
2214 @android.view.RemotableViewMethod
2215 public void setMaxLines(int maxlines) {
2216 mMaximum = maxlines;
2217 mMaxMode = LINES;
2218
2219 requestLayout();
2220 invalidate();
2221 }
2222
2223 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002224 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
2225 * {@link #setMaxLines(int)} method.
2226 *
2227 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002228 *
2229 * @attr ref android.R.styleable#TextView_maxHeight
2230 */
2231 @android.view.RemotableViewMethod
2232 public void setMaxHeight(int maxHeight) {
2233 mMaximum = maxHeight;
2234 mMaxMode = PIXELS;
2235
2236 requestLayout();
2237 invalidate();
2238 }
2239
2240 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002241 * Makes the TextView exactly this many lines tall.
2242 *
2243 * Note that setting this value overrides any other (minimum / maximum) number of lines or
2244 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002245 *
2246 * @attr ref android.R.styleable#TextView_lines
2247 */
2248 @android.view.RemotableViewMethod
2249 public void setLines(int lines) {
2250 mMaximum = mMinimum = lines;
2251 mMaxMode = mMinMode = LINES;
2252
2253 requestLayout();
2254 invalidate();
2255 }
2256
2257 /**
2258 * Makes the TextView exactly this many pixels tall.
2259 * You could do the same thing by specifying this number in the
2260 * LayoutParams.
2261 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08002262 * Note that setting this value overrides any other (minimum / maximum) number of lines or
2263 * height setting.
2264 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002265 * @attr ref android.R.styleable#TextView_height
2266 */
2267 @android.view.RemotableViewMethod
2268 public void setHeight(int pixels) {
2269 mMaximum = mMinimum = pixels;
2270 mMaxMode = mMinMode = PIXELS;
2271
2272 requestLayout();
2273 invalidate();
2274 }
2275
2276 /**
2277 * Makes the TextView at least this many ems wide
2278 *
2279 * @attr ref android.R.styleable#TextView_minEms
2280 */
2281 @android.view.RemotableViewMethod
2282 public void setMinEms(int minems) {
2283 mMinWidth = minems;
2284 mMinWidthMode = EMS;
2285
2286 requestLayout();
2287 invalidate();
2288 }
2289
2290 /**
2291 * Makes the TextView at least this many pixels wide
2292 *
2293 * @attr ref android.R.styleable#TextView_minWidth
2294 */
2295 @android.view.RemotableViewMethod
2296 public void setMinWidth(int minpixels) {
2297 mMinWidth = minpixels;
2298 mMinWidthMode = PIXELS;
2299
2300 requestLayout();
2301 invalidate();
2302 }
2303
2304 /**
2305 * Makes the TextView at most this many ems wide
2306 *
2307 * @attr ref android.R.styleable#TextView_maxEms
2308 */
2309 @android.view.RemotableViewMethod
2310 public void setMaxEms(int maxems) {
2311 mMaxWidth = maxems;
2312 mMaxWidthMode = EMS;
2313
2314 requestLayout();
2315 invalidate();
2316 }
2317
2318 /**
2319 * Makes the TextView at most this many pixels wide
2320 *
2321 * @attr ref android.R.styleable#TextView_maxWidth
2322 */
2323 @android.view.RemotableViewMethod
2324 public void setMaxWidth(int maxpixels) {
2325 mMaxWidth = maxpixels;
2326 mMaxWidthMode = PIXELS;
2327
2328 requestLayout();
2329 invalidate();
2330 }
2331
2332 /**
2333 * Makes the TextView exactly this many ems wide
2334 *
2335 * @attr ref android.R.styleable#TextView_ems
2336 */
2337 @android.view.RemotableViewMethod
2338 public void setEms(int ems) {
2339 mMaxWidth = mMinWidth = ems;
2340 mMaxWidthMode = mMinWidthMode = EMS;
2341
2342 requestLayout();
2343 invalidate();
2344 }
2345
2346 /**
2347 * Makes the TextView exactly this many pixels wide.
2348 * You could do the same thing by specifying this number in the
2349 * LayoutParams.
2350 *
2351 * @attr ref android.R.styleable#TextView_width
2352 */
2353 @android.view.RemotableViewMethod
2354 public void setWidth(int pixels) {
2355 mMaxWidth = mMinWidth = pixels;
2356 mMaxWidthMode = mMinWidthMode = PIXELS;
2357
2358 requestLayout();
2359 invalidate();
2360 }
2361
2362
2363 /**
2364 * Sets line spacing for this TextView. Each line will have its height
2365 * multiplied by <code>mult</code> and have <code>add</code> added to it.
2366 *
2367 * @attr ref android.R.styleable#TextView_lineSpacingExtra
2368 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
2369 */
2370 public void setLineSpacing(float add, float mult) {
2371 mSpacingMult = mult;
2372 mSpacingAdd = add;
2373
2374 if (mLayout != null) {
2375 nullLayouts();
2376 requestLayout();
2377 invalidate();
2378 }
2379 }
2380
2381 /**
2382 * Convenience method: Append the specified text to the TextView's
2383 * display buffer, upgrading it to BufferType.EDITABLE if it was
2384 * not already editable.
2385 */
2386 public final void append(CharSequence text) {
2387 append(text, 0, text.length());
2388 }
2389
2390 /**
2391 * Convenience method: Append the specified text slice to the TextView's
2392 * display buffer, upgrading it to BufferType.EDITABLE if it was
2393 * not already editable.
2394 */
2395 public void append(CharSequence text, int start, int end) {
2396 if (!(mText instanceof Editable)) {
2397 setText(mText, BufferType.EDITABLE);
2398 }
2399
2400 ((Editable) mText).append(text, start, end);
2401 }
2402
2403 private void updateTextColors() {
2404 boolean inval = false;
2405 int color = mTextColor.getColorForState(getDrawableState(), 0);
2406 if (color != mCurTextColor) {
2407 mCurTextColor = color;
2408 inval = true;
2409 }
2410 if (mLinkTextColor != null) {
2411 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
2412 if (color != mTextPaint.linkColor) {
2413 mTextPaint.linkColor = color;
2414 inval = true;
2415 }
2416 }
2417 if (mHintTextColor != null) {
2418 color = mHintTextColor.getColorForState(getDrawableState(), 0);
2419 if (color != mCurHintTextColor && mText.length() == 0) {
2420 mCurHintTextColor = color;
2421 inval = true;
2422 }
2423 }
2424 if (inval) {
2425 invalidate();
2426 }
2427 }
2428
2429 @Override
2430 protected void drawableStateChanged() {
2431 super.drawableStateChanged();
2432 if (mTextColor != null && mTextColor.isStateful()
2433 || (mHintTextColor != null && mHintTextColor.isStateful())
2434 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
2435 updateTextColors();
2436 }
2437
2438 final Drawables dr = mDrawables;
2439 if (dr != null) {
2440 int[] state = getDrawableState();
2441 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
2442 dr.mDrawableTop.setState(state);
2443 }
2444 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
2445 dr.mDrawableBottom.setState(state);
2446 }
2447 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
2448 dr.mDrawableLeft.setState(state);
2449 }
2450 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
2451 dr.mDrawableRight.setState(state);
2452 }
2453 }
2454 }
2455
2456 /**
2457 * User interface state that is stored by TextView for implementing
2458 * {@link View#onSaveInstanceState}.
2459 */
2460 public static class SavedState extends BaseSavedState {
2461 int selStart;
2462 int selEnd;
2463 CharSequence text;
2464 boolean frozenWithFocus;
The Android Open Source Project4df24232009-03-05 14:34:35 -08002465 CharSequence error;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002466
2467 SavedState(Parcelable superState) {
2468 super(superState);
2469 }
2470
2471 @Override
2472 public void writeToParcel(Parcel out, int flags) {
2473 super.writeToParcel(out, flags);
2474 out.writeInt(selStart);
2475 out.writeInt(selEnd);
2476 out.writeInt(frozenWithFocus ? 1 : 0);
2477 TextUtils.writeToParcel(text, out, flags);
The Android Open Source Project4df24232009-03-05 14:34:35 -08002478
2479 if (error == null) {
2480 out.writeInt(0);
2481 } else {
2482 out.writeInt(1);
2483 TextUtils.writeToParcel(error, out, flags);
2484 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002485 }
2486
2487 @Override
2488 public String toString() {
2489 String str = "TextView.SavedState{"
2490 + Integer.toHexString(System.identityHashCode(this))
2491 + " start=" + selStart + " end=" + selEnd;
2492 if (text != null) {
2493 str += " text=" + text;
2494 }
2495 return str + "}";
2496 }
2497
Gilles Debunnee15b3582010-06-16 15:17:21 -07002498 @SuppressWarnings("hiding")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002499 public static final Parcelable.Creator<SavedState> CREATOR
2500 = new Parcelable.Creator<SavedState>() {
2501 public SavedState createFromParcel(Parcel in) {
2502 return new SavedState(in);
2503 }
2504
2505 public SavedState[] newArray(int size) {
2506 return new SavedState[size];
2507 }
2508 };
2509
2510 private SavedState(Parcel in) {
2511 super(in);
2512 selStart = in.readInt();
2513 selEnd = in.readInt();
2514 frozenWithFocus = (in.readInt() != 0);
2515 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
The Android Open Source Project4df24232009-03-05 14:34:35 -08002516
2517 if (in.readInt() != 0) {
2518 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2519 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002520 }
2521 }
2522
2523 @Override
2524 public Parcelable onSaveInstanceState() {
2525 Parcelable superState = super.onSaveInstanceState();
2526
2527 // Save state if we are forced to
2528 boolean save = mFreezesText;
2529 int start = 0;
2530 int end = 0;
2531
2532 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07002533 start = getSelectionStart();
2534 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002535 if (start >= 0 || end >= 0) {
2536 // Or save state if there is a selection
2537 save = true;
2538 }
2539 }
2540
2541 if (save) {
2542 SavedState ss = new SavedState(superState);
2543 // XXX Should also save the current scroll position!
2544 ss.selStart = start;
2545 ss.selEnd = end;
2546
2547 if (mText instanceof Spanned) {
2548 /*
2549 * Calling setText() strips off any ChangeWatchers;
2550 * strip them now to avoid leaking references.
2551 * But do it to a copy so that if there are any
2552 * further changes to the text of this view, it
2553 * won't get into an inconsistent state.
2554 */
2555
2556 Spannable sp = new SpannableString(mText);
2557
2558 for (ChangeWatcher cw :
2559 sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2560 sp.removeSpan(cw);
2561 }
2562
2563 ss.text = sp;
2564 } else {
2565 ss.text = mText.toString();
2566 }
2567
2568 if (isFocused() && start >= 0 && end >= 0) {
2569 ss.frozenWithFocus = true;
2570 }
2571
The Android Open Source Project4df24232009-03-05 14:34:35 -08002572 ss.error = mError;
2573
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002574 return ss;
2575 }
2576
2577 return superState;
2578 }
2579
2580 @Override
2581 public void onRestoreInstanceState(Parcelable state) {
2582 if (!(state instanceof SavedState)) {
2583 super.onRestoreInstanceState(state);
2584 return;
2585 }
2586
2587 SavedState ss = (SavedState)state;
2588 super.onRestoreInstanceState(ss.getSuperState());
2589
2590 // XXX restore buffer type too, as well as lots of other stuff
2591 if (ss.text != null) {
2592 setText(ss.text);
2593 }
2594
2595 if (ss.selStart >= 0 && ss.selEnd >= 0) {
2596 if (mText instanceof Spannable) {
2597 int len = mText.length();
2598
2599 if (ss.selStart > len || ss.selEnd > len) {
2600 String restored = "";
2601
2602 if (ss.text != null) {
2603 restored = "(restored) ";
2604 }
2605
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07002606 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002607 "/" + ss.selEnd + " out of range for " + restored +
2608 "text " + mText);
2609 } else {
2610 Selection.setSelection((Spannable) mText, ss.selStart,
2611 ss.selEnd);
2612
2613 if (ss.frozenWithFocus) {
2614 mFrozenWithFocus = true;
2615 }
2616 }
2617 }
2618 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08002619
2620 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07002621 final CharSequence error = ss.error;
2622 // Display the error later, after the first layout pass
2623 post(new Runnable() {
2624 public void run() {
2625 setError(error);
2626 }
2627 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08002628 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002629 }
2630
2631 /**
2632 * Control whether this text view saves its entire text contents when
2633 * freezing to an icicle, in addition to dynamic state such as cursor
2634 * position. By default this is false, not saving the text. Set to true
2635 * if the text in the text view is not being saved somewhere else in
2636 * persistent storage (such as in a content provider) so that if the
2637 * view is later thawed the user will not lose their data.
2638 *
2639 * @param freezesText Controls whether a frozen icicle should include the
2640 * entire text data: true to include it, false to not.
2641 *
2642 * @attr ref android.R.styleable#TextView_freezesText
2643 */
2644 @android.view.RemotableViewMethod
2645 public void setFreezesText(boolean freezesText) {
2646 mFreezesText = freezesText;
2647 }
2648
2649 /**
2650 * Return whether this text view is including its entire text contents
2651 * in frozen icicles.
2652 *
2653 * @return Returns true if text is included, false if it isn't.
2654 *
2655 * @see #setFreezesText
2656 */
2657 public boolean getFreezesText() {
2658 return mFreezesText;
2659 }
2660
2661 ///////////////////////////////////////////////////////////////////////////
2662
2663 /**
2664 * Sets the Factory used to create new Editables.
2665 */
2666 public final void setEditableFactory(Editable.Factory factory) {
2667 mEditableFactory = factory;
2668 setText(mText);
2669 }
2670
2671 /**
2672 * Sets the Factory used to create new Spannables.
2673 */
2674 public final void setSpannableFactory(Spannable.Factory factory) {
2675 mSpannableFactory = factory;
2676 setText(mText);
2677 }
2678
2679 /**
2680 * Sets the string value of the TextView. TextView <em>does not</em> accept
2681 * HTML-like formatting, which you can do with text strings in XML resource files.
2682 * To style your strings, attach android.text.style.* objects to a
2683 * {@link android.text.SpannableString SpannableString}, or see the
2684 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
2685 * Available Resource Types</a> documentation for an example of setting
2686 * formatted text in the XML resource file.
2687 *
2688 * @attr ref android.R.styleable#TextView_text
2689 */
2690 @android.view.RemotableViewMethod
2691 public final void setText(CharSequence text) {
2692 setText(text, mBufferType);
2693 }
2694
2695 /**
2696 * Like {@link #setText(CharSequence)},
2697 * except that the cursor position (if any) is retained in the new text.
2698 *
2699 * @param text The new text to place in the text view.
2700 *
2701 * @see #setText(CharSequence)
2702 */
2703 @android.view.RemotableViewMethod
2704 public final void setTextKeepState(CharSequence text) {
2705 setTextKeepState(text, mBufferType);
2706 }
2707
2708 /**
2709 * Sets the text that this TextView is to display (see
2710 * {@link #setText(CharSequence)}) and also sets whether it is stored
2711 * in a styleable/spannable buffer and whether it is editable.
2712 *
2713 * @attr ref android.R.styleable#TextView_text
2714 * @attr ref android.R.styleable#TextView_bufferType
2715 */
2716 public void setText(CharSequence text, BufferType type) {
2717 setText(text, type, true, 0);
2718
2719 if (mCharWrapper != null) {
2720 mCharWrapper.mChars = null;
2721 }
2722 }
2723
2724 private void setText(CharSequence text, BufferType type,
2725 boolean notifyBefore, int oldlen) {
2726 if (text == null) {
2727 text = "";
2728 }
2729
Romain Guy939151f2009-04-08 14:22:40 -07002730 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
2731
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002732 if (text instanceof Spanned &&
2733 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
2734 setHorizontalFadingEdgeEnabled(true);
2735 setEllipsize(TextUtils.TruncateAt.MARQUEE);
2736 }
2737
2738 int n = mFilters.length;
2739 for (int i = 0; i < n; i++) {
2740 CharSequence out = mFilters[i].filter(text, 0, text.length(),
2741 EMPTY_SPANNED, 0, 0);
2742 if (out != null) {
2743 text = out;
2744 }
2745 }
2746
2747 if (notifyBefore) {
2748 if (mText != null) {
2749 oldlen = mText.length();
2750 sendBeforeTextChanged(mText, 0, oldlen, text.length());
2751 } else {
2752 sendBeforeTextChanged("", 0, 0, text.length());
2753 }
2754 }
2755
2756 boolean needEditableForNotification = false;
2757
2758 if (mListeners != null && mListeners.size() != 0) {
2759 needEditableForNotification = true;
2760 }
2761
2762 if (type == BufferType.EDITABLE || mInput != null ||
2763 needEditableForNotification) {
2764 Editable t = mEditableFactory.newEditable(text);
2765 text = t;
2766 setFilters(t, mFilters);
2767 InputMethodManager imm = InputMethodManager.peekInstance();
2768 if (imm != null) imm.restartInput(this);
2769 } else if (type == BufferType.SPANNABLE || mMovement != null) {
2770 text = mSpannableFactory.newSpannable(text);
2771 } else if (!(text instanceof CharWrapper)) {
2772 text = TextUtils.stringOrSpannedString(text);
2773 }
2774
2775 if (mAutoLinkMask != 0) {
2776 Spannable s2;
2777
2778 if (type == BufferType.EDITABLE || text instanceof Spannable) {
2779 s2 = (Spannable) text;
2780 } else {
2781 s2 = mSpannableFactory.newSpannable(text);
2782 }
2783
2784 if (Linkify.addLinks(s2, mAutoLinkMask)) {
2785 text = s2;
2786 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
2787
2788 /*
2789 * We must go ahead and set the text before changing the
2790 * movement method, because setMovementMethod() may call
2791 * setText() again to try to upgrade the buffer type.
2792 */
2793 mText = text;
2794
Gilles Debunnecbcb3452010-12-17 15:31:02 -08002795 // Do not change the movement method for text that support text selection as it
2796 // would prevent an arbitrary cursor displacement.
2797 final boolean hasTextSelection = this instanceof EditText || mTextIsSelectable;
2798 if (mLinksClickable && !hasTextSelection) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002799 setMovementMethod(LinkMovementMethod.getInstance());
2800 }
2801 }
2802 }
2803
2804 mBufferType = type;
2805 mText = text;
2806
2807 if (mTransformation == null)
2808 mTransformed = text;
2809 else
2810 mTransformed = mTransformation.getTransformation(text, this);
2811
2812 final int textLength = text.length();
2813
2814 if (text instanceof Spannable) {
2815 Spannable sp = (Spannable) text;
2816
2817 // Remove any ChangeWatchers that might have come
2818 // from other TextViews.
2819 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
2820 final int count = watchers.length;
2821 for (int i = 0; i < count; i++)
2822 sp.removeSpan(watchers[i]);
2823
2824 if (mChangeWatcher == null)
2825 mChangeWatcher = new ChangeWatcher();
2826
2827 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
2828 (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
2829
2830 if (mInput != null) {
2831 sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2832 }
2833
2834 if (mTransformation != null) {
2835 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2836
2837 }
2838
2839 if (mMovement != null) {
2840 mMovement.initialize(this, (Spannable) text);
2841
2842 /*
2843 * Initializing the movement method will have set the
2844 * selection, so reset mSelectionMoved to keep that from
2845 * interfering with the normal on-focus selection-setting.
2846 */
2847 mSelectionMoved = false;
2848 }
2849 }
2850
2851 if (mLayout != null) {
2852 checkForRelayout();
2853 }
2854
2855 sendOnTextChanged(text, 0, oldlen, textLength);
2856 onTextChanged(text, 0, oldlen, textLength);
2857
2858 if (needEditableForNotification) {
2859 sendAfterTextChanged((Editable) text);
2860 }
Gilles Debunne05336272010-07-09 20:13:45 -07002861
Gilles Debunnebaaace52010-10-01 15:47:13 -07002862 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunnef788a9f2010-07-22 10:17:23 -07002863 prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002864 }
2865
2866 /**
2867 * Sets the TextView to display the specified slice of the specified
2868 * char array. You must promise that you will not change the contents
2869 * of the array except for right before another call to setText(),
2870 * since the TextView has no way to know that the text
2871 * has changed and that it needs to invalidate and re-layout.
2872 */
2873 public final void setText(char[] text, int start, int len) {
2874 int oldlen = 0;
2875
2876 if (start < 0 || len < 0 || start + len > text.length) {
2877 throw new IndexOutOfBoundsException(start + ", " + len);
2878 }
2879
2880 /*
2881 * We must do the before-notification here ourselves because if
2882 * the old text is a CharWrapper we destroy it before calling
2883 * into the normal path.
2884 */
2885 if (mText != null) {
2886 oldlen = mText.length();
2887 sendBeforeTextChanged(mText, 0, oldlen, len);
2888 } else {
2889 sendBeforeTextChanged("", 0, 0, len);
2890 }
2891
2892 if (mCharWrapper == null) {
2893 mCharWrapper = new CharWrapper(text, start, len);
2894 } else {
2895 mCharWrapper.set(text, start, len);
2896 }
2897
2898 setText(mCharWrapper, mBufferType, false, oldlen);
2899 }
2900
2901 private static class CharWrapper
2902 implements CharSequence, GetChars, GraphicsOperations {
2903 private char[] mChars;
2904 private int mStart, mLength;
2905
2906 public CharWrapper(char[] chars, int start, int len) {
2907 mChars = chars;
2908 mStart = start;
2909 mLength = len;
2910 }
2911
2912 /* package */ void set(char[] chars, int start, int len) {
2913 mChars = chars;
2914 mStart = start;
2915 mLength = len;
2916 }
2917
2918 public int length() {
2919 return mLength;
2920 }
2921
2922 public char charAt(int off) {
2923 return mChars[off + mStart];
2924 }
2925
Gilles Debunnee15b3582010-06-16 15:17:21 -07002926 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002927 public String toString() {
2928 return new String(mChars, mStart, mLength);
2929 }
2930
2931 public CharSequence subSequence(int start, int end) {
2932 if (start < 0 || end < 0 || start > mLength || end > mLength) {
2933 throw new IndexOutOfBoundsException(start + ", " + end);
2934 }
2935
2936 return new String(mChars, start + mStart, end - start);
2937 }
2938
2939 public void getChars(int start, int end, char[] buf, int off) {
2940 if (start < 0 || end < 0 || start > mLength || end > mLength) {
2941 throw new IndexOutOfBoundsException(start + ", " + end);
2942 }
2943
2944 System.arraycopy(mChars, start + mStart, buf, off, end - start);
2945 }
2946
2947 public void drawText(Canvas c, int start, int end,
2948 float x, float y, Paint p) {
2949 c.drawText(mChars, start + mStart, end - start, x, y, p);
2950 }
2951
Doug Feltf47d7402010-04-21 16:01:52 -07002952 public void drawTextRun(Canvas c, int start, int end,
Doug Felt0c702b82010-05-14 10:55:42 -07002953 int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
2954 int count = end - start;
2955 int contextCount = contextEnd - contextStart;
2956 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
2957 contextCount, x, y, flags, p);
Doug Feltf47d7402010-04-21 16:01:52 -07002958 }
2959
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002960 public float measureText(int start, int end, Paint p) {
2961 return p.measureText(mChars, start + mStart, end - start);
2962 }
2963
2964 public int getTextWidths(int start, int end, float[] widths, Paint p) {
2965 return p.getTextWidths(mChars, start + mStart, end - start, widths);
2966 }
Doug Felt0c702b82010-05-14 10:55:42 -07002967
2968 public float getTextRunAdvances(int start, int end, int contextStart,
2969 int contextEnd, int flags, float[] advances, int advancesIndex,
2970 Paint p) {
2971 int count = end - start;
2972 int contextCount = contextEnd - contextStart;
2973 return p.getTextRunAdvances(mChars, start + mStart, count,
2974 contextStart + mStart, contextCount, flags, advances,
2975 advancesIndex);
2976 }
2977
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07002978 public float getTextRunAdvances(int start, int end, int contextStart,
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07002979 int contextEnd, int flags, float[] advances, int advancesIndex,
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07002980 Paint p, int reserved) {
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07002981 int count = end - start;
2982 int contextCount = contextEnd - contextStart;
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07002983 return p.getTextRunAdvances(mChars, start + mStart, count,
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07002984 contextStart + mStart, contextCount, flags, advances,
Fabrice Di Meglio0a1413e2011-04-21 17:36:26 -07002985 advancesIndex, reserved);
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -07002986 }
2987
Doug Felt0c702b82010-05-14 10:55:42 -07002988 public int getTextRunCursor(int contextStart, int contextEnd, int flags,
2989 int offset, int cursorOpt, Paint p) {
2990 int contextCount = contextEnd - contextStart;
2991 return p.getTextRunCursor(mChars, contextStart + mStart,
2992 contextCount, flags, offset + mStart, cursorOpt);
2993 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002994 }
2995
2996 /**
2997 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
2998 * except that the cursor position (if any) is retained in the new text.
2999 *
3000 * @see #setText(CharSequence, android.widget.TextView.BufferType)
3001 */
3002 public final void setTextKeepState(CharSequence text, BufferType type) {
3003 int start = getSelectionStart();
3004 int end = getSelectionEnd();
3005 int len = text.length();
3006
3007 setText(text, type);
3008
3009 if (start >= 0 || end >= 0) {
3010 if (mText instanceof Spannable) {
3011 Selection.setSelection((Spannable) mText,
3012 Math.max(0, Math.min(start, len)),
3013 Math.max(0, Math.min(end, len)));
3014 }
3015 }
3016 }
3017
3018 @android.view.RemotableViewMethod
3019 public final void setText(int resid) {
3020 setText(getContext().getResources().getText(resid));
3021 }
3022
3023 public final void setText(int resid, BufferType type) {
3024 setText(getContext().getResources().getText(resid), type);
3025 }
3026
3027 /**
3028 * Sets the text to be displayed when the text of the TextView is empty.
3029 * Null means to use the normal empty text. The hint does not currently
3030 * participate in determining the size of the view.
3031 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003032 * @attr ref android.R.styleable#TextView_hint
3033 */
3034 @android.view.RemotableViewMethod
3035 public final void setHint(CharSequence hint) {
3036 mHint = TextUtils.stringOrSpannedString(hint);
3037
3038 if (mLayout != null) {
3039 checkForRelayout();
3040 }
3041
Romain Guy4dc4f732009-06-19 15:16:40 -07003042 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003043 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07003044 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003045 }
3046
3047 /**
3048 * Sets the text to be displayed when the text of the TextView is empty,
3049 * from a resource.
3050 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003051 * @attr ref android.R.styleable#TextView_hint
3052 */
3053 @android.view.RemotableViewMethod
3054 public final void setHint(int resid) {
3055 setHint(getContext().getResources().getText(resid));
3056 }
3057
3058 /**
3059 * Returns the hint that is displayed when the text of the TextView
3060 * is empty.
3061 *
3062 * @attr ref android.R.styleable#TextView_hint
3063 */
3064 @ViewDebug.CapturedViewProperty
3065 public CharSequence getHint() {
3066 return mHint;
3067 }
3068
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003069 private boolean isMultilineInputType(int type) {
3070 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3071 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3072 }
3073
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003074 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003075 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3076 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3077 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
3078 * then a soft keyboard will not be displayed for this text view.
3079 *
3080 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3081 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3082 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003083 *
3084 * @see #getInputType()
3085 * @see #setRawInputType(int)
3086 * @see android.text.InputType
3087 * @attr ref android.R.styleable#TextView_inputType
3088 */
3089 public void setInputType(int type) {
Bjorn Bringertad8da912009-09-17 10:47:35 +01003090 final boolean wasPassword = isPasswordInputType(mInputType);
3091 final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003092 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003093 final boolean isPassword = isPasswordInputType(type);
3094 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003095 boolean forceUpdate = false;
3096 if (isPassword) {
3097 setTransformationMethod(PasswordTransformationMethod.getInstance());
3098 setTypefaceByIndex(MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003099 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07003100 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3101 forceUpdate = true;
3102 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08003103 setTypefaceByIndex(MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003104 } else if (wasPassword || wasVisiblePassword) {
3105 // not in password mode, clean up typeface and transformation
3106 setTypefaceByIndex(-1, -1);
3107 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3108 forceUpdate = true;
3109 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003110 }
3111
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003112 boolean singleLine = !isMultilineInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003113
3114 // We need to update the single line mode if it has changed or we
3115 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003116 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003117 // Change single line mode, but only change the transformation if
3118 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003119 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003120 }
3121
3122 InputMethodManager imm = InputMethodManager.peekInstance();
3123 if (imm != null) imm.restartInput(this);
3124 }
3125
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07003126 /**
3127 * It would be better to rely on the input type for everything. A password inputType should have
3128 * a password transformation. We should hence use isPasswordInputType instead of this method.
3129 *
3130 * We should:
3131 * - Call setInputType in setKeyListener instead of changing the input type directly (which
3132 * would install the correct transformation).
3133 * - Refuse the installation of a non-password transformation in setTransformation if the input
3134 * type is password.
3135 *
3136 * However, this is like this for legacy reasons and we cannot break existing apps. This method
3137 * is useful since it matches what the user can see (obfuscated text or not).
3138 *
3139 * @return true if the current transformation method is of the password type.
3140 */
3141 private boolean hasPasswordTransformationMethod() {
3142 return mTransformation instanceof PasswordTransformationMethod;
3143 }
3144
Bjorn Bringertad8da912009-09-17 10:47:35 +01003145 private boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08003146 final int variation =
3147 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003148 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08003149 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
3150 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09003151 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
3152 || variation
3153 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003154 }
3155
3156 private boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08003157 final int variation =
3158 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003159 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08003160 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003161 }
3162
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003163 /**
3164 * Directly change the content type integer of the text view, without
3165 * modifying any other state.
3166 * @see #setInputType(int)
3167 * @see android.text.InputType
3168 * @attr ref android.R.styleable#TextView_inputType
3169 */
3170 public void setRawInputType(int type) {
3171 mInputType = type;
3172 }
3173
3174 private void setInputType(int type, boolean direct) {
3175 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
3176 KeyListener input;
3177 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07003178 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003179 TextKeyListener.Capitalize cap;
3180 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
3181 cap = TextKeyListener.Capitalize.CHARACTERS;
3182 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
3183 cap = TextKeyListener.Capitalize.WORDS;
3184 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
3185 cap = TextKeyListener.Capitalize.SENTENCES;
3186 } else {
3187 cap = TextKeyListener.Capitalize.NONE;
3188 }
3189 input = TextKeyListener.getInstance(autotext, cap);
3190 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
3191 input = DigitsKeyListener.getInstance(
3192 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
3193 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
3194 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
3195 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
3196 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
3197 input = DateKeyListener.getInstance();
3198 break;
3199 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
3200 input = TimeKeyListener.getInstance();
3201 break;
3202 default:
3203 input = DateTimeKeyListener.getInstance();
3204 break;
3205 }
3206 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
3207 input = DialerKeyListener.getInstance();
3208 } else {
3209 input = TextKeyListener.getInstance();
3210 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003211 setRawInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003212 if (direct) mInput = input;
3213 else {
3214 setKeyListenerOnly(input);
3215 }
3216 }
3217
3218 /**
3219 * Get the type of the content.
3220 *
3221 * @see #setInputType(int)
3222 * @see android.text.InputType
3223 */
3224 public int getInputType() {
3225 return mInputType;
3226 }
3227
3228 /**
3229 * Change the editor type integer associated with the text view, which
3230 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
3231 * has focus.
3232 * @see #getImeOptions
3233 * @see android.view.inputmethod.EditorInfo
3234 * @attr ref android.R.styleable#TextView_imeOptions
3235 */
3236 public void setImeOptions(int imeOptions) {
3237 if (mInputContentType == null) {
3238 mInputContentType = new InputContentType();
3239 }
3240 mInputContentType.imeOptions = imeOptions;
3241 }
3242
3243 /**
3244 * Get the type of the IME editor.
3245 *
3246 * @see #setImeOptions(int)
3247 * @see android.view.inputmethod.EditorInfo
3248 */
3249 public int getImeOptions() {
3250 return mInputContentType != null
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07003251 ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003252 }
3253
3254 /**
3255 * Change the custom IME action associated with the text view, which
3256 * will be reported to an IME with {@link EditorInfo#actionLabel}
3257 * and {@link EditorInfo#actionId} when it has focus.
3258 * @see #getImeActionLabel
3259 * @see #getImeActionId
3260 * @see android.view.inputmethod.EditorInfo
3261 * @attr ref android.R.styleable#TextView_imeActionLabel
3262 * @attr ref android.R.styleable#TextView_imeActionId
3263 */
3264 public void setImeActionLabel(CharSequence label, int actionId) {
3265 if (mInputContentType == null) {
3266 mInputContentType = new InputContentType();
3267 }
3268 mInputContentType.imeActionLabel = label;
3269 mInputContentType.imeActionId = actionId;
3270 }
3271
3272 /**
3273 * Get the IME action label previous set with {@link #setImeActionLabel}.
3274 *
3275 * @see #setImeActionLabel
3276 * @see android.view.inputmethod.EditorInfo
3277 */
3278 public CharSequence getImeActionLabel() {
3279 return mInputContentType != null
3280 ? mInputContentType.imeActionLabel : null;
3281 }
3282
3283 /**
3284 * Get the IME action ID previous set with {@link #setImeActionLabel}.
3285 *
3286 * @see #setImeActionLabel
3287 * @see android.view.inputmethod.EditorInfo
3288 */
3289 public int getImeActionId() {
3290 return mInputContentType != null
3291 ? mInputContentType.imeActionId : 0;
3292 }
3293
3294 /**
3295 * Set a special listener to be called when an action is performed
3296 * on the text view. This will be called when the enter key is pressed,
3297 * or when an action supplied to the IME is selected by the user. Setting
3298 * this means that the normal hard key event will not insert a newline
3299 * into the text view, even if it is multi-line; holding down the ALT
3300 * modifier will, however, allow the user to insert a newline character.
3301 */
3302 public void setOnEditorActionListener(OnEditorActionListener l) {
3303 if (mInputContentType == null) {
3304 mInputContentType = new InputContentType();
3305 }
3306 mInputContentType.onEditorActionListener = l;
3307 }
3308
3309 /**
3310 * Called when an attached input method calls
3311 * {@link InputConnection#performEditorAction(int)
3312 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07003313 * for this text view. The default implementation will call your action
3314 * listener supplied to {@link #setOnEditorActionListener}, or perform
3315 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07003316 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
3317 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07003318 * EditorInfo.IME_ACTION_DONE}.
3319 *
3320 * <p>For backwards compatibility, if no IME options have been set and the
3321 * text view would not normally advance focus on enter, then
3322 * the NEXT and DONE actions received here will be turned into an enter
3323 * key down/up pair to go through the normal key handling.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003324 *
3325 * @param actionCode The code of the action being performed.
3326 *
3327 * @see #setOnEditorActionListener
3328 */
3329 public void onEditorAction(int actionCode) {
3330 final InputContentType ict = mInputContentType;
3331 if (ict != null) {
3332 if (ict.onEditorActionListener != null) {
3333 if (ict.onEditorActionListener.onEditorAction(this,
3334 actionCode, null)) {
3335 return;
3336 }
3337 }
The Android Open Source Project10592532009-03-18 17:39:46 -07003338
The Android Open Source Project4df24232009-03-05 14:34:35 -08003339 // This is the handling for some default action.
3340 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003341 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07003342 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08003343 // app may be expecting.
3344 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
3345 View v = focusSearch(FOCUS_DOWN);
3346 if (v != null) {
3347 if (!v.requestFocus(FOCUS_DOWN)) {
3348 throw new IllegalStateException("focus search returned a view " +
3349 "that wasn't able to take focus!");
3350 }
3351 }
3352 return;
3353
Dianne Hackborndea3ef72010-10-28 14:24:22 -07003354 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
3355 View v = focusSearch(FOCUS_UP);
3356 if (v != null) {
3357 if (!v.requestFocus(FOCUS_UP)) {
3358 throw new IllegalStateException("focus search returned a view " +
3359 "that wasn't able to take focus!");
3360 }
3361 }
3362 return;
3363
The Android Open Source Project4df24232009-03-05 14:34:35 -08003364 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
3365 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08003366 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08003367 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003368 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07003369 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003370 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003371 }
3372
3373 Handler h = getHandler();
The Android Open Source Project10592532009-03-18 17:39:46 -07003374 if (h != null) {
3375 long eventTime = SystemClock.uptimeMillis();
Joe Onoratoc6cc0f82011-04-12 11:53:13 -07003376 h.sendMessage(h.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
The Android Open Source Project10592532009-03-18 17:39:46 -07003377 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08003378 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
3379 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07003380 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3381 | KeyEvent.FLAG_EDITOR_ACTION)));
Joe Onoratoc6cc0f82011-04-12 11:53:13 -07003382 h.sendMessage(h.obtainMessage(ViewAncestor.DISPATCH_KEY_FROM_IME,
The Android Open Source Project10592532009-03-18 17:39:46 -07003383 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08003384 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
3385 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07003386 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3387 | KeyEvent.FLAG_EDITOR_ACTION)));
3388 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003389 }
3390
3391 /**
3392 * Set the private content type of the text, which is the
3393 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
3394 * field that will be filled in when creating an input connection.
3395 *
3396 * @see #getPrivateImeOptions()
3397 * @see EditorInfo#privateImeOptions
3398 * @attr ref android.R.styleable#TextView_privateImeOptions
3399 */
3400 public void setPrivateImeOptions(String type) {
3401 if (mInputContentType == null) mInputContentType = new InputContentType();
3402 mInputContentType.privateImeOptions = type;
3403 }
3404
3405 /**
3406 * Get the private type of the content.
3407 *
3408 * @see #setPrivateImeOptions(String)
3409 * @see EditorInfo#privateImeOptions
3410 */
3411 public String getPrivateImeOptions() {
3412 return mInputContentType != null
3413 ? mInputContentType.privateImeOptions : null;
3414 }
3415
3416 /**
3417 * Set the extra input data of the text, which is the
3418 * {@link EditorInfo#extras TextBoxAttribute.extras}
3419 * Bundle that will be filled in when creating an input connection. The
3420 * given integer is the resource ID of an XML resource holding an
3421 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
3422 *
3423 * @see #getInputExtras(boolean)
3424 * @see EditorInfo#extras
3425 * @attr ref android.R.styleable#TextView_editorExtras
3426 */
3427 public void setInputExtras(int xmlResId)
3428 throws XmlPullParserException, IOException {
3429 XmlResourceParser parser = getResources().getXml(xmlResId);
3430 if (mInputContentType == null) mInputContentType = new InputContentType();
3431 mInputContentType.extras = new Bundle();
3432 getResources().parseBundleExtras(parser, mInputContentType.extras);
3433 }
3434
3435 /**
3436 * Retrieve the input extras currently associated with the text view, which
3437 * can be viewed as well as modified.
3438 *
3439 * @param create If true, the extras will be created if they don't already
3440 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07003441 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003442 * @see EditorInfo#extras
3443 * @attr ref android.R.styleable#TextView_editorExtras
3444 */
3445 public Bundle getInputExtras(boolean create) {
3446 if (mInputContentType == null) {
3447 if (!create) return null;
3448 mInputContentType = new InputContentType();
3449 }
3450 if (mInputContentType.extras == null) {
3451 if (!create) return null;
3452 mInputContentType.extras = new Bundle();
3453 }
3454 return mInputContentType.extras;
3455 }
3456
3457 /**
3458 * Returns the error message that was set to be displayed with
3459 * {@link #setError}, or <code>null</code> if no error was set
3460 * or if it the error was cleared by the widget after user input.
3461 */
3462 public CharSequence getError() {
3463 return mError;
3464 }
3465
3466 /**
3467 * Sets the right-hand compound drawable of the TextView to the "error"
3468 * icon and sets an error message that will be displayed in a popup when
3469 * the TextView has focus. The icon and error message will be reset to
3470 * null when any key events cause changes to the TextView's text. If the
3471 * <code>error</code> is <code>null</code>, the error message and icon
3472 * will be cleared.
3473 */
3474 @android.view.RemotableViewMethod
3475 public void setError(CharSequence error) {
3476 if (error == null) {
3477 setError(null, null);
3478 } else {
3479 Drawable dr = getContext().getResources().
Gilles Debunnea85467b2011-01-19 16:53:31 -08003480 getDrawable(com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003481
3482 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
3483 setError(error, dr);
3484 }
3485 }
3486
3487 /**
3488 * Sets the right-hand compound drawable of the TextView to the specified
3489 * icon and sets an error message that will be displayed in a popup when
3490 * the TextView has focus. The icon and error message will be reset to
3491 * null when any key events cause changes to the TextView's text. The
3492 * drawable must already have had {@link Drawable#setBounds} set on it.
3493 * If the <code>error</code> is <code>null</code>, the error message will
3494 * be cleared (and you should provide a <code>null</code> icon as well).
3495 */
3496 public void setError(CharSequence error, Drawable icon) {
3497 error = TextUtils.stringOrSpannedString(error);
3498
3499 mError = error;
3500 mErrorWasChanged = true;
3501 final Drawables dr = mDrawables;
3502 if (dr != null) {
Gilles Debunnea85467b2011-01-19 16:53:31 -08003503 setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, dr.mDrawableBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003504 } else {
3505 setCompoundDrawables(null, null, icon, null);
3506 }
3507
3508 if (error == null) {
3509 if (mPopup != null) {
3510 if (mPopup.isShowing()) {
3511 mPopup.dismiss();
3512 }
3513
3514 mPopup = null;
3515 }
3516 } else {
3517 if (isFocused()) {
3518 showError();
3519 }
3520 }
3521 }
3522
3523 private void showError() {
3524 if (getWindowToken() == null) {
3525 mShowErrorAfterAttach = true;
3526 return;
3527 }
3528
3529 if (mPopup == null) {
3530 LayoutInflater inflater = LayoutInflater.from(getContext());
Gilles Debunnea85467b2011-01-19 16:53:31 -08003531 final TextView err = (TextView) inflater.inflate(
3532 com.android.internal.R.layout.textview_hint, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003533
Romain Guy9bc9fa12009-07-21 16:57:29 -07003534 final float scale = getResources().getDisplayMetrics().density;
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003535 mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003536 mPopup.setFocusable(false);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003537 // The user is entering text, so the input method is needed. We
3538 // don't want the popup to be displayed on top of it.
3539 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003540 }
3541
3542 TextView tv = (TextView) mPopup.getContentView();
3543 chooseSize(mPopup, mError, tv);
3544 tv.setText(mError);
3545
3546 mPopup.showAsDropDown(this, getErrorX(), getErrorY());
The Android Open Source Project10592532009-03-18 17:39:46 -07003547 mPopup.fixDirection(mPopup.isAboveAnchor());
3548 }
3549
3550 private static class ErrorPopup extends PopupWindow {
3551 private boolean mAbove = false;
Gilles Debunnee15b3582010-06-16 15:17:21 -07003552 private final TextView mView;
Gilles Debunne5f059e42011-01-12 17:49:12 -08003553 private int mPopupInlineErrorBackgroundId = 0;
3554 private int mPopupInlineErrorAboveBackgroundId = 0;
The Android Open Source Project10592532009-03-18 17:39:46 -07003555
3556 ErrorPopup(TextView v, int width, int height) {
3557 super(v, width, height);
3558 mView = v;
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003559 // Make sure the TextView has a background set as it will be used the first time it is
3560 // shown and positionned. Initialized with below background, which should have
3561 // dimensions identical to the above version for this to work (and is more likely).
3562 mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
3563 com.android.internal.R.styleable.Theme_errorMessageBackground);
3564 mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
The Android Open Source Project10592532009-03-18 17:39:46 -07003565 }
3566
3567 void fixDirection(boolean above) {
3568 mAbove = above;
3569
3570 if (above) {
Gilles Debunne5f059e42011-01-12 17:49:12 -08003571 mPopupInlineErrorAboveBackgroundId =
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003572 getResourceId(mPopupInlineErrorAboveBackgroundId,
3573 com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
The Android Open Source Project10592532009-03-18 17:39:46 -07003574 } else {
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003575 mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
3576 com.android.internal.R.styleable.Theme_errorMessageBackground);
The Android Open Source Project10592532009-03-18 17:39:46 -07003577 }
Gilles Debunne5f059e42011-01-12 17:49:12 -08003578
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003579 mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
3580 mPopupInlineErrorBackgroundId);
Gilles Debunne5f059e42011-01-12 17:49:12 -08003581 }
3582
3583 private int getResourceId(int currentId, int index) {
3584 if (currentId == 0) {
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003585 TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
3586 R.styleable.Theme);
Gilles Debunne5f059e42011-01-12 17:49:12 -08003587 currentId = styledAttributes.getResourceId(index, 0);
3588 styledAttributes.recycle();
3589 }
3590 return currentId;
The Android Open Source Project10592532009-03-18 17:39:46 -07003591 }
3592
3593 @Override
3594 public void update(int x, int y, int w, int h, boolean force) {
3595 super.update(x, y, w, h, force);
3596
3597 boolean above = isAboveAnchor();
3598 if (above != mAbove) {
3599 fixDirection(above);
3600 }
3601 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003602 }
3603
3604 /**
3605 * Returns the Y offset to make the pointy top of the error point
3606 * at the middle of the error icon.
3607 */
3608 private int getErrorX() {
3609 /*
3610 * The "25" is the distance between the point and the right edge
3611 * of the background
3612 */
Romain Guy9bc9fa12009-07-21 16:57:29 -07003613 final float scale = getResources().getDisplayMetrics().density;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003614
3615 final Drawables dr = mDrawables;
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003616 return getWidth() - mPopup.getWidth() - getPaddingRight() -
3617 (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003618 }
3619
3620 /**
3621 * Returns the Y offset to make the pointy top of the error point
3622 * at the bottom of the error icon.
3623 */
3624 private int getErrorY() {
3625 /*
3626 * Compound, not extended, because the icon is not clipped
3627 * if the text height is smaller.
3628 */
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003629 final int compoundPaddingTop = getCompoundPaddingTop();
3630 int vspace = mBottom - mTop - getCompoundPaddingBottom() - compoundPaddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003631
3632 final Drawables dr = mDrawables;
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003633 int icontop = compoundPaddingTop +
3634 (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003635
3636 /*
3637 * The "2" is the distance between the point and the top edge
3638 * of the background.
3639 */
Gilles Debunnef1f409a2011-01-27 17:31:00 -08003640 final float scale = getResources().getDisplayMetrics().density;
3641 return icontop + (dr != null ? dr.mDrawableHeightRight : 0) - getHeight() -
3642 (int) (2 * scale + 0.5f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003643 }
3644
3645 private void hideError() {
3646 if (mPopup != null) {
3647 if (mPopup.isShowing()) {
3648 mPopup.dismiss();
3649 }
3650 }
3651
3652 mShowErrorAfterAttach = false;
3653 }
3654
3655 private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
3656 int wid = tv.getPaddingLeft() + tv.getPaddingRight();
3657 int ht = tv.getPaddingTop() + tv.getPaddingBottom();
3658
3659 /*
3660 * Figure out how big the text would be if we laid it out to the
3661 * full width of this view minus the border.
3662 */
3663 int cap = getWidth() - wid;
3664 if (cap < 0) {
3665 cap = 200; // We must not be measured yet -- setFrame() will fix it.
3666 }
3667
3668 Layout l = new StaticLayout(text, tv.getPaint(), cap,
3669 Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
3670 float max = 0;
3671 for (int i = 0; i < l.getLineCount(); i++) {
3672 max = Math.max(max, l.getLineWidth(i));
3673 }
3674
3675 /*
3676 * Now set the popup size to be big enough for the text plus the border.
3677 */
3678 pop.setWidth(wid + (int) Math.ceil(max));
3679 pop.setHeight(ht + l.getHeight());
3680 }
3681
3682
3683 @Override
3684 protected boolean setFrame(int l, int t, int r, int b) {
3685 boolean result = super.setFrame(l, t, r, b);
3686
3687 if (mPopup != null) {
3688 TextView tv = (TextView) mPopup.getContentView();
3689 chooseSize(mPopup, mError, tv);
Eric Fischerfa0d2532009-09-17 17:01:59 -07003690 mPopup.update(this, getErrorX(), getErrorY(),
3691 mPopup.getWidth(), mPopup.getHeight());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003692 }
3693
Romain Guy986003d2009-03-25 17:42:35 -07003694 restartMarqueeIfNeeded();
3695
3696 return result;
3697 }
3698
3699 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003700 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3701 mRestartMarquee = false;
3702 startMarquee();
3703 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003704 }
3705
3706 /**
3707 * Sets the list of input filters that will be used if the buffer is
3708 * Editable. Has no effect otherwise.
3709 *
3710 * @attr ref android.R.styleable#TextView_maxLength
3711 */
3712 public void setFilters(InputFilter[] filters) {
3713 if (filters == null) {
3714 throw new IllegalArgumentException();
3715 }
3716
3717 mFilters = filters;
3718
3719 if (mText instanceof Editable) {
3720 setFilters((Editable) mText, filters);
3721 }
3722 }
3723
3724 /**
3725 * Sets the list of input filters on the specified Editable,
3726 * and includes mInput in the list if it is an InputFilter.
3727 */
3728 private void setFilters(Editable e, InputFilter[] filters) {
3729 if (mInput instanceof InputFilter) {
3730 InputFilter[] nf = new InputFilter[filters.length + 1];
3731
3732 System.arraycopy(filters, 0, nf, 0, filters.length);
3733 nf[filters.length] = (InputFilter) mInput;
3734
3735 e.setFilters(nf);
3736 } else {
3737 e.setFilters(filters);
3738 }
3739 }
3740
3741 /**
3742 * Returns the current list of input filters.
3743 */
3744 public InputFilter[] getFilters() {
3745 return mFilters;
3746 }
3747
3748 /////////////////////////////////////////////////////////////////////////
3749
3750 private int getVerticalOffset(boolean forceNormal) {
3751 int voffset = 0;
3752 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3753
3754 Layout l = mLayout;
3755 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3756 l = mHintLayout;
3757 }
3758
3759 if (gravity != Gravity.TOP) {
3760 int boxht;
3761
3762 if (l == mHintLayout) {
3763 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3764 getCompoundPaddingBottom();
3765 } else {
3766 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3767 getExtendedPaddingBottom();
3768 }
3769 int textht = l.getHeight();
3770
3771 if (textht < boxht) {
3772 if (gravity == Gravity.BOTTOM)
3773 voffset = boxht - textht;
3774 else // (gravity == Gravity.CENTER_VERTICAL)
3775 voffset = (boxht - textht) >> 1;
3776 }
3777 }
3778 return voffset;
3779 }
3780
3781 private int getBottomVerticalOffset(boolean forceNormal) {
3782 int voffset = 0;
3783 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3784
3785 Layout l = mLayout;
3786 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3787 l = mHintLayout;
3788 }
3789
3790 if (gravity != Gravity.BOTTOM) {
3791 int boxht;
3792
3793 if (l == mHintLayout) {
3794 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3795 getCompoundPaddingBottom();
3796 } else {
3797 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3798 getExtendedPaddingBottom();
3799 }
3800 int textht = l.getHeight();
3801
3802 if (textht < boxht) {
3803 if (gravity == Gravity.TOP)
3804 voffset = boxht - textht;
3805 else // (gravity == Gravity.CENTER_VERTICAL)
3806 voffset = (boxht - textht) >> 1;
3807 }
3808 }
3809 return voffset;
3810 }
3811
3812 private void invalidateCursorPath() {
3813 if (mHighlightPathBogus) {
3814 invalidateCursor();
3815 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08003816 final int horizontalPadding = getCompoundPaddingLeft();
3817 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003818
Gilles Debunnef75c97e2011-02-10 16:09:53 -08003819 if (mCursorCount == 0) {
3820 synchronized (sTempRect) {
3821 /*
3822 * The reason for this concern about the thickness of the
3823 * cursor and doing the floor/ceil on the coordinates is that
3824 * some EditTexts (notably textfields in the Browser) have
3825 * anti-aliased text where not all the characters are
3826 * necessarily at integer-multiple locations. This should
3827 * make sure the entire cursor gets invalidated instead of
3828 * sometimes missing half a pixel.
3829 */
3830 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
3831 if (thick < 1.0f) {
3832 thick = 1.0f;
3833 }
3834
3835 thick /= 2.0f;
3836
3837 mHighlightPath.computeBounds(sTempRect, false);
3838
3839 invalidate((int) FloatMath.floor(horizontalPadding + sTempRect.left - thick),
3840 (int) FloatMath.floor(verticalPadding + sTempRect.top - thick),
3841 (int) FloatMath.ceil(horizontalPadding + sTempRect.right + thick),
3842 (int) FloatMath.ceil(verticalPadding + sTempRect.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003843 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08003844 } else {
3845 for (int i = 0; i < mCursorCount; i++) {
3846 Rect bounds = mCursorDrawable[i].getBounds();
3847 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
3848 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
3849 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003850 }
3851 }
3852 }
3853
3854 private void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07003855 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003856
3857 invalidateCursor(where, where, where);
3858 }
3859
3860 private void invalidateCursor(int a, int b, int c) {
3861 if (mLayout == null) {
3862 invalidate();
3863 } else {
3864 if (a >= 0 || b >= 0 || c >= 0) {
3865 int first = Math.min(Math.min(a, b), c);
3866 int last = Math.max(Math.max(a, b), c);
3867
3868 int line = mLayout.getLineForOffset(first);
3869 int top = mLayout.getLineTop(line);
3870
3871 // This is ridiculous, but the descent from the line above
3872 // can hang down into the line we really want to redraw,
3873 // so we have to invalidate part of the line above to make
3874 // sure everything that needs to be redrawn really is.
3875 // (But not the whole line above, because that would cause
3876 // the same problem with the descenders on the line above it!)
3877 if (line > 0) {
3878 top -= mLayout.getLineDescent(line - 1);
3879 }
3880
3881 int line2;
3882
3883 if (first == last)
3884 line2 = line;
3885 else
3886 line2 = mLayout.getLineForOffset(last);
3887
3888 int bottom = mLayout.getLineTop(line2 + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003889
Gilles Debunnef75c97e2011-02-10 16:09:53 -08003890 final int horizontalPadding = getCompoundPaddingLeft();
3891 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
3892
3893 // If used, the cursor drawables can have an arbitrary dimension that can go beyond
3894 // the invalidated lines specified above.
3895 for (int i = 0; i < mCursorCount; i++) {
3896 Rect bounds = mCursorDrawable[i].getBounds();
3897 top = Math.min(top, bounds.top);
3898 bottom = Math.max(bottom, bounds.bottom);
3899 // Horizontal bounds are already full width, no need to update
3900 }
3901
3902 invalidate(horizontalPadding + mScrollX, top + verticalPadding,
3903 horizontalPadding + mScrollX + getWidth() -
3904 getCompoundPaddingLeft() - getCompoundPaddingRight(),
3905 bottom + verticalPadding);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003906 }
3907 }
3908 }
3909
3910 private void registerForPreDraw() {
3911 final ViewTreeObserver observer = getViewTreeObserver();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003912
3913 if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
3914 observer.addOnPreDrawListener(this);
3915 mPreDrawState = PREDRAW_PENDING;
3916 } else if (mPreDrawState == PREDRAW_DONE) {
3917 mPreDrawState = PREDRAW_PENDING;
3918 }
3919
3920 // else state is PREDRAW_PENDING, so keep waiting.
3921 }
3922
3923 /**
3924 * {@inheritDoc}
3925 */
3926 public boolean onPreDraw() {
3927 if (mPreDrawState != PREDRAW_PENDING) {
3928 return true;
3929 }
3930
3931 if (mLayout == null) {
3932 assumeLayout();
3933 }
3934
3935 boolean changed = false;
3936
3937 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07003938 /* This code also provides auto-scrolling when a cursor is moved using a
3939 * CursorController (insertion point or selection limits).
3940 * For selection, ensure start or end is visible depending on controller's state.
3941 */
3942 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08003943 // Do not create the controller if it is not already created.
3944 if (mSelectionModifierCursorController != null &&
3945 mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07003946 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07003947 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003948
3949 /*
3950 * TODO: This should really only keep the end in view if
3951 * it already was before the text changed. I'm not sure
3952 * of a good way to tell from here if it was.
3953 */
3954 if (curs < 0 &&
3955 (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3956 curs = mText.length();
3957 }
3958
3959 if (curs >= 0) {
3960 changed = bringPointIntoView(curs);
3961 }
3962 } else {
3963 changed = bringTextIntoView();
3964 }
3965
Gilles Debunne64e54a62010-09-07 19:07:17 -07003966 // This has to be checked here since:
3967 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
3968 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08003969 if (mCreatedWithASelection) {
3970 startSelectionActionMode();
3971 mCreatedWithASelection = false;
3972 }
3973
3974 // Phone specific code (there is no ExtractEditText on tablets).
3975 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
3976 // not be set. Do the test here instead.
3977 if (this instanceof ExtractEditText && hasSelection()) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07003978 startSelectionActionMode();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07003979 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07003980
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003981 mPreDrawState = PREDRAW_DONE;
3982 return !changed;
3983 }
3984
3985 @Override
3986 protected void onAttachedToWindow() {
3987 super.onAttachedToWindow();
3988
3989 mTemporaryDetach = false;
3990
3991 if (mShowErrorAfterAttach) {
3992 showError();
3993 mShowErrorAfterAttach = false;
3994 }
Adam Powell624380a2010-10-02 18:12:02 -07003995
3996 final ViewTreeObserver observer = getViewTreeObserver();
Gilles Debunne81f08082011-02-17 14:07:19 -08003997 // No need to create the controller.
3998 // The get method will add the listener on controller creation.
3999 if (mInsertionPointCursorController != null) {
4000 observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
4001 }
4002 if (mSelectionModifierCursorController != null) {
4003 observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
Adam Powell624380a2010-10-02 18:12:02 -07004004 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004005 }
4006
4007 @Override
4008 protected void onDetachedFromWindow() {
4009 super.onDetachedFromWindow();
4010
Adam Powell624380a2010-10-02 18:12:02 -07004011 final ViewTreeObserver observer = getViewTreeObserver();
Gilles Debunne81f08082011-02-17 14:07:19 -08004012 if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
4013 observer.removeOnPreDrawListener(this);
4014 mPreDrawState = PREDRAW_NOT_REGISTERED;
4015 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004016
4017 if (mError != null) {
4018 hideError();
4019 }
Adam Powellba0a2c32010-09-28 17:41:23 -07004020
Gilles Debunnef48e83b2010-12-06 18:36:08 -08004021 if (mBlink != null) {
Gilles Debunne3d010062011-02-18 14:16:41 -08004022 mBlink.removeCallbacks(mBlink);
Gilles Debunnef48e83b2010-12-06 18:36:08 -08004023 }
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08004024
4025 if (mInsertionPointCursorController != null) {
4026 mInsertionPointCursorController.onDetached();
4027 }
4028
4029 if (mSelectionModifierCursorController != null) {
4030 mSelectionModifierCursorController.onDetached();
4031 }
4032
Adam Powellba0a2c32010-09-28 17:41:23 -07004033 hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004034 }
4035
4036 @Override
4037 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004038 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004039 }
4040
4041 @Override
4042 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004043 return getCompoundPaddingLeft() - mPaddingLeft +
4044 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004045 }
4046
4047 @Override
4048 protected int getTopPaddingOffset() {
4049 return (int) Math.min(0, mShadowDy - mShadowRadius);
4050 }
4051
4052 @Override
4053 protected int getBottomPaddingOffset() {
4054 return (int) Math.max(0, mShadowDy + mShadowRadius);
4055 }
4056
4057 @Override
4058 protected int getRightPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004059 return -(getCompoundPaddingRight() - mPaddingRight) +
4060 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004061 }
4062
4063 @Override
4064 protected boolean verifyDrawable(Drawable who) {
4065 final boolean verified = super.verifyDrawable(who);
4066 if (!verified && mDrawables != null) {
4067 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
4068 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
4069 }
4070 return verified;
4071 }
4072
4073 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07004074 public void jumpDrawablesToCurrentState() {
4075 super.jumpDrawablesToCurrentState();
4076 if (mDrawables != null) {
4077 if (mDrawables.mDrawableLeft != null) {
4078 mDrawables.mDrawableLeft.jumpToCurrentState();
4079 }
4080 if (mDrawables.mDrawableTop != null) {
4081 mDrawables.mDrawableTop.jumpToCurrentState();
4082 }
4083 if (mDrawables.mDrawableRight != null) {
4084 mDrawables.mDrawableRight.jumpToCurrentState();
4085 }
4086 if (mDrawables.mDrawableBottom != null) {
4087 mDrawables.mDrawableBottom.jumpToCurrentState();
4088 }
4089 }
4090 }
4091
4092 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07004093 public void invalidateDrawable(Drawable drawable) {
4094 if (verifyDrawable(drawable)) {
4095 final Rect dirty = drawable.getBounds();
4096 int scrollX = mScrollX;
4097 int scrollY = mScrollY;
4098
4099 // IMPORTANT: The coordinates below are based on the coordinates computed
4100 // for each compound drawable in onDraw(). Make sure to update each section
4101 // accordingly.
4102 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07004103 if (drawables != null) {
4104 if (drawable == drawables.mDrawableLeft) {
4105 final int compoundPaddingTop = getCompoundPaddingTop();
4106 final int compoundPaddingBottom = getCompoundPaddingBottom();
4107 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004108
Romain Guya6cd4e02009-05-20 15:09:21 -07004109 scrollX += mPaddingLeft;
4110 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4111 } else if (drawable == drawables.mDrawableRight) {
4112 final int compoundPaddingTop = getCompoundPaddingTop();
4113 final int compoundPaddingBottom = getCompoundPaddingBottom();
4114 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004115
Romain Guya6cd4e02009-05-20 15:09:21 -07004116 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4117 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4118 } else if (drawable == drawables.mDrawableTop) {
4119 final int compoundPaddingLeft = getCompoundPaddingLeft();
4120 final int compoundPaddingRight = getCompoundPaddingRight();
4121 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004122
Romain Guya6cd4e02009-05-20 15:09:21 -07004123 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4124 scrollY += mPaddingTop;
4125 } else if (drawable == drawables.mDrawableBottom) {
4126 final int compoundPaddingLeft = getCompoundPaddingLeft();
4127 final int compoundPaddingRight = getCompoundPaddingRight();
4128 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004129
Romain Guya6cd4e02009-05-20 15:09:21 -07004130 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4131 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4132 }
Romain Guy3c77d392009-05-20 11:26:50 -07004133 }
4134
4135 invalidate(dirty.left + scrollX, dirty.top + scrollY,
4136 dirty.right + scrollX, dirty.bottom + scrollY);
4137 }
4138 }
4139
4140 @Override
Romain Guyc4d8eb62010-08-18 20:48:33 -07004141 protected boolean onSetAlpha(int alpha) {
Gilles Debunne3dbf55c2010-12-16 10:31:51 -08004142 // Alpha is supported if and only if the drawing can be done in one pass.
4143 // TODO text with spans with a background color currently do not respect this alpha.
4144 if (getBackground() == null) {
Romain Guyc4d8eb62010-08-18 20:48:33 -07004145 mCurrentAlpha = alpha;
4146 final Drawables dr = mDrawables;
4147 if (dr != null) {
Michael Jurka406f0522010-09-15 18:48:48 -07004148 if (dr.mDrawableLeft != null) dr.mDrawableLeft.mutate().setAlpha(alpha);
4149 if (dr.mDrawableTop != null) dr.mDrawableTop.mutate().setAlpha(alpha);
4150 if (dr.mDrawableRight != null) dr.mDrawableRight.mutate().setAlpha(alpha);
4151 if (dr.mDrawableBottom != null) dr.mDrawableBottom.mutate().setAlpha(alpha);
Romain Guyc4d8eb62010-08-18 20:48:33 -07004152 }
4153 return true;
4154 }
Gilles Debunne3dbf55c2010-12-16 10:31:51 -08004155
4156 mCurrentAlpha = 255;
Romain Guyc4d8eb62010-08-18 20:48:33 -07004157 return false;
4158 }
4159
Gilles Debunne86b9c782010-11-11 10:43:48 -08004160 /**
4161 * When a TextView is used to display a useful piece of information to the user (such as a
4162 * contact's address), it should be made selectable, so that the user can select and copy this
4163 * content.
4164 *
4165 * Use {@link #setTextIsSelectable(boolean)} or the
4166 * {@link android.R.styleable#TextView_textIsSelectable} XML attribute to make this TextView
Gilles Debunnee12f9992010-12-17 11:04:55 -08004167 * selectable (text is not selectable by default).
Gilles Debunne6f100f32010-12-13 18:04:20 -08004168 *
4169 * Note that the content of an EditText is always selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004170 *
4171 * @return True if the text displayed in this TextView can be selected by the user.
4172 *
4173 * @attr ref android.R.styleable#TextView_textIsSelectable
4174 */
4175 public boolean isTextSelectable() {
4176 return mTextIsSelectable;
4177 }
4178
4179 /**
4180 * Sets whether or not (default) the content of this view is selectable by the user.
Gilles Debunne6f100f32010-12-13 18:04:20 -08004181 *
Gilles Debunnee12f9992010-12-17 11:04:55 -08004182 * Note that this methods affect the {@link #setFocusable(boolean)},
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004183 * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
4184 * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
4185 * customized.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004186 *
4187 * See {@link #isTextSelectable} for details.
4188 *
4189 * @param selectable Whether or not the content of this TextView should be selectable.
4190 */
4191 public void setTextIsSelectable(boolean selectable) {
4192 if (mTextIsSelectable == selectable) return;
4193
4194 mTextIsSelectable = selectable;
4195
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004196 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004197 setFocusable(selectable);
4198 setClickable(selectable);
4199 setLongClickable(selectable);
4200
4201 // mInputType is already EditorInfo.TYPE_NULL and mInput is null;
4202
4203 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
4204 setText(getText(), selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
4205
4206 // Called by setText above, but safer in case of future code changes
4207 prepareCursorControllers();
4208 }
4209
4210 @Override
4211 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08004212 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004213
Gilles Debunnefb817032011-01-13 13:52:49 -08004214 if (mSingleLine) {
4215 drawableState = super.onCreateDrawableState(extraSpace);
4216 } else {
4217 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004218 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4219 }
4220
Gilles Debunne86b9c782010-11-11 10:43:48 -08004221 if (mTextIsSelectable) {
4222 // Disable pressed state, which was introduced when TextView was made clickable.
4223 // Prevents text color change.
4224 // setClickable(false) would have a similar effect, but it also disables focus changes
4225 // and long press actions, which are both needed by text selection.
4226 final int length = drawableState.length;
4227 for (int i = 0; i < length; i++) {
4228 if (drawableState[i] == R.attr.state_pressed) {
4229 final int[] nonPressedState = new int[length - 1];
4230 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4231 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4232 return nonPressedState;
4233 }
4234 }
4235 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004236
Gilles Debunne86b9c782010-11-11 10:43:48 -08004237 return drawableState;
4238 }
4239
Romain Guyc4d8eb62010-08-18 20:48:33 -07004240 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004241 protected void onDraw(Canvas canvas) {
Michael Jurka2b942d52011-03-01 13:26:11 -08004242 if (mPreDrawState == PREDRAW_DONE) {
4243 final ViewTreeObserver observer = getViewTreeObserver();
4244 observer.removeOnPreDrawListener(this);
4245 mPreDrawState = PREDRAW_NOT_REGISTERED;
4246 }
4247
Romain Guy909cbaf2010-10-13 18:19:48 -07004248 if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return;
4249
Romain Guy986003d2009-03-25 17:42:35 -07004250 restartMarqueeIfNeeded();
4251
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004252 // Draw the background for this view
4253 super.onDraw(canvas);
4254
4255 final int compoundPaddingLeft = getCompoundPaddingLeft();
4256 final int compoundPaddingTop = getCompoundPaddingTop();
4257 final int compoundPaddingRight = getCompoundPaddingRight();
4258 final int compoundPaddingBottom = getCompoundPaddingBottom();
4259 final int scrollX = mScrollX;
4260 final int scrollY = mScrollY;
4261 final int right = mRight;
4262 final int left = mLeft;
4263 final int bottom = mBottom;
4264 final int top = mTop;
4265
4266 final Drawables dr = mDrawables;
4267 if (dr != null) {
4268 /*
4269 * Compound, not extended, because the icon is not clipped
4270 * if the text height is smaller.
4271 */
4272
4273 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
4274 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
4275
Romain Guy3c77d392009-05-20 11:26:50 -07004276 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4277 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004278 if (dr.mDrawableLeft != null) {
4279 canvas.save();
4280 canvas.translate(scrollX + mPaddingLeft,
4281 scrollY + compoundPaddingTop +
4282 (vspace - dr.mDrawableHeightLeft) / 2);
4283 dr.mDrawableLeft.draw(canvas);
4284 canvas.restore();
4285 }
4286
Romain Guy3c77d392009-05-20 11:26:50 -07004287 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4288 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004289 if (dr.mDrawableRight != null) {
4290 canvas.save();
4291 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
4292 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
4293 dr.mDrawableRight.draw(canvas);
4294 canvas.restore();
4295 }
4296
Romain Guy3c77d392009-05-20 11:26:50 -07004297 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4298 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004299 if (dr.mDrawableTop != null) {
4300 canvas.save();
4301 canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
4302 scrollY + mPaddingTop);
4303 dr.mDrawableTop.draw(canvas);
4304 canvas.restore();
4305 }
4306
Romain Guy3c77d392009-05-20 11:26:50 -07004307 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
4308 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004309 if (dr.mDrawableBottom != null) {
4310 canvas.save();
4311 canvas.translate(scrollX + compoundPaddingLeft +
4312 (hspace - dr.mDrawableWidthBottom) / 2,
4313 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
4314 dr.mDrawableBottom.draw(canvas);
4315 canvas.restore();
4316 }
4317 }
4318
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004319 int color = mCurTextColor;
4320
4321 if (mLayout == null) {
4322 assumeLayout();
4323 }
4324
4325 Layout layout = mLayout;
4326 int cursorcolor = color;
4327
4328 if (mHint != null && mText.length() == 0) {
4329 if (mHintTextColor != null) {
4330 color = mCurHintTextColor;
4331 }
4332
4333 layout = mHintLayout;
4334 }
4335
4336 mTextPaint.setColor(color);
Gilles Debunne3dbf55c2010-12-16 10:31:51 -08004337 if (mCurrentAlpha != 255) {
4338 // If set, the alpha will override the color's alpha. Multiply the alphas.
4339 mTextPaint.setAlpha((mCurrentAlpha * Color.alpha(color)) / 255);
4340 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004341 mTextPaint.drawableState = getDrawableState();
4342
4343 canvas.save();
4344 /* Would be faster if we didn't have to do this. Can we chop the
4345 (displayable) text so that we don't need to do this ever?
4346 */
4347
4348 int extendedPaddingTop = getExtendedPaddingTop();
4349 int extendedPaddingBottom = getExtendedPaddingBottom();
4350
4351 float clipLeft = compoundPaddingLeft + scrollX;
4352 float clipTop = extendedPaddingTop + scrollY;
4353 float clipRight = right - left - compoundPaddingRight + scrollX;
4354 float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
4355
4356 if (mShadowRadius != 0) {
4357 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
4358 clipRight += Math.max(0, mShadowDx + mShadowRadius);
4359
4360 clipTop += Math.min(0, mShadowDy - mShadowRadius);
4361 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
4362 }
4363
4364 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
4365
4366 int voffsetText = 0;
4367 int voffsetCursor = 0;
4368
4369 // translate in by our padding
4370 {
4371 /* shortcircuit calling getVerticaOffset() */
4372 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4373 voffsetText = getVerticalOffset(false);
4374 voffsetCursor = getVerticalOffset(true);
4375 }
4376 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
4377 }
4378
4379 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4380 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
4381 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
4382 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
4383 getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
4384 }
4385
4386 if (mMarquee != null && mMarquee.isRunning()) {
4387 canvas.translate(-mMarquee.mScroll, 0.0f);
4388 }
4389 }
4390
4391 Path highlight = null;
4392 int selStart = -1, selEnd = -1;
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004393 boolean drawCursor = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004394
4395 // If there is no movement method, then there can be no selection.
4396 // Check that first and attempt to skip everything having to do with
4397 // the cursor.
4398 // XXX This is not strictly true -- a program could set the
4399 // selection manually if it really wanted to.
4400 if (mMovement != null && (isFocused() || isPressed())) {
Gilles Debunne05336272010-07-09 20:13:45 -07004401 selStart = getSelectionStart();
4402 selEnd = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004403
Gilles Debunne98dbfd42011-01-24 12:54:10 -08004404 if ((isCursorVisible() || mTextIsSelectable) && selStart >= 0 && isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004405 if (mHighlightPath == null)
4406 mHighlightPath = new Path();
4407
4408 if (selStart == selEnd) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08004409 if (!mTextIsSelectable &&
4410 (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004411 if (mHighlightPathBogus) {
4412 mHighlightPath.reset();
4413 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004414 updateCursorsPositions();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004415 mHighlightPathBogus = false;
4416 }
4417
4418 // XXX should pass to skin instead of drawing directly
4419 mHighlightPaint.setColor(cursorcolor);
Gilles Debunne3dbf55c2010-12-16 10:31:51 -08004420 if (mCurrentAlpha != 255) {
4421 mHighlightPaint.setAlpha(
4422 (mCurrentAlpha * Color.alpha(cursorcolor)) / 255);
4423 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004424 mHighlightPaint.setStyle(Paint.Style.STROKE);
Gilles Debunne46b7d442011-02-17 16:03:10 -08004425 highlight = mHighlightPath;
Gilles Debunneeca97a32011-02-23 17:48:28 -08004426 drawCursor = mCursorCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004427 }
4428 } else {
4429 if (mHighlightPathBogus) {
4430 mHighlightPath.reset();
4431 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4432 mHighlightPathBogus = false;
4433 }
4434
4435 // XXX should pass to skin instead of drawing directly
4436 mHighlightPaint.setColor(mHighlightColor);
Gilles Debunne3dbf55c2010-12-16 10:31:51 -08004437 if (mCurrentAlpha != 255) {
4438 mHighlightPaint.setAlpha(
4439 (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
4440 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004441 mHighlightPaint.setStyle(Paint.Style.FILL);
4442
4443 highlight = mHighlightPath;
4444 }
4445 }
4446 }
4447
4448 /* Comment out until we decide what to do about animations
4449 boolean isLinearTextOn = false;
4450 if (currentTransformation != null) {
4451 isLinearTextOn = mTextPaint.isLinearTextOn();
4452 Matrix m = currentTransformation.getMatrix();
4453 if (!m.isIdentity()) {
4454 // mTextPaint.setLinearTextOn(true);
4455 }
4456 }
4457 */
4458
4459 final InputMethodState ims = mInputMethodState;
Gilles Debunne12d91ce2010-12-10 11:36:29 -08004460 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004461 if (ims != null && ims.mBatchEditNesting == 0) {
4462 InputMethodManager imm = InputMethodManager.peekInstance();
4463 if (imm != null) {
4464 if (imm.isActive(this)) {
4465 boolean reported = false;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004466 if (ims.mContentChanged || ims.mSelectionModeChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004467 // We are in extract mode and the content has changed
4468 // in some way... just report complete new text to the
4469 // input method.
4470 reported = reportExtractedText();
4471 }
4472 if (!reported && highlight != null) {
4473 int candStart = -1;
4474 int candEnd = -1;
4475 if (mText instanceof Spannable) {
4476 Spannable sp = (Spannable)mText;
4477 candStart = EditableInputConnection.getComposingSpanStart(sp);
4478 candEnd = EditableInputConnection.getComposingSpanEnd(sp);
4479 }
4480 imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
4481 }
4482 }
4483
4484 if (imm.isWatchingCursor(this) && highlight != null) {
4485 highlight.computeBounds(ims.mTmpRectF, true);
4486 ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
4487
4488 canvas.getMatrix().mapPoints(ims.mTmpOffset);
4489 ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
4490
Gilles Debunne12d91ce2010-12-10 11:36:29 -08004491 ims.mTmpRectF.offset(0, cursorOffsetVertical);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004492
4493 ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
4494 (int)(ims.mTmpRectF.top + 0.5),
4495 (int)(ims.mTmpRectF.right + 0.5),
4496 (int)(ims.mTmpRectF.bottom + 0.5));
4497
4498 imm.updateCursor(this,
4499 ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
4500 ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
4501 }
4502 }
4503 }
4504
Gilles Debunne12d91ce2010-12-10 11:36:29 -08004505 if (mCorrectionHighlighter != null) {
4506 mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
4507 }
4508
Gilles Debunne46b7d442011-02-17 16:03:10 -08004509 if (drawCursor) {
4510 drawCursor(canvas, cursorOffsetVertical);
4511 // Rely on the drawable entirely, do not draw the cursor line.
4512 // Has to be done after the IMM related code above which relies on the highlight.
4513 highlight = null;
4514 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004515
Gilles Debunne12d91ce2010-12-10 11:36:29 -08004516 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004517
Romain Guyc2303192009-04-03 17:37:18 -07004518 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
4519 canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
Gilles Debunne12d91ce2010-12-10 11:36:29 -08004520 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07004521 }
4522
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004523 /* Comment out until we decide what to do about animations
4524 if (currentTransformation != null) {
4525 mTextPaint.setLinearTextOn(isLinearTextOn);
4526 }
4527 */
4528
4529 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04004530 }
4531
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004532 private void updateCursorsPositions() {
Gilles Debunneeca97a32011-02-23 17:48:28 -08004533 if (mCursorDrawableRes == 0) {
4534 mCursorCount = 0;
4535 return;
4536 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004537
4538 final int offset = getSelectionStart();
4539 final int line = mLayout.getLineForOffset(offset);
4540 final int top = mLayout.getLineTop(line);
4541 final int bottom = mLayout.getLineTop(line + 1);
4542
4543 mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
4544
4545 int middle = bottom;
4546 if (mCursorCount == 2) {
4547 // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
4548 middle = (top + bottom) >> 1;
4549 }
4550
4551 updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
4552
4553 if (mCursorCount == 2) {
4554 updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
4555 }
4556 }
4557
4558 private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
4559 if (mCursorDrawable[cursorIndex] == null)
4560 mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
4561
4562 if (mTempRect == null) mTempRect = new Rect();
4563
4564 mCursorDrawable[cursorIndex].getPadding(mTempRect);
4565 final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
4566 horizontal = Math.max(0.5f, horizontal - 0.5f);
4567 final int left = (int) (horizontal) - mTempRect.left;
4568 mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
4569 bottom + mTempRect.bottom);
4570 }
4571
4572 private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
4573 final boolean translate = cursorOffsetVertical != 0;
4574 if (translate) canvas.translate(0, cursorOffsetVertical);
4575 for (int i = 0; i < mCursorCount; i++) {
4576 mCursorDrawable[i].draw(canvas);
4577 }
4578 if (translate) canvas.translate(0, -cursorOffsetVertical);
4579 }
4580
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004581 @Override
4582 public void getFocusedRect(Rect r) {
4583 if (mLayout == null) {
4584 super.getFocusedRect(r);
4585 return;
4586 }
4587
4588 int sel = getSelectionEnd();
4589 if (sel < 0) {
4590 super.getFocusedRect(r);
4591 return;
4592 }
4593
4594 int line = mLayout.getLineForOffset(sel);
4595 r.top = mLayout.getLineTop(line);
4596 r.bottom = mLayout.getLineBottom(line);
4597
4598 r.left = (int) mLayout.getPrimaryHorizontal(sel);
4599 r.right = r.left + 1;
4600
4601 // Adjust for padding and gravity.
4602 int paddingLeft = getCompoundPaddingLeft();
4603 int paddingTop = getExtendedPaddingTop();
4604 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4605 paddingTop += getVerticalOffset(false);
4606 }
4607 r.offset(paddingLeft, paddingTop);
4608 }
4609
4610 /**
4611 * Return the number of lines of text, or 0 if the internal Layout has not
4612 * been built.
4613 */
4614 public int getLineCount() {
4615 return mLayout != null ? mLayout.getLineCount() : 0;
4616 }
4617
4618 /**
4619 * Return the baseline for the specified line (0...getLineCount() - 1)
4620 * If bounds is not null, return the top, left, right, bottom extents
4621 * of the specified line in it. If the internal Layout has not been built,
4622 * return 0 and set bounds to (0, 0, 0, 0)
4623 * @param line which line to examine (0..getLineCount() - 1)
4624 * @param bounds Optional. If not null, it returns the extent of the line
4625 * @return the Y-coordinate of the baseline
4626 */
4627 public int getLineBounds(int line, Rect bounds) {
4628 if (mLayout == null) {
4629 if (bounds != null) {
4630 bounds.set(0, 0, 0, 0);
4631 }
4632 return 0;
4633 }
4634 else {
4635 int baseline = mLayout.getLineBounds(line, bounds);
4636
4637 int voffset = getExtendedPaddingTop();
4638 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4639 voffset += getVerticalOffset(true);
4640 }
4641 if (bounds != null) {
4642 bounds.offset(getCompoundPaddingLeft(), voffset);
4643 }
4644 return baseline + voffset;
4645 }
4646 }
4647
4648 @Override
4649 public int getBaseline() {
4650 if (mLayout == null) {
4651 return super.getBaseline();
4652 }
4653
4654 int voffset = 0;
4655 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4656 voffset = getVerticalOffset(true);
4657 }
4658
4659 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
4660 }
4661
4662 @Override
4663 public boolean onKeyDown(int keyCode, KeyEvent event) {
4664 int which = doKeyDown(keyCode, event, null);
4665 if (which == 0) {
4666 // Go through default dispatching.
4667 return super.onKeyDown(keyCode, event);
4668 }
4669
4670 return true;
4671 }
4672
4673 @Override
4674 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004675 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004676
4677 int which = doKeyDown(keyCode, down, event);
4678 if (which == 0) {
4679 // Go through default dispatching.
4680 return super.onKeyMultiple(keyCode, repeatCount, event);
4681 }
4682 if (which == -1) {
4683 // Consumed the whole thing.
4684 return true;
4685 }
4686
4687 repeatCount--;
4688
4689 // We are going to dispatch the remaining events to either the input
4690 // or movement method. To do this, we will just send a repeated stream
4691 // of down and up events until we have done the complete repeatCount.
4692 // It would be nice if those interfaces had an onKeyMultiple() method,
4693 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07004694 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004695 if (which == 1) {
4696 mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4697 while (--repeatCount > 0) {
4698 mInput.onKeyDown(this, (Editable)mText, keyCode, down);
4699 mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4700 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08004701 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004702
4703 } else if (which == 2) {
4704 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4705 while (--repeatCount > 0) {
4706 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
4707 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4708 }
4709 }
4710
4711 return true;
4712 }
4713
4714 /**
4715 * Returns true if pressing ENTER in this field advances focus instead
4716 * of inserting the character. This is true mostly in single-line fields,
4717 * but also in mail addresses and subjects which will display on multiple
4718 * lines but where it doesn't make sense to insert newlines.
4719 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08004720 private boolean shouldAdvanceFocusOnEnter() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004721 if (mInput == null) {
4722 return false;
4723 }
4724
4725 if (mSingleLine) {
4726 return true;
4727 }
4728
4729 if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4730 int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08004731 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
4732 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004733 return true;
4734 }
4735 }
4736
4737 return false;
4738 }
4739
Jeff Brown4e6319b2010-12-13 10:36:51 -08004740 /**
4741 * Returns true if pressing TAB in this field advances focus instead
4742 * of inserting the character. Insert tabs only in multi-line editors.
4743 */
4744 private boolean shouldAdvanceFocusOnTab() {
4745 if (mInput != null && !mSingleLine) {
4746 if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4747 int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
4748 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
4749 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
4750 return false;
4751 }
4752 }
4753 }
4754 return true;
4755 }
4756
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004757 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
4758 if (!isEnabled()) {
4759 return 0;
4760 }
4761
4762 switch (keyCode) {
4763 case KeyEvent.KEYCODE_ENTER:
Gilles Debunnecf1e9252010-10-07 20:46:03 -07004764 mEnterKeyIsDown = true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08004765 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004766 // When mInputContentType is set, we know that we are
4767 // running in a "modern" cupcake environment, so don't need
4768 // to worry about the application trying to capture
4769 // enter key events.
4770 if (mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004771 // If there is an action listener, given them a
4772 // chance to consume the event.
4773 if (mInputContentType.onEditorActionListener != null &&
4774 mInputContentType.onEditorActionListener.onEditorAction(
4775 this, EditorInfo.IME_NULL, event)) {
4776 mInputContentType.enterDown = true;
4777 // We are consuming the enter key for them.
4778 return -1;
4779 }
4780 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08004781
The Android Open Source Project10592532009-03-18 17:39:46 -07004782 // If our editor should move focus when enter is pressed, or
4783 // this is a generated event from an IME action button, then
4784 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08004785 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07004786 || shouldAdvanceFocusOnEnter()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05004787 if (mOnClickListener != null) {
4788 return 0;
4789 }
The Android Open Source Project10592532009-03-18 17:39:46 -07004790 return -1;
4791 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004792 }
The Android Open Source Project10592532009-03-18 17:39:46 -07004793 break;
4794
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004795 case KeyEvent.KEYCODE_DPAD_CENTER:
Gilles Debunnecf1e9252010-10-07 20:46:03 -07004796 mDPadCenterIsDown = true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08004797 if (event.hasNoModifiers()) {
4798 if (shouldAdvanceFocusOnEnter()) {
4799 return 0;
4800 }
4801 }
4802 break;
4803
4804 case KeyEvent.KEYCODE_TAB:
4805 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
4806 if (shouldAdvanceFocusOnTab()) {
4807 return 0;
4808 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004809 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004810 break;
4811
4812 // Has to be done on key down (and not on key up) to correctly be intercepted.
4813 case KeyEvent.KEYCODE_BACK:
4814 if (mSelectionActionMode != null) {
4815 stopSelectionActionMode();
4816 return -1;
4817 }
4818 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004819 }
4820
4821 if (mInput != null) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08004822 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004823
4824 boolean doDown = true;
4825 if (otherEvent != null) {
4826 try {
4827 beginBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08004828 final boolean handled = mInput.onKeyOther(this, (Editable) mText, otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08004829 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004830 doDown = false;
4831 if (handled) {
4832 return -1;
4833 }
4834 } catch (AbstractMethodError e) {
4835 // onKeyOther was added after 1.0, so if it isn't
4836 // implemented we need to try to dispatch as a regular down.
4837 } finally {
4838 endBatchEdit();
4839 }
4840 }
4841
4842 if (doDown) {
4843 beginBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08004844 final boolean handled = mInput.onKeyDown(this, (Editable) mText, keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004845 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08004846 hideErrorIfUnchanged();
4847 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004848 }
4849 }
4850
4851 // bug 650865: sometimes we get a key event before a layout.
4852 // don't try to move around if we don't know the layout.
4853
4854 if (mMovement != null && mLayout != null) {
4855 boolean doDown = true;
4856 if (otherEvent != null) {
4857 try {
4858 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
4859 otherEvent);
4860 doDown = false;
4861 if (handled) {
4862 return -1;
4863 }
4864 } catch (AbstractMethodError e) {
4865 // onKeyOther was added after 1.0, so if it isn't
4866 // implemented we need to try to dispatch as a regular down.
4867 }
4868 }
4869 if (doDown) {
4870 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
4871 return 2;
4872 }
4873 }
4874
4875 return 0;
4876 }
4877
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08004878 /**
4879 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
4880 * can be recorded.
4881 * @hide
4882 */
4883 public void resetErrorChangedFlag() {
4884 /*
4885 * Keep track of what the error was before doing the input
4886 * so that if an input filter changed the error, we leave
4887 * that error showing. Otherwise, we take down whatever
4888 * error was showing when the user types something.
4889 */
4890 mErrorWasChanged = false;
4891 }
4892
4893 /**
4894 * @hide
4895 */
4896 public void hideErrorIfUnchanged() {
4897 if (mError != null && !mErrorWasChanged) {
4898 setError(null, null);
4899 }
4900 }
4901
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004902 @Override
4903 public boolean onKeyUp(int keyCode, KeyEvent event) {
4904 if (!isEnabled()) {
4905 return super.onKeyUp(keyCode, event);
4906 }
4907
4908 switch (keyCode) {
4909 case KeyEvent.KEYCODE_DPAD_CENTER:
Gilles Debunnecf1e9252010-10-07 20:46:03 -07004910 mDPadCenterIsDown = false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08004911 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004912 /*
4913 * If there is a click listener, just call through to
4914 * super, which will invoke it.
4915 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08004916 * If there isn't a click listener, try to show the soft
4917 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004918 * call performClick(), but that won't do anything in
4919 * this case.)
4920 */
4921 if (mOnClickListener == null) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08004922 if (mMovement != null && mText instanceof Editable
4923 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08004924 InputMethodManager imm = InputMethodManager.peekInstance();
4925 if (imm != null) imm.showSoftInput(this, 0);
Jeff Brown4e6319b2010-12-13 10:36:51 -08004926 }
4927 }
4928 }
4929 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004930
Jeff Brown4e6319b2010-12-13 10:36:51 -08004931 case KeyEvent.KEYCODE_ENTER:
4932 mEnterKeyIsDown = false;
4933 if (event.hasNoModifiers()) {
4934 if (mInputContentType != null
4935 && mInputContentType.onEditorActionListener != null
4936 && mInputContentType.enterDown) {
4937 mInputContentType.enterDown = false;
4938 if (mInputContentType.onEditorActionListener.onEditorAction(
4939 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004940 return true;
4941 }
4942 }
4943
Jeff Brown4e6319b2010-12-13 10:36:51 -08004944 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
4945 || shouldAdvanceFocusOnEnter()) {
4946 /*
4947 * If there is a click listener, just call through to
4948 * super, which will invoke it.
4949 *
4950 * If there isn't a click listener, try to advance focus,
4951 * but still call through to super, which will reset the
4952 * pressed state and longpress state. (It will also
4953 * call performClick(), but that won't do anything in
4954 * this case.)
4955 */
4956 if (mOnClickListener == null) {
4957 View v = focusSearch(FOCUS_DOWN);
4958
4959 if (v != null) {
4960 if (!v.requestFocus(FOCUS_DOWN)) {
4961 throw new IllegalStateException(
4962 "focus search returned a view " +
4963 "that wasn't able to take focus!");
4964 }
4965
4966 /*
4967 * Return true because we handled the key; super
4968 * will return false because there was no click
4969 * listener.
4970 */
4971 super.onKeyUp(keyCode, event);
4972 return true;
4973 } else if ((event.getFlags()
4974 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
4975 // No target for next focus, but make sure the IME
4976 // if this came from it.
4977 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004978 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08004979 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4980 }
4981 }
4982 }
4983 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004984 return super.onKeyUp(keyCode, event);
4985 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004986 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004987 }
4988
4989 if (mInput != null)
4990 if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
4991 return true;
4992
4993 if (mMovement != null && mLayout != null)
4994 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
4995 return true;
4996
4997 return super.onKeyUp(keyCode, event);
4998 }
4999
5000 @Override public boolean onCheckIsTextEditor() {
5001 return mInputType != EditorInfo.TYPE_NULL;
5002 }
5003
5004 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03005005 if (onCheckIsTextEditor() && isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005006 if (mInputMethodState == null) {
5007 mInputMethodState = new InputMethodState();
5008 }
5009 outAttrs.inputType = mInputType;
5010 if (mInputContentType != null) {
5011 outAttrs.imeOptions = mInputContentType.imeOptions;
5012 outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
5013 outAttrs.actionLabel = mInputContentType.imeActionLabel;
5014 outAttrs.actionId = mInputContentType.imeActionId;
5015 outAttrs.extras = mInputContentType.extras;
5016 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005017 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005018 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005019 if (focusSearch(FOCUS_DOWN) != null) {
5020 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5021 }
5022 if (focusSearch(FOCUS_UP) != null) {
5023 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5024 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005025 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5026 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005027 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005028 // An action has not been set, but the enter key will move to
5029 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005030 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005031 } else {
5032 // An action has not been set, and there is no focus to move
5033 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005034 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005035 }
5036 if (!shouldAdvanceFocusOnEnter()) {
5037 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005038 }
5039 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08005040 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005041 // Multi-line text editors should always show an enter key.
5042 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5043 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005044 outAttrs.hintText = mHint;
5045 if (mText instanceof Editable) {
5046 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07005047 outAttrs.initialSelStart = getSelectionStart();
5048 outAttrs.initialSelEnd = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005049 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
5050 return ic;
5051 }
5052 }
5053 return null;
5054 }
5055
5056 /**
5057 * If this TextView contains editable content, extract a portion of it
5058 * based on the information in <var>request</var> in to <var>outText</var>.
5059 * @return Returns true if the text was successfully extracted, else false.
5060 */
5061 public boolean extractText(ExtractedTextRequest request,
5062 ExtractedText outText) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005063 return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
5064 EXTRACT_UNKNOWN, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005065 }
5066
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005067 static final int EXTRACT_NOTHING = -2;
5068 static final int EXTRACT_UNKNOWN = -1;
5069
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005070 boolean extractTextInternal(ExtractedTextRequest request,
5071 int partialStartOffset, int partialEndOffset, int delta,
5072 ExtractedText outText) {
5073 final CharSequence content = mText;
5074 if (content != null) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005075 if (partialStartOffset != EXTRACT_NOTHING) {
5076 final int N = content.length();
5077 if (partialStartOffset < 0) {
5078 outText.partialStartOffset = outText.partialEndOffset = -1;
5079 partialStartOffset = 0;
5080 partialEndOffset = N;
5081 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01005082 // Now use the delta to determine the actual amount of text
5083 // we need.
5084 partialEndOffset += delta;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005085 // Adjust offsets to ensure we contain full spans.
5086 if (content instanceof Spanned) {
5087 Spanned spanned = (Spanned)content;
5088 Object[] spans = spanned.getSpans(partialStartOffset,
5089 partialEndOffset, ParcelableSpan.class);
5090 int i = spans.length;
5091 while (i > 0) {
5092 i--;
5093 int j = spanned.getSpanStart(spans[i]);
5094 if (j < partialStartOffset) partialStartOffset = j;
5095 j = spanned.getSpanEnd(spans[i]);
5096 if (j > partialEndOffset) partialEndOffset = j;
5097 }
5098 }
5099 outText.partialStartOffset = partialStartOffset;
Viktor Yakovel964be412010-02-17 08:35:57 +01005100 outText.partialEndOffset = partialEndOffset - delta;
5101
Eric Fischer32929412009-12-14 17:33:11 -08005102 if (partialStartOffset > N) {
5103 partialStartOffset = N;
5104 } else if (partialStartOffset < 0) {
5105 partialStartOffset = 0;
5106 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005107 if (partialEndOffset > N) {
5108 partialEndOffset = N;
5109 } else if (partialEndOffset < 0) {
5110 partialEndOffset = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005111 }
5112 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005113 if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
5114 outText.text = content.subSequence(partialStartOffset,
5115 partialEndOffset);
5116 } else {
5117 outText.text = TextUtils.substring(content, partialStartOffset,
5118 partialEndOffset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005119 }
Viktor Yakovel970a138c92010-02-12 16:00:41 +01005120 } else {
5121 outText.partialStartOffset = 0;
5122 outText.partialEndOffset = 0;
5123 outText.text = "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005124 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005125 outText.flags = 0;
5126 if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
5127 outText.flags |= ExtractedText.FLAG_SELECTING;
5128 }
5129 if (mSingleLine) {
5130 outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005131 }
5132 outText.startOffset = 0;
Gilles Debunne05336272010-07-09 20:13:45 -07005133 outText.selectionStart = getSelectionStart();
5134 outText.selectionEnd = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005135 return true;
5136 }
5137 return false;
5138 }
5139
5140 boolean reportExtractedText() {
5141 final InputMethodState ims = mInputMethodState;
Marco Nelissen9ea92312009-05-14 15:11:23 -07005142 if (ims != null) {
5143 final boolean contentChanged = ims.mContentChanged;
5144 if (contentChanged || ims.mSelectionModeChanged) {
5145 ims.mContentChanged = false;
5146 ims.mSelectionModeChanged = false;
5147 final ExtractedTextRequest req = mInputMethodState.mExtracting;
5148 if (req != null) {
5149 InputMethodManager imm = InputMethodManager.peekInstance();
5150 if (imm != null) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07005151 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
Marco Nelissen9ea92312009-05-14 15:11:23 -07005152 + ims.mChangedStart + " end=" + ims.mChangedEnd
5153 + " delta=" + ims.mChangedDelta);
5154 if (ims.mChangedStart < 0 && !contentChanged) {
5155 ims.mChangedStart = EXTRACT_NOTHING;
5156 }
5157 if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
5158 ims.mChangedDelta, ims.mTmpExtracted)) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07005159 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
Marco Nelissen9ea92312009-05-14 15:11:23 -07005160 + ims.mTmpExtracted.partialStartOffset
5161 + " end=" + ims.mTmpExtracted.partialEndOffset
5162 + ": " + ims.mTmpExtracted.text);
5163 imm.updateExtractedText(this, req.token,
5164 mInputMethodState.mTmpExtracted);
Viktor Yakovel964be412010-02-17 08:35:57 +01005165 ims.mChangedStart = EXTRACT_UNKNOWN;
5166 ims.mChangedEnd = EXTRACT_UNKNOWN;
5167 ims.mChangedDelta = 0;
5168 ims.mContentChanged = false;
Marco Nelissen9ea92312009-05-14 15:11:23 -07005169 return true;
5170 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005171 }
5172 }
5173 }
5174 }
5175 return false;
5176 }
5177
5178 /**
5179 * This is used to remove all style-impacting spans from text before new
5180 * extracted text is being replaced into it, so that we don't have any
5181 * lingering spans applied during the replace.
5182 */
5183 static void removeParcelableSpans(Spannable spannable, int start, int end) {
5184 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5185 int i = spans.length;
5186 while (i > 0) {
5187 i--;
5188 spannable.removeSpan(spans[i]);
5189 }
5190 }
5191
5192 /**
5193 * Apply to this text view the given extracted text, as previously
5194 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5195 */
5196 public void setExtractedText(ExtractedText text) {
5197 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005198 if (text.text != null) {
5199 if (content == null) {
5200 setText(text.text, TextView.BufferType.EDITABLE);
5201 } else if (text.partialStartOffset < 0) {
5202 removeParcelableSpans(content, 0, content.length());
5203 content.replace(0, content.length(), text.text);
5204 } else {
5205 final int N = content.length();
5206 int start = text.partialStartOffset;
5207 if (start > N) start = N;
5208 int end = text.partialEndOffset;
5209 if (end > N) end = N;
5210 removeParcelableSpans(content, start, end);
5211 content.replace(start, end, text.text);
5212 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005213 }
5214
5215 // Now set the selection position... make sure it is in range, to
5216 // avoid crashes. If this is a partial update, it is possible that
5217 // the underlying text may have changed, causing us problems here.
5218 // Also we just don't want to trust clients to do the right thing.
5219 Spannable sp = (Spannable)getText();
5220 final int N = sp.length();
5221 int start = text.selectionStart;
5222 if (start < 0) start = 0;
5223 else if (start > N) start = N;
5224 int end = text.selectionEnd;
5225 if (end < 0) end = 0;
5226 else if (end > N) end = N;
5227 Selection.setSelection(sp, start, end);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005228
5229 // Finally, update the selection mode.
5230 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5231 MetaKeyKeyListener.startSelecting(this, sp);
5232 } else {
5233 MetaKeyKeyListener.stopSelecting(this, sp);
5234 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005235 }
5236
5237 /**
5238 * @hide
5239 */
5240 public void setExtracting(ExtractedTextRequest req) {
5241 if (mInputMethodState != null) {
5242 mInputMethodState.mExtracting = req;
5243 }
Gilles Debunned94f8c52011-01-10 11:29:15 -08005244 // This stops a possible text selection mode. Maybe not intended.
Adam Powellba0a2c32010-09-28 17:41:23 -07005245 hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005246 }
5247
5248 /**
5249 * Called by the framework in response to a text completion from
5250 * the current input method, provided by it calling
5251 * {@link InputConnection#commitCompletion
5252 * InputConnection.commitCompletion()}. The default implementation does
5253 * nothing; text views that are supporting auto-completion should override
5254 * this to do their desired behavior.
5255 *
5256 * @param text The auto complete text the user has selected.
5257 */
5258 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005259 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005260 }
5261
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005262 /**
5263 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5264 * a dictionnary) from the current input method, provided by it calling
5265 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5266 * implementation flashes the background of the corrected word to provide feedback to the user.
5267 *
5268 * @param info The auto correct info about the text that was corrected.
5269 */
5270 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005271 if (mCorrectionHighlighter == null) {
5272 mCorrectionHighlighter = new CorrectionHighlighter();
5273 } else {
5274 mCorrectionHighlighter.invalidate(false);
5275 }
5276
5277 mCorrectionHighlighter.highlight(info);
5278 }
5279
5280 private class CorrectionHighlighter {
5281 private final Path mPath = new Path();
5282 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
5283 private int mStart, mEnd;
5284 private long mFadingStartTime;
5285 private final static int FADE_OUT_DURATION = 400;
5286
5287 public CorrectionHighlighter() {
5288 mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
5289 mPaint.setStyle(Paint.Style.FILL);
5290 }
5291
5292 public void highlight(CorrectionInfo info) {
5293 mStart = info.getOffset();
5294 mEnd = mStart + info.getNewText().length();
5295 mFadingStartTime = SystemClock.uptimeMillis();
5296
5297 if (mStart < 0 || mEnd < 0) {
5298 stopAnimation();
5299 }
5300 }
5301
5302 public void draw(Canvas canvas, int cursorOffsetVertical) {
5303 if (updatePath() && updatePaint()) {
5304 if (cursorOffsetVertical != 0) {
5305 canvas.translate(0, cursorOffsetVertical);
5306 }
5307
5308 canvas.drawPath(mPath, mPaint);
5309
5310 if (cursorOffsetVertical != 0) {
5311 canvas.translate(0, -cursorOffsetVertical);
5312 }
5313 invalidate(true);
5314 } else {
5315 stopAnimation();
5316 invalidate(false);
5317 }
5318 }
5319
5320 private boolean updatePaint() {
5321 final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
5322 if (duration > FADE_OUT_DURATION) return false;
5323
5324 final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
5325 final int highlightColorAlpha = Color.alpha(mHighlightColor);
5326 final int color = (mHighlightColor & 0x00FFFFFF) +
5327 ((int) (highlightColorAlpha * coef) << 24);
5328 mPaint.setColor(color);
5329 return true;
5330 }
5331
5332 private boolean updatePath() {
5333 final Layout layout = TextView.this.mLayout;
5334 if (layout == null) return false;
5335
5336 // Update in case text is edited while the animation is run
5337 final int length = mText.length();
5338 int start = Math.min(length, mStart);
5339 int end = Math.min(length, mEnd);
5340
5341 mPath.reset();
5342 TextView.this.mLayout.getSelectionPath(start, end, mPath);
5343 return true;
5344 }
5345
5346 private void invalidate(boolean delayed) {
5347 if (TextView.this.mLayout == null) return;
5348
5349 synchronized (sTempRect) {
5350 mPath.computeBounds(sTempRect, false);
5351
5352 int left = getCompoundPaddingLeft();
5353 int top = getExtendedPaddingTop() + getVerticalOffset(true);
5354
5355 if (delayed) {
5356 TextView.this.postInvalidateDelayed(16, // 60 Hz update
5357 left + (int) sTempRect.left, top + (int) sTempRect.top,
5358 left + (int) sTempRect.right, top + (int) sTempRect.bottom);
5359 } else {
5360 TextView.this.postInvalidate((int) sTempRect.left, (int) sTempRect.top,
5361 (int) sTempRect.right, (int) sTempRect.bottom);
5362 }
5363 }
5364 }
5365
5366 private void stopAnimation() {
5367 TextView.this.mCorrectionHighlighter = null;
5368 }
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005369 }
5370
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005371 public void beginBatchEdit() {
Adam Powell965b9692010-10-21 18:44:32 -07005372 mInBatchEditControllers = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005373 final InputMethodState ims = mInputMethodState;
5374 if (ims != null) {
5375 int nesting = ++ims.mBatchEditNesting;
5376 if (nesting == 1) {
5377 ims.mCursorChanged = false;
5378 ims.mChangedDelta = 0;
5379 if (ims.mContentChanged) {
5380 // We already have a pending change from somewhere else,
5381 // so turn this into a full update.
5382 ims.mChangedStart = 0;
5383 ims.mChangedEnd = mText.length();
5384 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005385 ims.mChangedStart = EXTRACT_UNKNOWN;
5386 ims.mChangedEnd = EXTRACT_UNKNOWN;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005387 ims.mContentChanged = false;
5388 }
5389 onBeginBatchEdit();
5390 }
5391 }
5392 }
5393
5394 public void endBatchEdit() {
Adam Powell965b9692010-10-21 18:44:32 -07005395 mInBatchEditControllers = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005396 final InputMethodState ims = mInputMethodState;
5397 if (ims != null) {
5398 int nesting = --ims.mBatchEditNesting;
5399 if (nesting == 0) {
5400 finishBatchEdit(ims);
5401 }
5402 }
5403 }
5404
5405 void ensureEndedBatchEdit() {
5406 final InputMethodState ims = mInputMethodState;
5407 if (ims != null && ims.mBatchEditNesting != 0) {
5408 ims.mBatchEditNesting = 0;
5409 finishBatchEdit(ims);
5410 }
5411 }
5412
5413 void finishBatchEdit(final InputMethodState ims) {
5414 onEndBatchEdit();
5415
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005416 if (ims.mContentChanged || ims.mSelectionModeChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005417 updateAfterEdit();
5418 reportExtractedText();
5419 } else if (ims.mCursorChanged) {
5420 // Cheezy way to get us to report the current cursor location.
5421 invalidateCursor();
5422 }
5423 }
5424
5425 void updateAfterEdit() {
5426 invalidate();
Gilles Debunne05336272010-07-09 20:13:45 -07005427 int curs = getSelectionStart();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005428
Gilles Debunne3d010062011-02-18 14:16:41 -08005429 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005430 registerForPreDraw();
5431 }
5432
5433 if (curs >= 0) {
5434 mHighlightPathBogus = true;
Gilles Debunne3d010062011-02-18 14:16:41 -08005435 makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005436 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07005437
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005438 checkForResize();
5439 }
5440
5441 /**
5442 * Called by the framework in response to a request to begin a batch
5443 * of edit operations through a call to link {@link #beginBatchEdit()}.
5444 */
5445 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005446 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005447 }
5448
5449 /**
5450 * Called by the framework in response to a request to end a batch
5451 * of edit operations through a call to link {@link #endBatchEdit}.
5452 */
5453 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005454 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005455 }
5456
5457 /**
5458 * Called by the framework in response to a private command from the
5459 * current method, provided by it calling
5460 * {@link InputConnection#performPrivateCommand
5461 * InputConnection.performPrivateCommand()}.
5462 *
5463 * @param action The action name of the command.
5464 * @param data Any additional data for the command. This may be null.
5465 * @return Return true if you handled the command, else false.
5466 */
5467 public boolean onPrivateIMECommand(String action, Bundle data) {
5468 return false;
5469 }
5470
5471 private void nullLayouts() {
5472 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5473 mSavedLayout = (BoringLayout) mLayout;
5474 }
5475 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5476 mSavedHintLayout = (BoringLayout) mHintLayout;
5477 }
5478
5479 mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07005480
5481 // Since it depends on the value of mLayout
5482 prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005483 }
5484
5485 /**
5486 * Make a new Layout based on the already-measured size of the view,
5487 * on the assumption that it was measured correctly at some point.
5488 */
5489 private void assumeLayout() {
5490 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5491
5492 if (width < 1) {
5493 width = 0;
5494 }
5495
5496 int physicalWidth = width;
5497
5498 if (mHorizontallyScrolling) {
5499 width = VERY_WIDE;
5500 }
5501
5502 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5503 physicalWidth, false);
5504 }
5505
5506 /**
5507 * The width passed in is now the desired layout width,
5508 * not the full view width with padding.
5509 * {@hide}
5510 */
5511 protected void makeNewLayout(int w, int hintWidth,
5512 BoringLayout.Metrics boring,
5513 BoringLayout.Metrics hintBoring,
5514 int ellipsisWidth, boolean bringIntoView) {
5515 stopMarquee();
5516
5517 mHighlightPathBogus = true;
5518
5519 if (w < 0) {
5520 w = 0;
5521 }
5522 if (hintWidth < 0) {
5523 hintWidth = 0;
5524 }
5525
5526 Layout.Alignment alignment;
5527 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
5528 case Gravity.CENTER_HORIZONTAL:
5529 alignment = Layout.Alignment.ALIGN_CENTER;
5530 break;
5531
5532 case Gravity.RIGHT:
Doug Feltc982f602010-05-25 11:51:40 -07005533 // Note, Layout resolves ALIGN_OPPOSITE to left or
5534 // right based on the paragraph direction.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005535 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5536 break;
5537
5538 default:
5539 alignment = Layout.Alignment.ALIGN_NORMAL;
5540 }
5541
Romain Guy4dc4f732009-06-19 15:16:40 -07005542 boolean shouldEllipsize = mEllipsize != null && mInput == null;
5543
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005544 if (mText instanceof Spannable) {
5545 mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
5546 alignment, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07005547 mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005548 ellipsisWidth);
5549 } else {
5550 if (boring == UNKNOWN_BORING) {
Gilles Debunne81f08082011-02-17 14:07:19 -08005551 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005552 if (boring != null) {
5553 mBoring = boring;
5554 }
5555 }
5556
5557 if (boring != null) {
5558 if (boring.width <= w &&
5559 (mEllipsize == null || boring.width <= ellipsisWidth)) {
5560 if (mSavedLayout != null) {
5561 mLayout = mSavedLayout.
5562 replaceOrMake(mTransformed, mTextPaint,
5563 w, alignment, mSpacingMult, mSpacingAdd,
5564 boring, mIncludePad);
5565 } else {
5566 mLayout = BoringLayout.make(mTransformed, mTextPaint,
5567 w, alignment, mSpacingMult, mSpacingAdd,
5568 boring, mIncludePad);
5569 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005570
5571 mSavedLayout = (BoringLayout) mLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07005572 } else if (shouldEllipsize && boring.width <= w) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005573 if (mSavedLayout != null) {
5574 mLayout = mSavedLayout.
5575 replaceOrMake(mTransformed, mTextPaint,
5576 w, alignment, mSpacingMult, mSpacingAdd,
5577 boring, mIncludePad, mEllipsize,
5578 ellipsisWidth);
5579 } else {
5580 mLayout = BoringLayout.make(mTransformed, mTextPaint,
5581 w, alignment, mSpacingMult, mSpacingAdd,
5582 boring, mIncludePad, mEllipsize,
5583 ellipsisWidth);
5584 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005585 } else if (shouldEllipsize) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005586 mLayout = new StaticLayout(mTransformed,
5587 0, mTransformed.length(),
5588 mTextPaint, w, alignment, mSpacingMult,
5589 mSpacingAdd, mIncludePad, mEllipsize,
5590 ellipsisWidth);
5591 } else {
5592 mLayout = new StaticLayout(mTransformed, mTextPaint,
5593 w, alignment, mSpacingMult, mSpacingAdd,
5594 mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005595 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005596 } else if (shouldEllipsize) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005597 mLayout = new StaticLayout(mTransformed,
5598 0, mTransformed.length(),
5599 mTextPaint, w, alignment, mSpacingMult,
5600 mSpacingAdd, mIncludePad, mEllipsize,
5601 ellipsisWidth);
5602 } else {
5603 mLayout = new StaticLayout(mTransformed, mTextPaint,
5604 w, alignment, mSpacingMult, mSpacingAdd,
5605 mIncludePad);
5606 }
5607 }
5608
Romain Guy4dc4f732009-06-19 15:16:40 -07005609 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005610 mHintLayout = null;
5611
5612 if (mHint != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005613 if (shouldEllipsize) hintWidth = w;
5614
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005615 if (hintBoring == UNKNOWN_BORING) {
5616 hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
5617 mHintBoring);
5618 if (hintBoring != null) {
5619 mHintBoring = hintBoring;
5620 }
5621 }
5622
5623 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005624 if (hintBoring.width <= hintWidth &&
5625 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005626 if (mSavedHintLayout != null) {
5627 mHintLayout = mSavedHintLayout.
5628 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005629 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5630 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005631 } else {
5632 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07005633 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5634 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005635 }
5636
5637 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07005638 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
5639 if (mSavedHintLayout != null) {
5640 mHintLayout = mSavedHintLayout.
5641 replaceOrMake(mHint, mTextPaint,
5642 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5643 hintBoring, mIncludePad, mEllipsize,
5644 ellipsisWidth);
5645 } else {
5646 mHintLayout = BoringLayout.make(mHint, mTextPaint,
5647 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5648 hintBoring, mIncludePad, mEllipsize,
5649 ellipsisWidth);
5650 }
5651 } else if (shouldEllipsize) {
5652 mHintLayout = new StaticLayout(mHint,
5653 0, mHint.length(),
5654 mTextPaint, hintWidth, alignment, mSpacingMult,
5655 mSpacingAdd, mIncludePad, mEllipsize,
5656 ellipsisWidth);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005657 } else {
5658 mHintLayout = new StaticLayout(mHint, mTextPaint,
5659 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5660 mIncludePad);
5661 }
Romain Guy4dc4f732009-06-19 15:16:40 -07005662 } else if (shouldEllipsize) {
5663 mHintLayout = new StaticLayout(mHint,
5664 0, mHint.length(),
5665 mTextPaint, hintWidth, alignment, mSpacingMult,
5666 mSpacingAdd, mIncludePad, mEllipsize,
5667 ellipsisWidth);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005668 } else {
5669 mHintLayout = new StaticLayout(mHint, mTextPaint,
5670 hintWidth, alignment, mSpacingMult, mSpacingAdd,
5671 mIncludePad);
5672 }
5673 }
5674
5675 if (bringIntoView) {
5676 registerForPreDraw();
5677 }
5678
5679 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07005680 if (!compressText(ellipsisWidth)) {
5681 final int height = mLayoutParams.height;
5682 // If the size of the view does not depend on the size of the text, try to
5683 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08005684 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07005685 startMarquee();
5686 } else {
5687 // Defer the start of the marquee until we know our width (see setFrame())
5688 mRestartMarquee = true;
5689 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005690 }
5691 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07005692
5693 // CursorControllers need a non-null mLayout
5694 prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005695 }
5696
Romain Guy939151f2009-04-08 14:22:40 -07005697 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07005698 if (isHardwareAccelerated()) return false;
5699
Romain Guy3373ed62009-05-04 14:13:32 -07005700 // Only compress the text if it hasn't been compressed by the previous pass
5701 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
5702 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07005703 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07005704 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07005705 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
5706 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
5707 post(new Runnable() {
5708 public void run() {
5709 requestLayout();
5710 }
5711 });
5712 return true;
5713 }
5714 }
5715
5716 return false;
5717 }
5718
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005719 private static int desired(Layout layout) {
5720 int n = layout.getLineCount();
5721 CharSequence text = layout.getText();
5722 float max = 0;
5723
5724 // if any line was wrapped, we can't use it.
5725 // but it's ok for the last line not to have a newline
5726
5727 for (int i = 0; i < n - 1; i++) {
5728 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
5729 return -1;
5730 }
5731
5732 for (int i = 0; i < n; i++) {
5733 max = Math.max(max, layout.getLineWidth(i));
5734 }
5735
5736 return (int) FloatMath.ceil(max);
5737 }
5738
5739 /**
5740 * Set whether the TextView includes extra top and bottom padding to make
5741 * room for accents that go above the normal ascent and descent.
5742 * The default is true.
5743 *
5744 * @attr ref android.R.styleable#TextView_includeFontPadding
5745 */
5746 public void setIncludeFontPadding(boolean includepad) {
5747 mIncludePad = includepad;
5748
5749 if (mLayout != null) {
5750 nullLayouts();
5751 requestLayout();
5752 invalidate();
5753 }
5754 }
5755
Romain Guy4dc4f732009-06-19 15:16:40 -07005756 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005757
5758 @Override
5759 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
5760 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
5761 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
5762 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
5763 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
5764
5765 int width;
5766 int height;
5767
5768 BoringLayout.Metrics boring = UNKNOWN_BORING;
5769 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
5770
5771 int des = -1;
5772 boolean fromexisting = false;
5773
5774 if (widthMode == MeasureSpec.EXACTLY) {
5775 // Parent has told us how big to be. So be it.
5776 width = widthSize;
5777 } else {
5778 if (mLayout != null && mEllipsize == null) {
5779 des = desired(mLayout);
5780 }
5781
5782 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005783 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005784 if (boring != null) {
5785 mBoring = boring;
5786 }
5787 } else {
5788 fromexisting = true;
5789 }
5790
5791 if (boring == null || boring == UNKNOWN_BORING) {
5792 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005793 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005794 }
5795
5796 width = des;
5797 } else {
5798 width = boring.width;
5799 }
5800
5801 final Drawables dr = mDrawables;
5802 if (dr != null) {
5803 width = Math.max(width, dr.mDrawableWidthTop);
5804 width = Math.max(width, dr.mDrawableWidthBottom);
5805 }
5806
5807 if (mHint != null) {
5808 int hintDes = -1;
5809 int hintWidth;
5810
Romain Guy4dc4f732009-06-19 15:16:40 -07005811 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005812 hintDes = desired(mHintLayout);
5813 }
5814
5815 if (hintDes < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005816 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005817 if (hintBoring != null) {
5818 mHintBoring = hintBoring;
5819 }
5820 }
5821
5822 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
5823 if (hintDes < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005824 hintDes = (int) FloatMath.ceil(
5825 Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005826 }
5827
5828 hintWidth = hintDes;
5829 } else {
5830 hintWidth = hintBoring.width;
5831 }
5832
5833 if (hintWidth > width) {
5834 width = hintWidth;
5835 }
5836 }
5837
5838 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
5839
5840 if (mMaxWidthMode == EMS) {
5841 width = Math.min(width, mMaxWidth * getLineHeight());
5842 } else {
5843 width = Math.min(width, mMaxWidth);
5844 }
5845
5846 if (mMinWidthMode == EMS) {
5847 width = Math.max(width, mMinWidth * getLineHeight());
5848 } else {
5849 width = Math.max(width, mMinWidth);
5850 }
5851
5852 // Check against our minimum width
5853 width = Math.max(width, getSuggestedMinimumWidth());
5854
5855 if (widthMode == MeasureSpec.AT_MOST) {
5856 width = Math.min(widthSize, width);
5857 }
5858 }
5859
5860 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
5861 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08005862
5863 if (mHorizontallyScrolling) want = VERY_WIDE;
5864
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005865 int hintWant = want;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005866 int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
5867
5868 if (mLayout == null) {
5869 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07005870 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005871 } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
5872 (mLayout.getEllipsizedWidth() !=
5873 width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5874 if (mHint == null && mEllipsize == null &&
5875 want > mLayout.getWidth() &&
5876 (mLayout instanceof BoringLayout ||
Romain Guy4dc4f732009-06-19 15:16:40 -07005877 (fromexisting && des >= 0 && des <= want))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005878 mLayout.increaseWidthTo(want);
5879 } else {
5880 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07005881 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005882 }
5883 } else {
5884 // Width has not changed.
5885 }
5886
5887 if (heightMode == MeasureSpec.EXACTLY) {
5888 // Parent has told us how big to be. So be it.
5889 height = heightSize;
5890 mDesiredHeightAtMeasure = -1;
5891 } else {
5892 int desired = getDesiredHeight();
5893
5894 height = desired;
5895 mDesiredHeightAtMeasure = desired;
5896
5897 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02005898 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005899 }
5900 }
5901
Romain Guy4dc4f732009-06-19 15:16:40 -07005902 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005903 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07005904 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005905 }
5906
5907 /*
5908 * We didn't let makeNewLayout() register to bring the cursor into view,
5909 * so do it here if there is any possibility that it is needed.
5910 */
5911 if (mMovement != null ||
5912 mLayout.getWidth() > unpaddedWidth ||
5913 mLayout.getHeight() > unpaddedHeight) {
5914 registerForPreDraw();
5915 } else {
5916 scrollTo(0, 0);
5917 }
5918
5919 setMeasuredDimension(width, height);
5920 }
5921
5922 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07005923 return Math.max(
5924 getDesiredHeight(mLayout, true),
5925 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005926 }
5927
5928 private int getDesiredHeight(Layout layout, boolean cap) {
5929 if (layout == null) {
5930 return 0;
5931 }
5932
5933 int linecount = layout.getLineCount();
5934 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
5935 int desired = layout.getLineTop(linecount);
5936
5937 final Drawables dr = mDrawables;
5938 if (dr != null) {
5939 desired = Math.max(desired, dr.mDrawableHeightLeft);
5940 desired = Math.max(desired, dr.mDrawableHeightRight);
5941 }
5942
5943 desired += pad;
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08005944 layout.setMaximumVisibleLineCount(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005945
5946 if (mMaxMode == LINES) {
5947 /*
5948 * Don't cap the hint to a certain number of lines.
5949 * (Do cap it, though, if we have a maximum pixel height.)
5950 */
5951 if (cap) {
5952 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08005953 layout.setMaximumVisibleLineCount(mMaximum);
5954 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005955
5956 if (dr != null) {
5957 desired = Math.max(desired, dr.mDrawableHeightLeft);
5958 desired = Math.max(desired, dr.mDrawableHeightRight);
5959 }
5960
5961 desired += pad;
5962 linecount = mMaximum;
5963 }
5964 }
5965 } else {
5966 desired = Math.min(desired, mMaximum);
5967 }
5968
5969 if (mMinMode == LINES) {
5970 if (linecount < mMinimum) {
5971 desired += getLineHeight() * (mMinimum - linecount);
5972 }
5973 } else {
5974 desired = Math.max(desired, mMinimum);
5975 }
5976
5977 // Check against our minimum height
5978 desired = Math.max(desired, getSuggestedMinimumHeight());
5979
5980 return desired;
5981 }
5982
5983 /**
5984 * Check whether a change to the existing text layout requires a
5985 * new view layout.
5986 */
5987 private void checkForResize() {
5988 boolean sizeChanged = false;
5989
5990 if (mLayout != null) {
5991 // Check if our width changed
5992 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
5993 sizeChanged = true;
5994 invalidate();
5995 }
5996
5997 // Check if our height changed
5998 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
5999 int desiredHeight = getDesiredHeight();
6000
6001 if (desiredHeight != this.getHeight()) {
6002 sizeChanged = true;
6003 }
Romain Guy980a9382010-01-08 15:06:28 -08006004 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006005 if (mDesiredHeightAtMeasure >= 0) {
6006 int desiredHeight = getDesiredHeight();
6007
6008 if (desiredHeight != mDesiredHeightAtMeasure) {
6009 sizeChanged = true;
6010 }
6011 }
6012 }
6013 }
6014
6015 if (sizeChanged) {
6016 requestLayout();
6017 // caller will have already invalidated
6018 }
6019 }
6020
6021 /**
6022 * Check whether entirely new text requires a new view layout
6023 * or merely a new text layout.
6024 */
6025 private void checkForRelayout() {
6026 // If we have a fixed width, we can just swap in a new text layout
6027 // if the text height stays the same or if the view height is fixed.
6028
6029 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6030 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6031 (mHint == null || mHintLayout != null) &&
6032 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6033 // Static width, so try making a new text layout.
6034
6035 int oldht = mLayout.getHeight();
6036 int want = mLayout.getWidth();
6037 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6038
6039 /*
6040 * No need to bring the text into view, since the size is not
6041 * changing (unless we do the requestLayout(), in which case it
6042 * will happen at measure).
6043 */
6044 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08006045 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6046 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006047
Romain Guye1e0dc82009-11-03 17:21:04 -08006048 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6049 // In a fixed-height view, so use our new text layout.
6050 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08006051 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08006052 invalidate();
6053 return;
6054 }
6055
6056 // Dynamic height, but height has stayed the same,
6057 // so use our new text layout.
6058 if (mLayout.getHeight() == oldht &&
6059 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6060 invalidate();
6061 return;
6062 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006063 }
6064
6065 // We lose: the height has changed and we have a dynamic height.
6066 // Request a new view layout using our new text layout.
6067 requestLayout();
6068 invalidate();
6069 } else {
6070 // Dynamic width, so we have no choice but to request a new
6071 // view layout with a new text layout.
6072
6073 nullLayouts();
6074 requestLayout();
6075 invalidate();
6076 }
6077 }
6078
6079 /**
6080 * Returns true if anything changed.
6081 */
6082 private boolean bringTextIntoView() {
6083 int line = 0;
6084 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6085 line = mLayout.getLineCount() - 1;
6086 }
6087
6088 Layout.Alignment a = mLayout.getParagraphAlignment(line);
6089 int dir = mLayout.getParagraphDirection(line);
6090 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6091 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6092 int ht = mLayout.getHeight();
6093
6094 int scrollx, scrolly;
6095
6096 if (a == Layout.Alignment.ALIGN_CENTER) {
6097 /*
6098 * Keep centered if possible, or, if it is too wide to fit,
6099 * keep leading edge in view.
6100 */
6101
6102 int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6103 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6104
6105 if (right - left < hspace) {
6106 scrollx = (right + left) / 2 - hspace / 2;
6107 } else {
6108 if (dir < 0) {
6109 scrollx = right - hspace;
6110 } else {
6111 scrollx = left;
6112 }
6113 }
6114 } else if (a == Layout.Alignment.ALIGN_NORMAL) {
6115 /*
6116 * Keep leading edge in view.
6117 */
6118
6119 if (dir < 0) {
6120 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6121 scrollx = right - hspace;
6122 } else {
6123 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
6124 }
6125 } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
6126 /*
6127 * Keep trailing edge in view.
6128 */
6129
6130 if (dir < 0) {
6131 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
6132 } else {
6133 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6134 scrollx = right - hspace;
6135 }
6136 }
6137
6138 if (ht < vspace) {
6139 scrolly = 0;
6140 } else {
6141 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6142 scrolly = ht - vspace;
6143 } else {
6144 scrolly = 0;
6145 }
6146 }
6147
6148 if (scrollx != mScrollX || scrolly != mScrollY) {
6149 scrollTo(scrollx, scrolly);
6150 return true;
6151 } else {
6152 return false;
6153 }
6154 }
6155
6156 /**
6157 * Move the point, specified by the offset, into the view if it is needed.
6158 * This has to be called after layout. Returns true if anything changed.
6159 */
6160 public boolean bringPointIntoView(int offset) {
6161 boolean changed = false;
6162
6163 int line = mLayout.getLineForOffset(offset);
6164
6165 // FIXME: Is it okay to truncate this, or should we round?
6166 final int x = (int)mLayout.getPrimaryHorizontal(offset);
6167 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006168 final int bottom = mLayout.getLineTop(line + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006169
6170 int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
6171 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
6172 int ht = mLayout.getHeight();
6173
6174 int grav;
6175
6176 switch (mLayout.getParagraphAlignment(line)) {
6177 case ALIGN_NORMAL:
6178 grav = 1;
6179 break;
6180
6181 case ALIGN_OPPOSITE:
6182 grav = -1;
6183 break;
6184
6185 default:
6186 grav = 0;
6187 }
6188
6189 grav *= mLayout.getParagraphDirection(line);
6190
6191 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6192 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6193
6194 int hslack = (bottom - top) / 2;
6195 int vslack = hslack;
6196
6197 if (vslack > vspace / 4)
6198 vslack = vspace / 4;
6199 if (hslack > hspace / 4)
6200 hslack = hspace / 4;
6201
6202 int hs = mScrollX;
6203 int vs = mScrollY;
6204
6205 if (top - vs < vslack)
6206 vs = top - vslack;
6207 if (bottom - vs > vspace - vslack)
6208 vs = bottom - (vspace - vslack);
6209 if (ht - vs < vspace)
6210 vs = ht - vspace;
6211 if (0 - vs > 0)
6212 vs = 0;
6213
6214 if (grav != 0) {
6215 if (x - hs < hslack) {
6216 hs = x - hslack;
6217 }
6218 if (x - hs > hspace - hslack) {
6219 hs = x - (hspace - hslack);
6220 }
6221 }
6222
6223 if (grav < 0) {
6224 if (left - hs > 0)
6225 hs = left;
6226 if (right - hs < hspace)
6227 hs = right - hspace;
6228 } else if (grav > 0) {
6229 if (right - hs < hspace)
6230 hs = right - hspace;
6231 if (left - hs > 0)
6232 hs = left;
6233 } else /* grav == 0 */ {
6234 if (right - left <= hspace) {
6235 /*
6236 * If the entire text fits, center it exactly.
6237 */
6238 hs = left - (hspace - (right - left)) / 2;
6239 } else if (x > right - hslack) {
6240 /*
6241 * If we are near the right edge, keep the right edge
6242 * at the edge of the view.
6243 */
6244 hs = right - hspace;
6245 } else if (x < left + hslack) {
6246 /*
6247 * If we are near the left edge, keep the left edge
6248 * at the edge of the view.
6249 */
6250 hs = left;
6251 } else if (left > hs) {
6252 /*
6253 * Is there whitespace visible at the left? Fix it if so.
6254 */
6255 hs = left;
6256 } else if (right < hs + hspace) {
6257 /*
6258 * Is there whitespace visible at the right? Fix it if so.
6259 */
6260 hs = right - hspace;
6261 } else {
6262 /*
6263 * Otherwise, float as needed.
6264 */
6265 if (x - hs < hslack) {
6266 hs = x - hslack;
6267 }
6268 if (x - hs > hspace - hslack) {
6269 hs = x - (hspace - hslack);
6270 }
6271 }
6272 }
6273
6274 if (hs != mScrollX || vs != mScrollY) {
6275 if (mScroller == null) {
6276 scrollTo(hs, vs);
6277 } else {
6278 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6279 int dx = hs - mScrollX;
6280 int dy = vs - mScrollY;
6281
6282 if (duration > ANIMATED_SCROLL_GAP) {
6283 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07006284 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006285 invalidate();
6286 } else {
6287 if (!mScroller.isFinished()) {
6288 mScroller.abortAnimation();
6289 }
6290
6291 scrollBy(dx, dy);
6292 }
6293
6294 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6295 }
6296
6297 changed = true;
6298 }
6299
6300 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08006301 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6302 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006303
Gilles Debunne716dbf62011-03-07 18:12:10 -08006304 if (mTempRect == null) mTempRect = new Rect();
6305 mTempRect.set(x, top, x + 1, bottom);
6306 getInterestingRect(mTempRect, line);
6307 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006308
Gilles Debunne716dbf62011-03-07 18:12:10 -08006309 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006310 changed = true;
6311 }
6312 }
6313
6314 return changed;
6315 }
6316
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006317 /**
6318 * Move the cursor, if needed, so that it is at an offset that is visible
6319 * to the user. This will not move the cursor if it represents more than
6320 * one character (a selection range). This will only work if the
6321 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07006322 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07006323 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006324 */
6325 public boolean moveCursorToVisibleOffset() {
6326 if (!(mText instanceof Spannable)) {
6327 return false;
6328 }
Gilles Debunne05336272010-07-09 20:13:45 -07006329 int start = getSelectionStart();
6330 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006331 if (start != end) {
6332 return false;
6333 }
6334
6335 // First: make sure the line is visible on screen:
6336
6337 int line = mLayout.getLineForOffset(start);
6338
6339 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006340 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006341 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6342 int vslack = (bottom - top) / 2;
6343 if (vslack > vspace / 4)
6344 vslack = vspace / 4;
6345 final int vs = mScrollY;
6346
6347 if (top < (vs+vslack)) {
6348 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6349 } else if (bottom > (vspace+vs-vslack)) {
6350 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6351 }
6352
6353 // Next: make sure the character is visible on screen:
6354
6355 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6356 final int hs = mScrollX;
6357 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6358 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
6359
Doug Feltc982f602010-05-25 11:51:40 -07006360 // line might contain bidirectional text
6361 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6362 final int highChar = leftChar > rightChar ? leftChar : rightChar;
6363
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006364 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07006365 if (newStart < lowChar) {
6366 newStart = lowChar;
6367 } else if (newStart > highChar) {
6368 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006369 }
6370
6371 if (newStart != start) {
6372 Selection.setSelection((Spannable)mText, newStart);
6373 return true;
6374 }
6375
6376 return false;
6377 }
6378
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006379 @Override
6380 public void computeScroll() {
6381 if (mScroller != null) {
6382 if (mScroller.computeScrollOffset()) {
6383 mScrollX = mScroller.getCurrX();
6384 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08006385 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006386 postInvalidate(); // So we draw again
6387 }
6388 }
6389 }
6390
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006391 private void getInterestingRect(Rect r, int line) {
6392 convertFromViewportToContentCoordinates(r);
6393
6394 // Rectangle can can be expanded on first and last line to take
6395 // padding into account.
6396 // TODO Take left/right padding into account too?
6397 if (line == 0) r.top -= getExtendedPaddingTop();
6398 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6399 }
6400
6401 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006402 final int horizontalOffset = viewportToContentHorizontalOffset();
6403 r.left += horizontalOffset;
6404 r.right += horizontalOffset;
6405
6406 final int verticalOffset = viewportToContentVerticalOffset();
6407 r.top += verticalOffset;
6408 r.bottom += verticalOffset;
6409 }
6410
6411 private int viewportToContentHorizontalOffset() {
6412 return getCompoundPaddingLeft() - mScrollX;
6413 }
6414
6415 private int viewportToContentVerticalOffset() {
6416 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006417 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006418 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006419 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006420 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006421 }
6422
6423 @Override
6424 public void debug(int depth) {
6425 super.debug(depth);
6426
6427 String output = debugIndent(depth);
6428 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6429 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6430 + "} ";
6431
6432 if (mText != null) {
6433
6434 output += "mText=\"" + mText + "\" ";
6435 if (mLayout != null) {
6436 output += "mLayout width=" + mLayout.getWidth()
6437 + " height=" + mLayout.getHeight();
6438 }
6439 } else {
6440 output += "mText=NULL";
6441 }
6442 Log.d(VIEW_LOG_TAG, output);
6443 }
6444
6445 /**
6446 * Convenience for {@link Selection#getSelectionStart}.
6447 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006448 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006449 public int getSelectionStart() {
6450 return Selection.getSelectionStart(getText());
6451 }
6452
6453 /**
6454 * Convenience for {@link Selection#getSelectionEnd}.
6455 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006456 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006457 public int getSelectionEnd() {
6458 return Selection.getSelectionEnd(getText());
6459 }
6460
6461 /**
6462 * Return true iff there is a selection inside this text view.
6463 */
6464 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07006465 final int selectionStart = getSelectionStart();
6466 final int selectionEnd = getSelectionEnd();
6467
6468 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006469 }
6470
6471 /**
6472 * Sets the properties of this field (lines, horizontally scrolling,
6473 * transformation method) to be for a single-line input.
6474 *
6475 * @attr ref android.R.styleable#TextView_singleLine
6476 */
6477 public void setSingleLine() {
6478 setSingleLine(true);
6479 }
6480
6481 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006482 * If true, sets the properties of this field (number of lines, horizontally scrolling,
6483 * transformation method) to be for a single-line input; if false, restores these to the default
6484 * conditions.
6485 *
6486 * Note that the default conditions are not necessarily those that were in effect prior this
6487 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006488 *
6489 * @attr ref android.R.styleable#TextView_singleLine
6490 */
6491 @android.view.RemotableViewMethod
6492 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006493 // Could be used, but may break backward compatibility.
6494 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08006495 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006496 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08006497 }
6498
6499 /**
6500 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
6501 * @param singleLine
6502 */
6503 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08006504 if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006505 if (singleLine) {
6506 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6507 } else {
6508 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
6509 }
6510 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006511 }
6512
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006513 private void applySingleLine(boolean singleLine, boolean applyTransformation,
6514 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006515 mSingleLine = singleLine;
6516 if (singleLine) {
6517 setLines(1);
6518 setHorizontallyScrolling(true);
6519 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08006520 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006521 }
6522 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08006523 if (changeMaxLines) {
6524 setMaxLines(Integer.MAX_VALUE);
6525 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006526 setHorizontallyScrolling(false);
6527 if (applyTransformation) {
6528 setTransformationMethod(null);
6529 }
6530 }
6531 }
Gilles Debunneb2316962010-12-21 17:32:43 -08006532
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006533 /**
6534 * Causes words in the text that are longer than the view is wide
6535 * to be ellipsized instead of broken in the middle. You may also
6536 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05006537 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006538 * to turn off ellipsizing.
6539 *
6540 * @attr ref android.R.styleable#TextView_ellipsize
6541 */
6542 public void setEllipsize(TextUtils.TruncateAt where) {
6543 mEllipsize = where;
6544
6545 if (mLayout != null) {
6546 nullLayouts();
6547 requestLayout();
6548 invalidate();
6549 }
6550 }
6551
6552 /**
6553 * Sets how many times to repeat the marquee animation. Only applied if the
6554 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
6555 *
6556 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
6557 */
6558 public void setMarqueeRepeatLimit(int marqueeLimit) {
6559 mMarqueeRepeatLimit = marqueeLimit;
6560 }
6561
6562 /**
6563 * Returns where, if anywhere, words that are longer than the view
6564 * is wide should be ellipsized.
6565 */
6566 @ViewDebug.ExportedProperty
6567 public TextUtils.TruncateAt getEllipsize() {
6568 return mEllipsize;
6569 }
6570
6571 /**
6572 * Set the TextView so that when it takes focus, all the text is
6573 * selected.
6574 *
6575 * @attr ref android.R.styleable#TextView_selectAllOnFocus
6576 */
6577 @android.view.RemotableViewMethod
6578 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
6579 mSelectAllOnFocus = selectAllOnFocus;
6580
6581 if (selectAllOnFocus && !(mText instanceof Spannable)) {
6582 setText(mText, BufferType.SPANNABLE);
6583 }
6584 }
6585
6586 /**
6587 * Set whether the cursor is visible. The default is true.
6588 *
6589 * @attr ref android.R.styleable#TextView_cursorVisible
6590 */
6591 @android.view.RemotableViewMethod
6592 public void setCursorVisible(boolean visible) {
Gilles Debunne3d010062011-02-18 14:16:41 -08006593 if (mCursorVisible != visible) {
6594 mCursorVisible = visible;
6595 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006596
Gilles Debunne3d010062011-02-18 14:16:41 -08006597 makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006598
Gilles Debunne3d010062011-02-18 14:16:41 -08006599 // InsertionPointCursorController depends on mCursorVisible
6600 prepareCursorControllers();
6601 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006602 }
6603
Gilles Debunne98dbfd42011-01-24 12:54:10 -08006604 private boolean isCursorVisible() {
6605 return mCursorVisible && isTextEditable();
6606 }
6607
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006608 private boolean canMarquee() {
6609 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
6610 return width > 0 && mLayout.getLineWidth(0) > width;
6611 }
6612
6613 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006614 // Do not ellipsize EditText
6615 if (mInput != null) return;
6616
Romain Guy939151f2009-04-08 14:22:40 -07006617 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
6618 return;
6619 }
6620
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006621 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
6622 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07006623
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006624 if (mMarquee == null) mMarquee = new Marquee(this);
6625 mMarquee.start(mMarqueeRepeatLimit);
6626 }
6627 }
6628
6629 private void stopMarquee() {
6630 if (mMarquee != null && !mMarquee.isStopped()) {
6631 mMarquee.stop();
6632 }
6633 }
6634
6635 private void startStopMarquee(boolean start) {
6636 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6637 if (start) {
6638 startMarquee();
6639 } else {
6640 stopMarquee();
6641 }
6642 }
6643 }
6644
6645 private static final class Marquee extends Handler {
6646 // TODO: Add an option to configure this
Romain Guy939151f2009-04-08 14:22:40 -07006647 private static final float MARQUEE_DELTA_MAX = 0.07f;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006648 private static final int MARQUEE_DELAY = 1200;
6649 private static final int MARQUEE_RESTART_DELAY = 1200;
6650 private static final int MARQUEE_RESOLUTION = 1000 / 30;
6651 private static final int MARQUEE_PIXELS_PER_SECOND = 30;
6652
6653 private static final byte MARQUEE_STOPPED = 0x0;
6654 private static final byte MARQUEE_STARTING = 0x1;
6655 private static final byte MARQUEE_RUNNING = 0x2;
6656
6657 private static final int MESSAGE_START = 0x1;
6658 private static final int MESSAGE_TICK = 0x2;
6659 private static final int MESSAGE_RESTART = 0x3;
6660
6661 private final WeakReference<TextView> mView;
6662
6663 private byte mStatus = MARQUEE_STOPPED;
Gilles Debunnee15b3582010-06-16 15:17:21 -07006664 private final float mScrollUnit;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006665 private float mMaxScroll;
Romain Guyc2303192009-04-03 17:37:18 -07006666 float mMaxFadeScroll;
6667 private float mGhostStart;
6668 private float mGhostOffset;
6669 private float mFadeStop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006670 private int mRepeatLimit;
6671
6672 float mScroll;
6673
6674 Marquee(TextView v) {
6675 final float density = v.getContext().getResources().getDisplayMetrics().density;
Gilles Debunnee15b3582010-06-16 15:17:21 -07006676 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006677 mView = new WeakReference<TextView>(v);
6678 }
6679
6680 @Override
6681 public void handleMessage(Message msg) {
6682 switch (msg.what) {
6683 case MESSAGE_START:
6684 mStatus = MARQUEE_RUNNING;
6685 tick();
6686 break;
6687 case MESSAGE_TICK:
6688 tick();
6689 break;
6690 case MESSAGE_RESTART:
6691 if (mStatus == MARQUEE_RUNNING) {
6692 if (mRepeatLimit >= 0) {
6693 mRepeatLimit--;
6694 }
6695 start(mRepeatLimit);
6696 }
6697 break;
6698 }
6699 }
6700
6701 void tick() {
6702 if (mStatus != MARQUEE_RUNNING) {
6703 return;
6704 }
6705
6706 removeMessages(MESSAGE_TICK);
6707
6708 final TextView textView = mView.get();
6709 if (textView != null && (textView.isFocused() || textView.isSelected())) {
6710 mScroll += mScrollUnit;
6711 if (mScroll > mMaxScroll) {
6712 mScroll = mMaxScroll;
6713 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
6714 } else {
6715 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
6716 }
6717 textView.invalidate();
6718 }
6719 }
6720
6721 void stop() {
6722 mStatus = MARQUEE_STOPPED;
6723 removeMessages(MESSAGE_START);
6724 removeMessages(MESSAGE_RESTART);
6725 removeMessages(MESSAGE_TICK);
6726 resetScroll();
6727 }
6728
6729 private void resetScroll() {
6730 mScroll = 0.0f;
6731 final TextView textView = mView.get();
6732 if (textView != null) textView.invalidate();
6733 }
6734
6735 void start(int repeatLimit) {
6736 if (repeatLimit == 0) {
6737 stop();
6738 return;
6739 }
6740 mRepeatLimit = repeatLimit;
6741 final TextView textView = mView.get();
6742 if (textView != null && textView.mLayout != null) {
6743 mStatus = MARQUEE_STARTING;
6744 mScroll = 0.0f;
Romain Guyc2303192009-04-03 17:37:18 -07006745 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
6746 textView.getCompoundPaddingRight();
6747 final float lineWidth = textView.mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006748 final float gap = textWidth / 3.0f;
6749 mGhostStart = lineWidth - textWidth + gap;
Romain Guyc2303192009-04-03 17:37:18 -07006750 mMaxScroll = mGhostStart + textWidth;
Romain Guy3373ed62009-05-04 14:13:32 -07006751 mGhostOffset = lineWidth + gap;
Romain Guyc2303192009-04-03 17:37:18 -07006752 mFadeStop = lineWidth + textWidth / 6.0f;
6753 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
6754
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006755 textView.invalidate();
6756 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
6757 }
6758 }
6759
Romain Guyc2303192009-04-03 17:37:18 -07006760 float getGhostOffset() {
6761 return mGhostOffset;
6762 }
6763
6764 boolean shouldDrawLeftFade() {
6765 return mScroll <= mFadeStop;
6766 }
6767
6768 boolean shouldDrawGhost() {
6769 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
6770 }
6771
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006772 boolean isRunning() {
6773 return mStatus == MARQUEE_RUNNING;
6774 }
6775
6776 boolean isStopped() {
6777 return mStatus == MARQUEE_STOPPED;
6778 }
6779 }
6780
6781 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08006782 * This method is called when the text is changed, in case any subclasses
6783 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006784 *
Gilles Debunne4469e602011-03-09 14:38:04 -08006785 * Within <code>text</code>, the <code>lengthAfter</code> characters
6786 * beginning at <code>start</code> have just replaced old text that had
6787 * length <code>lengthBefore</code>. It is an error to attempt to make
6788 * changes to <code>text</code> from this callback.
6789 *
6790 * @param text The text the TextView is displaying
6791 * @param start The offset of the start of the range of the text that was
6792 * modified
6793 * @param lengthBefore The length of the former text that has been replaced
6794 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006795 */
Gilles Debunne4469e602011-03-09 14:38:04 -08006796 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006797 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006798 }
6799
6800 /**
6801 * This method is called when the selection has changed, in case any
6802 * subclasses would like to know.
6803 *
6804 * @param selStart The new selection start location.
6805 * @param selEnd The new selection end location.
6806 */
6807 protected void onSelectionChanged(int selStart, int selEnd) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006808 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006809 }
6810
6811 /**
6812 * Adds a TextWatcher to the list of those whose methods are called
6813 * whenever this TextView's text changes.
6814 * <p>
6815 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
6816 * not called after {@link #setText} calls. Now, doing {@link #setText}
6817 * if there are any text changed listeners forces the buffer type to
6818 * Editable if it would not otherwise be and does call this method.
6819 */
6820 public void addTextChangedListener(TextWatcher watcher) {
6821 if (mListeners == null) {
6822 mListeners = new ArrayList<TextWatcher>();
6823 }
6824
6825 mListeners.add(watcher);
6826 }
6827
6828 /**
6829 * Removes the specified TextWatcher from the list of those whose
6830 * methods are called
6831 * whenever this TextView's text changes.
6832 */
6833 public void removeTextChangedListener(TextWatcher watcher) {
6834 if (mListeners != null) {
6835 int i = mListeners.indexOf(watcher);
6836
6837 if (i >= 0) {
6838 mListeners.remove(i);
6839 }
6840 }
6841 }
6842
6843 private void sendBeforeTextChanged(CharSequence text, int start, int before,
6844 int after) {
6845 if (mListeners != null) {
6846 final ArrayList<TextWatcher> list = mListeners;
6847 final int count = list.size();
6848 for (int i = 0; i < count; i++) {
6849 list.get(i).beforeTextChanged(text, start, before, after);
6850 }
6851 }
6852 }
6853
6854 /**
6855 * Not private so it can be called from an inner class without going
6856 * through a thunk.
6857 */
6858 void sendOnTextChanged(CharSequence text, int start, int before,
6859 int after) {
6860 if (mListeners != null) {
6861 final ArrayList<TextWatcher> list = mListeners;
6862 final int count = list.size();
6863 for (int i = 0; i < count; i++) {
6864 list.get(i).onTextChanged(text, start, before, after);
6865 }
6866 }
6867 }
6868
6869 /**
6870 * Not private so it can be called from an inner class without going
6871 * through a thunk.
6872 */
6873 void sendAfterTextChanged(Editable text) {
6874 if (mListeners != null) {
6875 final ArrayList<TextWatcher> list = mListeners;
6876 final int count = list.size();
6877 for (int i = 0; i < count; i++) {
6878 list.get(i).afterTextChanged(text);
6879 }
6880 }
6881 }
6882
6883 /**
6884 * Not private so it can be called from an inner class without going
6885 * through a thunk.
6886 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006887 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006888 final InputMethodState ims = mInputMethodState;
6889 if (ims == null || ims.mBatchEditNesting == 0) {
6890 updateAfterEdit();
6891 }
6892 if (ims != null) {
6893 ims.mContentChanged = true;
6894 if (ims.mChangedStart < 0) {
6895 ims.mChangedStart = start;
6896 ims.mChangedEnd = start+before;
6897 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01006898 ims.mChangedStart = Math.min(ims.mChangedStart, start);
6899 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006900 }
6901 ims.mChangedDelta += after-before;
6902 }
6903
6904 sendOnTextChanged(buffer, start, before, after);
6905 onTextChanged(buffer, start, before, after);
Adam Powellba0a2c32010-09-28 17:41:23 -07006906
Gilles Debunned94f8c52011-01-10 11:29:15 -08006907 // Hide the controllers if the amount of content changed
Adam Powellba0a2c32010-09-28 17:41:23 -07006908 if (before != after) {
6909 hideControllers();
6910 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006911 }
6912
6913 /**
6914 * Not private so it can be called from an inner class without going
6915 * through a thunk.
6916 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006917 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006918 // XXX Make the start and end move together if this ends up
6919 // spending too much time invalidating.
6920
6921 boolean selChanged = false;
6922 int newSelStart=-1, newSelEnd=-1;
6923
6924 final InputMethodState ims = mInputMethodState;
6925
6926 if (what == Selection.SELECTION_END) {
6927 mHighlightPathBogus = true;
6928 selChanged = true;
6929 newSelEnd = newStart;
6930
6931 if (!isFocused()) {
6932 mSelectionMoved = true;
6933 }
6934
6935 if (oldStart >= 0 || newStart >= 0) {
6936 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
6937 registerForPreDraw();
Gilles Debunne3d010062011-02-18 14:16:41 -08006938 makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006939 }
6940 }
6941
6942 if (what == Selection.SELECTION_START) {
6943 mHighlightPathBogus = true;
6944 selChanged = true;
6945 newSelStart = newStart;
6946
6947 if (!isFocused()) {
6948 mSelectionMoved = true;
6949 }
6950
6951 if (oldStart >= 0 || newStart >= 0) {
6952 int end = Selection.getSelectionEnd(buf);
6953 invalidateCursor(end, oldStart, newStart);
6954 }
6955 }
6956
6957 if (selChanged) {
6958 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
6959 if (newSelStart < 0) {
6960 newSelStart = Selection.getSelectionStart(buf);
6961 }
6962 if (newSelEnd < 0) {
6963 newSelEnd = Selection.getSelectionEnd(buf);
6964 }
6965 onSelectionChanged(newSelStart, newSelEnd);
6966 }
6967 }
6968
6969 if (what instanceof UpdateAppearance ||
6970 what instanceof ParagraphStyle) {
6971 if (ims == null || ims.mBatchEditNesting == 0) {
6972 invalidate();
6973 mHighlightPathBogus = true;
6974 checkForResize();
6975 } else {
6976 ims.mContentChanged = true;
6977 }
6978 }
6979
6980 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
6981 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006982 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
6983 ims.mSelectionModeChanged = true;
6984 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006985
6986 if (Selection.getSelectionStart(buf) >= 0) {
6987 if (ims == null || ims.mBatchEditNesting == 0) {
6988 invalidateCursor();
6989 } else {
6990 ims.mCursorChanged = true;
6991 }
6992 }
6993 }
6994
6995 if (what instanceof ParcelableSpan) {
6996 // If this is a span that can be sent to a remote process,
6997 // the current extract editor would be interested in it.
6998 if (ims != null && ims.mExtracting != null) {
6999 if (ims.mBatchEditNesting != 0) {
7000 if (oldStart >= 0) {
7001 if (ims.mChangedStart > oldStart) {
7002 ims.mChangedStart = oldStart;
7003 }
7004 if (ims.mChangedStart > oldEnd) {
7005 ims.mChangedStart = oldEnd;
7006 }
7007 }
7008 if (newStart >= 0) {
7009 if (ims.mChangedStart > newStart) {
7010 ims.mChangedStart = newStart;
7011 }
7012 if (ims.mChangedStart > newEnd) {
7013 ims.mChangedStart = newEnd;
7014 }
7015 }
7016 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007017 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007018 + oldStart + "-" + oldEnd + ","
7019 + newStart + "-" + newEnd + what);
7020 ims.mContentChanged = true;
7021 }
7022 }
7023 }
7024 }
7025
7026 private class ChangeWatcher
7027 implements TextWatcher, SpanWatcher {
svetoslavganov75986cf2009-05-14 22:28:01 -07007028
7029 private CharSequence mBeforeText;
7030
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007031 public void beforeTextChanged(CharSequence buffer, int start,
7032 int before, int after) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007033 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007034 + " before=" + before + " after=" + after + ": " + buffer);
svetoslavganov75986cf2009-05-14 22:28:01 -07007035
Amith Yamasani91ccdb52010-01-14 18:56:02 -08007036 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08007037 && !isPasswordInputType(mInputType)
7038 && !hasPasswordTransformationMethod()) {
svetoslavganov75986cf2009-05-14 22:28:01 -07007039 mBeforeText = buffer.toString();
7040 }
7041
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007042 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
7043 }
7044
7045 public void onTextChanged(CharSequence buffer, int start,
7046 int before, int after) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007047 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007048 + " before=" + before + " after=" + after + ": " + buffer);
7049 TextView.this.handleTextChanged(buffer, start, before, after);
svetoslavganov75986cf2009-05-14 22:28:01 -07007050
7051 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
7052 (isFocused() || isSelected() &&
7053 isShown())) {
7054 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
7055 mBeforeText = null;
7056 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007057 }
7058
7059 public void afterTextChanged(Editable buffer) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007060 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007061 TextView.this.sendAfterTextChanged(buffer);
7062
7063 if (MetaKeyKeyListener.getMetaState(buffer,
7064 MetaKeyKeyListener.META_SELECTING) != 0) {
7065 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
7066 }
7067 }
7068
7069 public void onSpanChanged(Spannable buf,
7070 Object what, int s, int e, int st, int en) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007071 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007072 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
7073 TextView.this.spanChange(buf, what, s, st, e, en);
7074 }
7075
7076 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007077 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007078 + " what=" + what + ": " + buf);
7079 TextView.this.spanChange(buf, what, -1, s, -1, e);
7080 }
7081
7082 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007083 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007084 + " what=" + what + ": " + buf);
7085 TextView.this.spanChange(buf, what, s, -1, e, -1);
7086 }
7087 }
7088
Romain Guydcc490f2010-02-24 17:59:35 -08007089 /**
7090 * @hide
7091 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007092 @Override
Romain Guya440b002010-02-24 15:57:54 -08007093 public void dispatchFinishTemporaryDetach() {
7094 mDispatchTemporaryDetach = true;
7095 super.dispatchFinishTemporaryDetach();
7096 mDispatchTemporaryDetach = false;
7097 }
7098
7099 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007100 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007101 super.onStartTemporaryDetach();
7102 // Only track when onStartTemporaryDetach() is called directly,
7103 // usually because this instance is an editable field in a list
7104 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08007105
7106 // Because of View recycling in ListView, there is no easy way to know when a TextView with
7107 // selection becomes visible again. Until a better solution is found, stop text selection
7108 // mode (if any) as soon as this TextView is recycled.
7109 stopSelectionActionMode();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007110 }
7111
7112 @Override
7113 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007114 super.onFinishTemporaryDetach();
7115 // Only track when onStartTemporaryDetach() is called directly,
7116 // usually because this instance is an editable field in a list
7117 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007118 }
7119
7120 @Override
7121 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7122 if (mTemporaryDetach) {
7123 // If we are temporarily in the detach state, then do nothing.
7124 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7125 return;
7126 }
7127
7128 mShowCursor = SystemClock.uptimeMillis();
7129
7130 ensureEndedBatchEdit();
Gilles Debunne03789e82010-09-07 19:07:17 -07007131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007132 if (focused) {
7133 int selStart = getSelectionStart();
7134 int selEnd = getSelectionEnd();
7135
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08007136 // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
7137 // mode for these, unless there was a specific selection already started.
7138 final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
7139 selEnd == mText.length();
7140 mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
7141
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007142 if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
Gilles Debunne528c64882010-10-08 11:56:13 -07007143 // If a tap was used to give focus to that view, move cursor at tap position.
Gilles Debunne64e54a62010-09-07 19:07:17 -07007144 // Has to be done before onTakeFocus, which can be overloaded.
Gilles Debunne380b6042010-10-08 16:12:11 -07007145 final int lastTapPosition = getLastTapPosition();
7146 if (lastTapPosition >= 0) {
7147 Selection.setSelection((Spannable) mText, lastTapPosition);
7148 }
Gilles Debunne2703a422010-08-23 15:14:03 -07007149
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007150 if (mMovement != null) {
7151 mMovement.onTakeFocus(this, (Spannable) mText, direction);
7152 }
7153
Gilles Debunne64e54a62010-09-07 19:07:17 -07007154 // The DecorView does not have focus when the 'Done' ExtractEditText button is
Joe Onoratoc6cc0f82011-04-12 11:53:13 -07007155 // pressed. Since it is the ViewAncestor's mView, it requests focus before
Gilles Debunne64e54a62010-09-07 19:07:17 -07007156 // ExtractEditText clears focus, which gives focus to the ExtractEditText.
7157 // This special case ensure that we keep current selection in that case.
7158 // It would be better to know why the DecorView does not have focus at that time.
Gilles Debunne47fa8e82010-09-07 18:50:07 -07007159 if (((this instanceof ExtractEditText) || mSelectionMoved) &&
7160 selStart >= 0 && selEnd >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007161 /*
7162 * Someone intentionally set the selection, so let them
7163 * do whatever it is that they wanted to do instead of
7164 * the default on-focus behavior. We reset the selection
7165 * here instead of just skipping the onTakeFocus() call
7166 * because some movement methods do something other than
7167 * just setting the selection in theirs and we still
7168 * need to go through that path.
7169 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007170 Selection.setSelection((Spannable) mText, selStart, selEnd);
7171 }
Gilles Debunnef170a342010-11-11 11:08:59 -08007172
7173 if (mSelectAllOnFocus) {
Gilles Debunnef4dceb12010-12-01 15:54:20 -08007174 selectAll();
Gilles Debunnef170a342010-11-11 11:08:59 -08007175 }
7176
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007177 mTouchFocusSelected = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007178 }
7179
7180 mFrozenWithFocus = false;
7181 mSelectionMoved = false;
7182
7183 if (mText instanceof Spannable) {
7184 Spannable sp = (Spannable) mText;
7185 MetaKeyKeyListener.resetMetaState(sp);
7186 }
7187
7188 makeBlink();
7189
7190 if (mError != null) {
7191 showError();
7192 }
7193 } else {
7194 if (mError != null) {
7195 hideError();
7196 }
7197 // Don't leave us in the middle of a batch edit.
7198 onEndBatchEdit();
Gilles Debunne05336272010-07-09 20:13:45 -07007199
Gilles Debunne64e54a62010-09-07 19:07:17 -07007200 if (this instanceof ExtractEditText) {
7201 // terminateTextSelectionMode removes selection, which we want to keep when
7202 // ExtractEditText goes out of focus.
7203 final int selStart = getSelectionStart();
7204 final int selEnd = getSelectionEnd();
Gilles Debunneb7012e842011-02-24 15:40:38 -08007205 hideControllers();
Gilles Debunne64e54a62010-09-07 19:07:17 -07007206 Selection.setSelection((Spannable) mText, selStart, selEnd);
7207 } else {
Gilles Debunneb7012e842011-02-24 15:40:38 -08007208 hideControllers();
Gilles Debunne64e54a62010-09-07 19:07:17 -07007209 }
Gilles Debunne380b6042010-10-08 16:12:11 -07007210
Gilles Debunnee587d832010-11-23 20:20:11 -08007211 // No need to create the controller
Gilles Debunne380b6042010-10-08 16:12:11 -07007212 if (mSelectionModifierCursorController != null) {
Gilles Debunnee587d832010-11-23 20:20:11 -08007213 mSelectionModifierCursorController.resetTouchOffsets();
Gilles Debunne380b6042010-10-08 16:12:11 -07007214 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007215 }
7216
7217 startStopMarquee(focused);
7218
7219 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007220 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007221 }
7222
7223 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7224 }
7225
Gilles Debunne380b6042010-10-08 16:12:11 -07007226 private int getLastTapPosition() {
Gilles Debunnee587d832010-11-23 20:20:11 -08007227 // No need to create the controller at that point, no last tap position saved
Gilles Debunne528c64882010-10-08 11:56:13 -07007228 if (mSelectionModifierCursorController != null) {
Gilles Debunnee587d832010-11-23 20:20:11 -08007229 int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
Gilles Debunne380b6042010-10-08 16:12:11 -07007230 if (lastTapPosition >= 0) {
Gilles Debunne528c64882010-10-08 11:56:13 -07007231 // Safety check, should not be possible.
Gilles Debunne380b6042010-10-08 16:12:11 -07007232 if (lastTapPosition > mText.length()) {
7233 Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
Gilles Debunne528c64882010-10-08 11:56:13 -07007234 + mText.length() + ")");
Gilles Debunne380b6042010-10-08 16:12:11 -07007235 lastTapPosition = mText.length();
Gilles Debunne528c64882010-10-08 11:56:13 -07007236 }
Gilles Debunne380b6042010-10-08 16:12:11 -07007237 return lastTapPosition;
Gilles Debunne528c64882010-10-08 11:56:13 -07007238 }
7239 }
Gilles Debunne380b6042010-10-08 16:12:11 -07007240
7241 return -1;
Gilles Debunne528c64882010-10-08 11:56:13 -07007242 }
7243
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007244 @Override
7245 public void onWindowFocusChanged(boolean hasWindowFocus) {
7246 super.onWindowFocusChanged(hasWindowFocus);
7247
7248 if (hasWindowFocus) {
7249 if (mBlink != null) {
7250 mBlink.uncancel();
Gilles Debunne3d010062011-02-18 14:16:41 -08007251 makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007252 }
7253 } else {
7254 if (mBlink != null) {
7255 mBlink.cancel();
7256 }
7257 // Don't leave us in the middle of a batch edit.
7258 onEndBatchEdit();
7259 if (mInputContentType != null) {
7260 mInputContentType.enterDown = false;
7261 }
Gilles Debunnee507a9e2010-10-10 12:44:18 -07007262 hideControllers();
Gilles Debunneba4997a2011-05-02 14:35:42 -07007263 removeAllSuggestionSpans();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007264 }
7265
7266 startStopMarquee(hasWindowFocus);
7267 }
7268
Gilles Debunneba4997a2011-05-02 14:35:42 -07007269 private void removeAllSuggestionSpans() {
7270 if (mText instanceof Editable) {
7271 Editable editable = ((Editable) mText);
7272 SuggestionSpan[] spans = editable.getSpans(0, mText.length(), SuggestionSpan.class);
7273 final int length = spans.length;
7274 for (int i = 0; i < length; i++) {
7275 editable.removeSpan(spans[i]);
7276 }
7277 }
7278 }
7279
Adam Powellba0a2c32010-09-28 17:41:23 -07007280 @Override
7281 protected void onVisibilityChanged(View changedView, int visibility) {
7282 super.onVisibilityChanged(changedView, visibility);
7283 if (visibility != VISIBLE) {
Gilles Debunnee507a9e2010-10-10 12:44:18 -07007284 hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07007285 }
7286 }
7287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007288 /**
7289 * Use {@link BaseInputConnection#removeComposingSpans
7290 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7291 * state from this text view.
7292 */
7293 public void clearComposingText() {
7294 if (mText instanceof Spannable) {
7295 BaseInputConnection.removeComposingSpans((Spannable)mText);
7296 }
7297 }
7298
7299 @Override
7300 public void setSelected(boolean selected) {
7301 boolean wasSelected = isSelected();
7302
7303 super.setSelected(selected);
7304
7305 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7306 if (selected) {
7307 startMarquee();
7308 } else {
7309 stopMarquee();
7310 }
7311 }
7312 }
7313
7314 @Override
7315 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007316 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07007317
7318 if (hasInsertionController()) {
7319 getInsertionController().onTouchEvent(event);
Gilles Debunne5347c582010-10-27 14:22:35 -07007320 }
Adam Powell965b9692010-10-21 18:44:32 -07007321 if (hasSelectionController()) {
7322 getSelectionController().onTouchEvent(event);
Gilles Debunne5347c582010-10-27 14:22:35 -07007323 }
Adam Powellb08013c2010-09-16 16:28:11 -07007324
Gilles Debunne5347c582010-10-27 14:22:35 -07007325 if (action == MotionEvent.ACTION_DOWN) {
Gilles Debunne9948ad72010-11-24 14:00:46 -08007326 mLastDownPositionX = (int) event.getX();
7327 mLastDownPositionY = (int) event.getY();
7328
The Android Open Source Project4df24232009-03-05 14:34:35 -08007329 // Reset this state; it will be re-set if super.onTouchEvent
7330 // causes focus to move to the view.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007331 mTouchFocusSelected = false;
Gilles Debunne0eb704c2010-11-30 12:50:54 -08007332 mIgnoreActionUpEvent = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007333 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007335 final boolean superResult = super.onTouchEvent(event);
7336
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007337 /*
7338 * Don't handle the release after a long press, because it will
7339 * move the selection away from whatever the menu action was
7340 * trying to affect.
7341 */
Gilles Debunne0eb704c2010-11-30 12:50:54 -08007342 if (mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7343 mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007344 return superResult;
7345 }
7346
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007347 final boolean touchIsFinished = action == MotionEvent.ACTION_UP && !mIgnoreActionUpEvent &&
7348 isFocused();
7349
Janos Levai042856c2010-10-15 02:53:58 +03007350 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
7351 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007352 boolean handled = false;
7353
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07007354 if (mMovement != null) {
7355 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7356 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007357
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007358 if (mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable && touchIsFinished) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007359 // The LinkMovementMethod which should handle taps on links has not been installed
7360 // to support text selection. We reproduce its behavior here to open links.
7361 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7362 getSelectionEnd(), ClickableSpan.class);
7363
7364 if (links.length != 0) {
7365 links[0].onClick(this);
7366 handled = true;
7367 }
7368 }
7369
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007370 if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
7371 // Show the IME, except when selecting in read-only text.
7372 if (!mTextIsSelectable) {
7373 final InputMethodManager imm = InputMethodManager.peekInstance();
7374 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07007375 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007376
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007377 boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
7378 if (!selectAllGotFocus && hasSelection()) {
7379 startSelectionActionMode();
7380 } else {
7381 stopSelectionActionMode();
Gilles Debunne69340442011-03-31 13:37:51 -07007382 hideSuggestions();
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007383 if (hasInsertionController() && !selectAllGotFocus && mText.length() > 0) {
7384 getInsertionController().show();
Gilles Debunne8cbb4c62011-01-24 12:33:56 -08007385 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007386 }
7387 }
7388
The Android Open Source Project4df24232009-03-05 14:34:35 -08007389 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007390 return true;
7391 }
7392 }
7393
7394 return superResult;
7395 }
7396
Jeff Brown8f345672011-02-26 13:29:53 -08007397 @Override
7398 public boolean onGenericMotionEvent(MotionEvent event) {
7399 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7400 try {
7401 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7402 return true;
7403 }
7404 } catch (AbstractMethodError ex) {
7405 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7406 // Ignore its absence in case third party applications implemented the
7407 // interface directly.
7408 }
7409 }
7410 return super.onGenericMotionEvent(event);
7411 }
7412
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007413 private void prepareCursorControllers() {
Adam Powell8c8293b2010-10-12 14:45:12 -07007414 boolean windowSupportsHandles = false;
7415
7416 ViewGroup.LayoutParams params = getRootView().getLayoutParams();
7417 if (params instanceof WindowManager.LayoutParams) {
7418 WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
7419 windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
7420 || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
7421 }
7422
Gilles Debunne98dbfd42011-01-24 12:54:10 -08007423 mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
Adam Powell965b9692010-10-21 18:44:32 -07007424 mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
7425 mLayout != null;
7426
7427 if (!mInsertionControllerEnabled) {
Gilles Debunnef48e83b2010-12-06 18:36:08 -08007428 hideInsertionPointCursorController();
Adam Powell65a1de92011-01-30 15:47:29 -08007429 if (mInsertionPointCursorController != null) {
7430 mInsertionPointCursorController.onDetached();
7431 mInsertionPointCursorController = null;
7432 }
Gilles Debunne05336272010-07-09 20:13:45 -07007433 }
7434
Adam Powell965b9692010-10-21 18:44:32 -07007435 if (!mSelectionControllerEnabled) {
Gilles Debunnee587d832010-11-23 20:20:11 -08007436 stopSelectionActionMode();
Adam Powell65a1de92011-01-30 15:47:29 -08007437 if (mSelectionModifierCursorController != null) {
7438 mSelectionModifierCursorController.onDetached();
7439 mSelectionModifierCursorController = null;
7440 }
Gilles Debunne05336272010-07-09 20:13:45 -07007441 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007442 }
7443
7444 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08007445 * @return True iff this TextView contains a text that can be edited, or if this is
7446 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007447 */
7448 private boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08007449 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007450 }
7451
The Android Open Source Project4df24232009-03-05 14:34:35 -08007452 /**
7453 * Returns true, only while processing a touch gesture, if the initial
7454 * touch down event caused focus to move to the text view and as a result
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007455 * its selection changed. Only valid while processing the touch gesture
7456 * of interest.
The Android Open Source Project4df24232009-03-05 14:34:35 -08007457 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007458 public boolean didTouchFocusSelect() {
7459 return mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007460 }
7461
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007462 @Override
7463 public void cancelLongPress() {
7464 super.cancelLongPress();
Gilles Debunne0eb704c2010-11-30 12:50:54 -08007465 mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007466 }
7467
7468 @Override
7469 public boolean onTrackballEvent(MotionEvent event) {
7470 if (mMovement != null && mText instanceof Spannable &&
7471 mLayout != null) {
7472 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7473 return true;
7474 }
7475 }
7476
7477 return super.onTrackballEvent(event);
7478 }
7479
7480 public void setScroller(Scroller s) {
7481 mScroller = s;
7482 }
7483
7484 private static class Blink extends Handler implements Runnable {
Gilles Debunnee15b3582010-06-16 15:17:21 -07007485 private final WeakReference<TextView> mView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007486 private boolean mCancelled;
7487
7488 public Blink(TextView v) {
7489 mView = new WeakReference<TextView>(v);
7490 }
7491
7492 public void run() {
7493 if (mCancelled) {
7494 return;
7495 }
7496
7497 removeCallbacks(Blink.this);
7498
7499 TextView tv = mView.get();
7500
Gilles Debunne3d010062011-02-18 14:16:41 -08007501 if (tv != null && tv.shouldBlink()) {
7502 if (tv.mLayout != null) {
7503 tv.invalidateCursorPath();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007504 }
Gilles Debunne3d010062011-02-18 14:16:41 -08007505
7506 postAtTime(this, SystemClock.uptimeMillis() + BLINK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007507 }
7508 }
7509
7510 void cancel() {
7511 if (!mCancelled) {
7512 removeCallbacks(Blink.this);
7513 mCancelled = true;
7514 }
7515 }
7516
7517 void uncancel() {
7518 mCancelled = false;
7519 }
7520 }
7521
Gilles Debunne3d010062011-02-18 14:16:41 -08007522 /**
7523 * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
7524 */
7525 private boolean shouldBlink() {
7526 if (!isFocused()) return false;
7527
7528 final int start = getSelectionStart();
7529 if (start < 0) return false;
7530
7531 final int end = getSelectionEnd();
7532 if (end < 0) return false;
7533
7534 return start == end;
7535 }
7536
7537 private void makeBlink() {
7538 if (isCursorVisible()) {
7539 if (shouldBlink()) {
7540 mShowCursor = SystemClock.uptimeMillis();
7541 if (mBlink == null) mBlink = new Blink(this);
7542 mBlink.removeCallbacks(mBlink);
7543 mBlink.postAtTime(mBlink, mShowCursor + BLINK);
7544 }
7545 } else {
7546 if (mBlink != null) mBlink.removeCallbacks(mBlink);
7547 }
7548 }
7549
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007550 @Override
7551 protected float getLeftFadingEdgeStrength() {
Romain Guy909cbaf2010-10-13 18:19:48 -07007552 if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007553 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7554 if (mMarquee != null && !mMarquee.isStopped()) {
7555 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007556 if (marquee.shouldDrawLeftFade()) {
7557 return marquee.mScroll / getHorizontalFadingEdgeLength();
7558 } else {
7559 return 0.0f;
7560 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007561 } else if (getLineCount() == 1) {
7562 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7563 case Gravity.LEFT:
7564 return 0.0f;
7565 case Gravity.RIGHT:
7566 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7567 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7568 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7569 case Gravity.CENTER_HORIZONTAL:
7570 return 0.0f;
7571 }
7572 }
7573 }
7574 return super.getLeftFadingEdgeStrength();
7575 }
7576
7577 @Override
7578 protected float getRightFadingEdgeStrength() {
Romain Guy909cbaf2010-10-13 18:19:48 -07007579 if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007580 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7581 if (mMarquee != null && !mMarquee.isStopped()) {
7582 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007583 return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007584 } else if (getLineCount() == 1) {
7585 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
7586 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07007587 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7588 getCompoundPaddingRight();
7589 final float lineWidth = mLayout.getLineWidth(0);
7590 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007591 case Gravity.RIGHT:
7592 return 0.0f;
7593 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07007594 case Gravity.FILL_HORIZONTAL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007595 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7596 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7597 getHorizontalFadingEdgeLength();
7598 }
7599 }
7600 }
7601 return super.getRightFadingEdgeStrength();
7602 }
7603
7604 @Override
7605 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07007606 if (mLayout != null) {
7607 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7608 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7609 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007610
7611 return super.computeHorizontalScrollRange();
7612 }
7613
7614 @Override
7615 protected int computeVerticalScrollRange() {
7616 if (mLayout != null)
7617 return mLayout.getHeight();
7618
7619 return super.computeVerticalScrollRange();
7620 }
7621
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007622 @Override
7623 protected int computeVerticalScrollExtent() {
7624 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7625 }
7626
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007627 public enum BufferType {
7628 NORMAL, SPANNABLE, EDITABLE,
7629 }
7630
7631 /**
7632 * Returns the TextView_textColor attribute from the
7633 * Resources.StyledAttributes, if set, or the TextAppearance_textColor
7634 * from the TextView_textAppearance attribute, if TextView_textColor
7635 * was not set directly.
7636 */
7637 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7638 ColorStateList colors;
7639 colors = attrs.getColorStateList(com.android.internal.R.styleable.
7640 TextView_textColor);
7641
7642 if (colors == null) {
7643 int ap = attrs.getResourceId(com.android.internal.R.styleable.
7644 TextView_textAppearance, -1);
7645 if (ap != -1) {
7646 TypedArray appearance;
7647 appearance = context.obtainStyledAttributes(ap,
7648 com.android.internal.R.styleable.TextAppearance);
7649 colors = appearance.getColorStateList(com.android.internal.R.styleable.
7650 TextAppearance_textColor);
7651 appearance.recycle();
7652 }
7653 }
7654
7655 return colors;
7656 }
7657
7658 /**
7659 * Returns the default color from the TextView_textColor attribute
7660 * from the AttributeSet, if set, or the default color from the
7661 * TextAppearance_textColor from the TextView_textAppearance attribute,
7662 * if TextView_textColor was not set directly.
7663 */
7664 public static int getTextColor(Context context,
7665 TypedArray attrs,
7666 int def) {
7667 ColorStateList colors = getTextColors(context, attrs);
7668
7669 if (colors == null) {
7670 return def;
7671 } else {
7672 return colors.getDefaultColor();
7673 }
7674 }
7675
7676 @Override
7677 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Jeff Brownc1df9072010-12-21 16:38:50 -08007678 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7679 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7680 switch (keyCode) {
7681 case KeyEvent.KEYCODE_A:
7682 if (canSelectText()) {
7683 return onTextContextMenuItem(ID_SELECT_ALL);
7684 }
7685 break;
7686 case KeyEvent.KEYCODE_X:
7687 if (canCut()) {
7688 return onTextContextMenuItem(ID_CUT);
7689 }
7690 break;
7691 case KeyEvent.KEYCODE_C:
7692 if (canCopy()) {
7693 return onTextContextMenuItem(ID_COPY);
7694 }
7695 break;
7696 case KeyEvent.KEYCODE_V:
7697 if (canPaste()) {
7698 return onTextContextMenuItem(ID_PASTE);
7699 }
7700 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007701 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007702 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007703 return super.onKeyShortcut(keyCode, event);
7704 }
7705
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007706 /**
7707 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7708 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
7709 * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient.
7710 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07007711 private boolean canSelectText() {
Gilles Debunne6da7e932010-12-07 14:28:14 -08007712 return hasSelectionController() && mText.length() != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007713 }
7714
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007715 /**
7716 * Test based on the <i>intrinsic</i> charateristics of the TextView.
7717 * The text must be spannable and the movement method must allow for arbitary selection.
7718 *
7719 * See also {@link #canSelectText()}.
7720 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07007721 private boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07007722 // prepareCursorController() relies on this method.
7723 // If you change this condition, make sure prepareCursorController is called anywhere
7724 // the value of this condition might be changed.
Gilles Debunne6da7e932010-12-07 14:28:14 -08007725 return mText instanceof Spannable && mMovement != null && mMovement.canSelectArbitrarily();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007726 }
7727
7728 private boolean canCut() {
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07007729 if (hasPasswordTransformationMethod()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007730 return false;
7731 }
7732
Gilles Debunnef4dceb12010-12-01 15:54:20 -08007733 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
7734 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007735 }
7736
7737 return false;
7738 }
7739
7740 private boolean canCopy() {
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07007741 if (hasPasswordTransformationMethod()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007742 return false;
7743 }
7744
Gilles Debunne03789e82010-09-07 19:07:17 -07007745 if (mText.length() > 0 && hasSelection()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007746 return true;
7747 }
7748
7749 return false;
7750 }
7751
7752 private boolean canPaste() {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007753 return (mText instanceof Editable &&
7754 mInput != null &&
7755 getSelectionStart() >= 0 &&
7756 getSelectionEnd() >= 0 &&
7757 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
Dianne Hackborn23fdaf62010-08-06 12:16:55 -07007758 hasPrimaryClip());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007759 }
7760
Gilles Debunne79ff9142011-01-07 10:16:21 -08007761 private boolean isWordCharacter(int c, int type) {
Gilles Debunne8e06a632010-11-30 12:05:55 -08007762 return (c == '\'' || c == '"' ||
7763 type == Character.UPPERCASE_LETTER ||
7764 type == Character.LOWERCASE_LETTER ||
7765 type == Character.TITLECASE_LETTER ||
7766 type == Character.MODIFIER_LETTER ||
Gilles Debunne87380bc2011-01-04 13:24:54 -08007767 type == Character.OTHER_LETTER || // Should handle asian characters
Gilles Debunne8e06a632010-11-30 12:05:55 -08007768 type == Character.DECIMAL_DIGIT_NUMBER);
7769 }
7770
Gilles Debunnecf1e9252010-10-07 20:46:03 -07007771 private static long packRangeInLong(int start, int end) {
Gilles Debunne05336272010-07-09 20:13:45 -07007772 return (((long) start) << 32) | end;
7773 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007774
Gilles Debunnecf1e9252010-10-07 20:46:03 -07007775 private static int extractRangeStartFromLong(long range) {
7776 return (int) (range >>> 32);
7777 }
7778
7779 private static int extractRangeEndFromLong(long range) {
7780 return (int) (range & 0x00000000FFFFFFFFL);
7781 }
Gilles Debunnecbfbb522010-10-07 16:57:31 -07007782
Gilles Debunnec59269f2011-04-22 11:46:09 -07007783 private boolean selectAll() {
7784 final int length = mText.length();
7785 Selection.setSelection((Spannable) mText, 0, length);
7786 return length > 0;
Gilles Debunnef4dceb12010-12-01 15:54:20 -08007787 }
7788
Gilles Debunnec59269f2011-04-22 11:46:09 -07007789 /**
7790 * Adjusts selection to the word under last touch offset.
7791 * Return true if the operation was successfully performed.
7792 */
7793 private boolean selectCurrentWord() {
Gilles Debunne6da7e932010-12-07 14:28:14 -08007794 if (!canSelectText()) {
Gilles Debunnec59269f2011-04-22 11:46:09 -07007795 return false;
Gilles Debunne6da7e932010-12-07 14:28:14 -08007796 }
7797
Gilles Debunne710a9102010-11-23 16:50:28 -08007798 if (hasPasswordTransformationMethod()) {
Gilles Debunne87380bc2011-01-04 13:24:54 -08007799 // Always select all on a password field.
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007800 // Cut/copy menu entries are not available for passwords, but being able to select all
7801 // is however useful to delete or paste to replace the entire content.
Gilles Debunnec59269f2011-04-22 11:46:09 -07007802 return selectAll();
7803 }
7804
7805 int klass = mInputType & InputType.TYPE_MASK_CLASS;
7806 int variation = mInputType & InputType.TYPE_MASK_VARIATION;
7807
7808 // Specific text field types: select the entire text for these
7809 if (klass == InputType.TYPE_CLASS_NUMBER ||
7810 klass == InputType.TYPE_CLASS_PHONE ||
7811 klass == InputType.TYPE_CLASS_DATETIME ||
7812 variation == InputType.TYPE_TEXT_VARIATION_URI ||
7813 variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
7814 variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
7815 variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
7816 return selectAll();
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007817 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007818
Gilles Debunne87380bc2011-01-04 13:24:54 -08007819 long lastTouchOffsets = getLastTouchOffsets();
7820 final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
7821 final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007822
7823 int selectionStart, selectionEnd;
7824
7825 // If a URLSpan (web address, email, phone...) is found at that position, select it.
7826 URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class);
7827 if (urlSpans.length == 1) {
7828 URLSpan url = urlSpans[0];
7829 selectionStart = ((Spanned) mText).getSpanStart(url);
7830 selectionEnd = ((Spanned) mText).getSpanEnd(url);
7831 } else {
Gilles Debunnec59269f2011-04-22 11:46:09 -07007832 if (mWordIterator == null) {
7833 mWordIterator = new WordIterator();
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007834 }
Gilles Debunnec59269f2011-04-22 11:46:09 -07007835 // WordIerator handles text changes, this is a no-op if text in unchanged.
7836 mWordIterator.setCharSequence(mText);
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007837
Gilles Debunnec59269f2011-04-22 11:46:09 -07007838 selectionStart = mWordIterator.getBeginning(minOffset);
7839 if (selectionStart == BreakIterator.DONE) return false;
7840
7841 selectionEnd = mWordIterator.getEnd(maxOffset);
7842 if (selectionEnd == BreakIterator.DONE) return false;
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007843 }
7844
7845 Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
Gilles Debunnec59269f2011-04-22 11:46:09 -07007846 return true;
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007847 }
7848
7849 private long getLastTouchOffsets() {
Gilles Debunnecf1e9252010-10-07 20:46:03 -07007850 int minOffset, maxOffset;
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007851
Gilles Debunne528c64882010-10-08 11:56:13 -07007852 if (mContextMenuTriggeredByKey) {
Gilles Debunnecf1e9252010-10-07 20:46:03 -07007853 minOffset = getSelectionStart();
7854 maxOffset = getSelectionEnd();
7855 } else {
Gilles Debunnee587d832010-11-23 20:20:11 -08007856 SelectionModifierCursorController selectionController = getSelectionController();
7857 minOffset = selectionController.getMinTouchOffset();
7858 maxOffset = selectionController.getMaxTouchOffset();
Gilles Debunne03789e82010-09-07 19:07:17 -07007859 }
7860
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007861 return packRangeInLong(minOffset, maxOffset);
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007862 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07007863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007864 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -07007865 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07007866 super.onPopulateAccessibilityEvent(event);
7867
Svetoslav Ganovbe7565942010-03-10 11:51:20 -08007868 if (!isShown()) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07007869 return;
Svetoslav Ganovbe7565942010-03-10 11:51:20 -08007870 }
7871
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08007872 final boolean isPassword = hasPasswordTransformationMethod();
svetoslavganov75986cf2009-05-14 22:28:01 -07007873
7874 if (!isPassword) {
7875 CharSequence text = getText();
7876 if (TextUtils.isEmpty(text)) {
7877 text = getHint();
7878 }
7879 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07007880 event.getText().add(text);
7881 }
7882 } else {
7883 event.setPassword(isPassword);
7884 }
svetoslavganov75986cf2009-05-14 22:28:01 -07007885 }
7886
7887 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
7888 int fromIndex, int removedCount, int addedCount) {
7889 AccessibilityEvent event =
7890 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
7891 event.setFromIndex(fromIndex);
7892 event.setRemovedCount(removedCount);
7893 event.setAddedCount(addedCount);
7894 event.setBeforeText(beforeText);
7895 sendAccessibilityEventUnchecked(event);
7896 }
7897
7898 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007899 protected void onCreateContextMenu(ContextMenu menu) {
7900 super.onCreateContextMenu(menu);
7901 boolean added = false;
Gilles Debunne528c64882010-10-08 11:56:13 -07007902 mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
7903 // Problem with context menu on long press: the menu appears while the key in down and when
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007904 // the key is released, the view does not receive the key_up event.
7905 // We need two layers of flags: mDPadCenterIsDown and mEnterKeyIsDown are set in key down/up
7906 // events. We cannot simply clear these flags in onTextContextMenuItem since
Gilles Debunne528c64882010-10-08 11:56:13 -07007907 // it may not be called (if the user/ discards the context menu with the back key).
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007908 // We clear these flags here and mContextMenuTriggeredByKey saves that state so that it is
7909 // available in onTextContextMenuItem.
Gilles Debunne528c64882010-10-08 11:56:13 -07007910 mDPadCenterIsDown = mEnterKeyIsDown = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007911
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007912 MenuHandler handler = new MenuHandler();
7913
Gilles Debunneb0db5942011-01-04 13:58:54 -08007914 if (mText instanceof Spanned && hasSelectionController()) {
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007915 long lastTouchOffset = getLastTouchOffsets();
7916 final int selStart = extractRangeStartFromLong(lastTouchOffset);
7917 final int selEnd = extractRangeEndFromLong(lastTouchOffset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007918
Gilles Debunne4dfe0862010-12-17 15:46:28 -08007919 URLSpan[] urls = ((Spanned) mText).getSpans(selStart, selEnd, URLSpan.class);
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007920 if (urls.length > 0) {
7921 menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
7922 setOnMenuItemClickListener(handler);
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007923
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007924 added = true;
7925 }
7926 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007927
7928 // The context menu is not empty, which will prevent the selection mode from starting.
7929 // Add a entry to start it in the context menu.
7930 // TODO Does not handle the case where a subclass does not call super.thisMethod or
7931 // populates the menu AFTER this call.
7932 if (menu.size() > 0) {
7933 menu.add(0, ID_SELECTION_MODE, 0, com.android.internal.R.string.selectTextMode).
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007934 setOnMenuItemClickListener(handler);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007935 added = true;
7936 }
7937
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007938 if (added) {
7939 menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
7940 }
7941 }
7942
7943 /**
7944 * Returns whether this text view is a current input method target. The
7945 * default implementation just checks with {@link InputMethodManager}.
7946 */
7947 public boolean isInputMethodTarget() {
7948 InputMethodManager imm = InputMethodManager.peekInstance();
7949 return imm != null && imm.isActive(this);
7950 }
7951
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007952 // Selection context mode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007953 private static final int ID_SELECT_ALL = android.R.id.selectAll;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007954 private static final int ID_CUT = android.R.id.cut;
7955 private static final int ID_COPY = android.R.id.copy;
7956 private static final int ID_PASTE = android.R.id.paste;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007957 // Context menu entries
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007958 private static final int ID_COPY_URL = android.R.id.copyUrl;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007959 private static final int ID_SELECTION_MODE = android.R.id.selectTextMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007960
7961 private class MenuHandler implements MenuItem.OnMenuItemClickListener {
7962 public boolean onMenuItemClick(MenuItem item) {
7963 return onTextContextMenuItem(item.getItemId());
7964 }
7965 }
7966
7967 /**
7968 * Called when a context menu option for the text view is selected. Currently
Jeff Brownc1df9072010-12-21 16:38:50 -08007969 * this will be {@link android.R.id#copyUrl}, {@link android.R.id#selectTextMode},
7970 * {@link android.R.id#selectAll}, {@link android.R.id#paste}, {@link android.R.id#cut}
7971 * or {@link android.R.id#copy}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07007972 *
7973 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007974 */
7975 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007976 int min = 0;
7977 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07007978
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007979 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007980 final int selStart = getSelectionStart();
7981 final int selEnd = getSelectionEnd();
7982
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07007983 min = Math.max(0, Math.min(selStart, selEnd));
7984 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007985 }
7986
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007987 switch (id) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007988 case ID_COPY_URL:
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007989 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
Dianne Hackborn23fdaf62010-08-06 12:16:55 -07007990 if (urls.length >= 1) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -07007991 ClipData clip = null;
Dianne Hackborn23fdaf62010-08-06 12:16:55 -07007992 for (int i=0; i<urls.length; i++) {
7993 Uri uri = Uri.parse(urls[0].getURL());
Dianne Hackborn23fdaf62010-08-06 12:16:55 -07007994 if (clip == null) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08007995 clip = ClipData.newRawUri(null, uri);
Dianne Hackborn23fdaf62010-08-06 12:16:55 -07007996 } else {
Dianne Hackborn1040dc42010-08-26 22:11:06 -07007997 clip.addItem(new ClipData.Item(uri));
Dianne Hackborn23fdaf62010-08-06 12:16:55 -07007998 }
7999 }
Dianne Hackborn1040dc42010-08-26 22:11:06 -07008000 if (clip != null) {
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008001 setPrimaryClip(clip);
Dianne Hackborn1040dc42010-08-26 22:11:06 -07008002 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008003 }
Gilles Debunne4dfe0862010-12-17 15:46:28 -08008004 stopSelectionActionMode();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008005 return true;
8006
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008007 case ID_SELECTION_MODE:
Gilles Debunne4dfe0862010-12-17 15:46:28 -08008008 if (mSelectionActionMode != null) {
8009 // Selection mode is already started, simply change selected part.
Gilles Debunne2037b822011-04-22 13:07:33 -07008010 selectCurrentWord();
Gilles Debunne4dfe0862010-12-17 15:46:28 -08008011 } else {
8012 startSelectionActionMode();
8013 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008014 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008015
Jeff Brownc1df9072010-12-21 16:38:50 -08008016 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008017 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008018 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Jeff Brownc1df9072010-12-21 16:38:50 -08008019 selectAll();
Jeff Brownc1df9072010-12-21 16:38:50 -08008020 return true;
8021
8022 case ID_PASTE:
8023 paste(min, max);
8024 return true;
8025
8026 case ID_CUT:
Dianne Hackborn327fbd22011-01-17 14:38:50 -08008027 setPrimaryClip(ClipData.newPlainText(null, mTransformed.subSequence(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008028 ((Editable) mText).delete(min, max);
8029 stopSelectionActionMode();
8030 return true;
8031
8032 case ID_COPY:
Dianne Hackborn327fbd22011-01-17 14:38:50 -08008033 setPrimaryClip(ClipData.newPlainText(null, mTransformed.subSequence(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008034 stopSelectionActionMode();
8035 return true;
8036 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008037 return false;
8038 }
8039
Gilles Debunnecf1e9252010-10-07 20:46:03 -07008040 /**
8041 * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8042 * by [min, max] when replacing this region by paste.
Gilles Debunnec0752ee2010-12-22 17:50:42 -08008043 * Note that if there were two spaces (or more) at that position before, they are kept. We just
Gilles Debunne4ae0f292010-11-29 14:56:39 -08008044 * make sure we do not add an extra one from the paste content.
Gilles Debunnecf1e9252010-10-07 20:46:03 -07008045 */
8046 private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
Gilles Debunnec0752ee2010-12-22 17:50:42 -08008047 if (paste.length() > 0) {
8048 if (min > 0) {
8049 final char charBefore = mTransformed.charAt(min - 1);
8050 final char charAfter = paste.charAt(0);
Gilles Debunnecf1e9252010-10-07 20:46:03 -07008051
Gilles Debunnec0752ee2010-12-22 17:50:42 -08008052 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8053 // Two spaces at beginning of paste: remove one
8054 final int originalLength = mText.length();
8055 ((Editable) mText).delete(min - 1, min);
8056 // Due to filters, there is no guarantee that exactly one character was
8057 // removed: count instead.
8058 final int delta = mText.length() - originalLength;
8059 min += delta;
8060 max += delta;
8061 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8062 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8063 // No space at beginning of paste: add one
8064 final int originalLength = mText.length();
8065 ((Editable) mText).replace(min, min, " ");
8066 // Taking possible filters into account as above.
8067 final int delta = mText.length() - originalLength;
8068 min += delta;
8069 max += delta;
8070 }
Gilles Debunnecf1e9252010-10-07 20:46:03 -07008071 }
Gilles Debunnec0752ee2010-12-22 17:50:42 -08008072
8073 if (max < mText.length()) {
8074 final char charBefore = paste.charAt(paste.length() - 1);
8075 final char charAfter = mTransformed.charAt(max);
8076
8077 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8078 // Two spaces at end of paste: remove one
8079 ((Editable) mText).delete(max, max + 1);
8080 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8081 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8082 // No space at end of paste: add one
8083 ((Editable) mText).replace(max, max, " ");
8084 }
Gilles Debunnecf1e9252010-10-07 20:46:03 -07008085 }
8086 }
Gilles Debunne4ae0f292010-11-29 14:56:39 -08008087
Gilles Debunnecf1e9252010-10-07 20:46:03 -07008088 return packRangeInLong(min, max);
8089 }
8090
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008091 private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
8092 TextView shadowView = (TextView) inflate(mContext,
Gilles Debunnef170a342010-11-11 11:08:59 -08008093 com.android.internal.R.layout.text_drag_thumbnail, null);
8094
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008095 if (shadowView == null) {
Gilles Debunnef170a342010-11-11 11:08:59 -08008096 throw new IllegalArgumentException("Unable to inflate text drag thumbnail");
8097 }
8098
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008099 if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) {
8100 text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH);
Gilles Debunnef170a342010-11-11 11:08:59 -08008101 }
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008102 shadowView.setText(text);
8103 shadowView.setTextColor(getTextColors());
Gilles Debunnef170a342010-11-11 11:08:59 -08008104
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008105 shadowView.setTextAppearance(mContext, R.styleable.Theme_textAppearanceLarge);
8106 shadowView.setGravity(Gravity.CENTER);
Gilles Debunnef170a342010-11-11 11:08:59 -08008107
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008108 shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
Gilles Debunnef170a342010-11-11 11:08:59 -08008109 ViewGroup.LayoutParams.WRAP_CONTENT));
8110
8111 final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008112 shadowView.measure(size, size);
Gilles Debunnef170a342010-11-11 11:08:59 -08008113
Christopher Tate36d4c3f2011-01-07 13:34:24 -08008114 shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight());
8115 shadowView.invalidate();
8116 return new DragShadowBuilder(shadowView);
Gilles Debunnef170a342010-11-11 11:08:59 -08008117 }
8118
Gilles Debunneaaa84792010-12-03 11:10:14 -08008119 private static class DragLocalState {
8120 public TextView sourceTextView;
8121 public int start, end;
8122
8123 public DragLocalState(TextView sourceTextView, int start, int end) {
8124 this.sourceTextView = sourceTextView;
8125 this.start = start;
8126 this.end = end;
8127 }
8128 }
8129
Gilles Debunnee15b3582010-06-16 15:17:21 -07008130 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008131 public boolean performLongClick() {
8132 if (super.performLongClick()) {
Gilles Debunne0eb704c2010-11-30 12:50:54 -08008133 mDiscardNextActionUp = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008134 return true;
8135 }
Gilles Debunnef170a342010-11-11 11:08:59 -08008136
Gilles Debunne299733e2011-02-07 17:11:41 -08008137 boolean handled = false;
8138
Gilles Debunne33a8cfb2010-12-10 12:00:42 -08008139 // Long press in empty space moves cursor and shows the Paste affordance if available.
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008140 if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
8141 mInsertionControllerEnabled) {
Gilles Debunne9948ad72010-11-24 14:00:46 -08008142 final int offset = getOffset(mLastDownPositionX, mLastDownPositionY);
Gilles Debunned1dc72a2010-11-30 10:16:35 -08008143 stopSelectionActionMode();
Gilles Debunne75beb332011-04-29 11:40:22 -07008144 Selection.setSelection((Spannable) mText, offset);
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008145 getInsertionController().showWithPaste();
Gilles Debunne299733e2011-02-07 17:11:41 -08008146 handled = true;
Gilles Debunne9948ad72010-11-24 14:00:46 -08008147 }
8148
Gilles Debunne299733e2011-02-07 17:11:41 -08008149 if (!handled && mSelectionActionMode != null) {
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008150 if (touchPositionIsInSelection()) {
8151 // Start a drag
8152 final int start = getSelectionStart();
8153 final int end = getSelectionEnd();
8154 CharSequence selectedText = mTransformed.subSequence(start, end);
Dianne Hackborn327fbd22011-01-17 14:38:50 -08008155 ClipData data = ClipData.newPlainText(null, selectedText);
Gilles Debunneaaa84792010-12-03 11:10:14 -08008156 DragLocalState localState = new DragLocalState(this, start, end);
Christopher Tate02d2b3b2011-01-10 20:43:53 -08008157 startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008158 stopSelectionActionMode();
8159 } else {
Gilles Debunne2037b822011-04-22 13:07:33 -07008160 selectCurrentWord();
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008161 }
Gilles Debunne299733e2011-02-07 17:11:41 -08008162 handled = true;
Gilles Debunnef170a342010-11-11 11:08:59 -08008163 }
8164
Gilles Debunne33a8cfb2010-12-10 12:00:42 -08008165 // Start a new selection
Gilles Debunne299733e2011-02-07 17:11:41 -08008166 handled |= !handled && startSelectionActionMode();
8167
8168 if (handled) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008169 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne0eb704c2010-11-30 12:50:54 -08008170 mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008171 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008172
Gilles Debunne299733e2011-02-07 17:11:41 -08008173 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008174 }
8175
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008176 private boolean touchPositionIsInSelection() {
8177 int selectionStart = getSelectionStart();
8178 int selectionEnd = getSelectionEnd();
Gilles Debunne05336272010-07-09 20:13:45 -07008179
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008180 if (selectionStart == selectionEnd) {
8181 return false;
8182 }
Gilles Debunne05336272010-07-09 20:13:45 -07008183
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008184 if (selectionStart > selectionEnd) {
8185 int tmp = selectionStart;
8186 selectionStart = selectionEnd;
8187 selectionEnd = tmp;
Gilles Debunne05336272010-07-09 20:13:45 -07008188 Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008189 }
Gilles Debunne05336272010-07-09 20:13:45 -07008190
Gilles Debunnee587d832010-11-23 20:20:11 -08008191 SelectionModifierCursorController selectionController = getSelectionController();
8192 int minOffset = selectionController.getMinTouchOffset();
8193 int maxOffset = selectionController.getMaxTouchOffset();
Gilles Debunne05336272010-07-09 20:13:45 -07008194
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008195 return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
8196 }
8197
Gilles Debunne214a8622011-04-26 15:44:37 -07008198 private static class SuggestionRangeSpan extends UnderlineSpan {
8199 // TODO themable, would be nice to make it a child class of TextAppearanceSpan, but
8200 // there is no way to have underline and TextAppearanceSpan.
8201 }
8202
Gilles Debunne69340442011-03-31 13:37:51 -07008203 private class SuggestionsPopupWindow implements OnClickListener {
8204 private static final int MAX_NUMBER_SUGGESTIONS = 5;
Gilles Debunne214a8622011-04-26 15:44:37 -07008205 private static final int NO_SUGGESTIONS = -1;
Gilles Debunne69340442011-03-31 13:37:51 -07008206 private final PopupWindow mContainer;
8207 private final ViewGroup[] mSuggestionViews = new ViewGroup[2];
8208 private final int[] mSuggestionViewLayouts = new int[] {
8209 mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout};
Gilles Debunne214a8622011-04-26 15:44:37 -07008210 private WordIterator mWordIterator;
8211 private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan[0];
Gilles Debunne69340442011-03-31 13:37:51 -07008212
8213 public SuggestionsPopupWindow() {
8214 mContainer = new PopupWindow(TextView.this.mContext, null,
8215 com.android.internal.R.attr.textSuggestionsWindowStyle);
8216 mContainer.setSplitTouchEnabled(true);
8217 mContainer.setClippingEnabled(false);
8218 mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
8219
8220 mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
8221 mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
8222 }
8223
Gilles Debunne214a8622011-04-26 15:44:37 -07008224 private class SuggestionInfo {
8225 int suggestionStart, suggestionEnd; // range of suggestion item with replacement text
8226 int spanStart, spanEnd; // range in TextView where text should be inserted
8227 }
8228
Gilles Debunne69340442011-03-31 13:37:51 -07008229 private ViewGroup getViewGroup(boolean under) {
8230 final int viewIndex = under ? 0 : 1;
8231 ViewGroup viewGroup = mSuggestionViews[viewIndex];
8232
8233 if (viewGroup == null) {
8234 final int layout = mSuggestionViewLayouts[viewIndex];
8235 LayoutInflater inflater = (LayoutInflater) TextView.this.mContext.
8236 getSystemService(Context.LAYOUT_INFLATER_SERVICE);
8237
8238 if (inflater == null) {
8239 throw new IllegalArgumentException(
8240 "Unable to create TextEdit suggestion window inflater");
8241 }
8242
8243 View view = inflater.inflate(layout, null);
8244
8245 if (! (view instanceof ViewGroup)) {
8246 throw new IllegalArgumentException(
8247 "Inflated TextEdit suggestion window is not a ViewGroup: " + view);
8248 }
8249
8250 viewGroup = (ViewGroup) view;
8251
8252 // Inflate the suggestion items once and for all.
8253 for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
8254 View childView = inflater.inflate(mTextEditSuggestionItemLayout, viewGroup,
8255 false);
8256
8257 if (! (childView instanceof TextView)) {
8258 throw new IllegalArgumentException(
8259 "Inflated TextEdit suggestion item is not a TextView: " + childView);
8260 }
8261
Gilles Debunne214a8622011-04-26 15:44:37 -07008262 childView.setTag(new SuggestionInfo());
Gilles Debunne69340442011-03-31 13:37:51 -07008263 viewGroup.addView(childView);
8264 childView.setOnClickListener(this);
8265 }
8266
8267 mSuggestionViews[viewIndex] = viewGroup;
8268 }
8269
8270 return viewGroup;
8271 }
8272
8273 public void show() {
8274 if (!(mText instanceof Editable)) return;
8275
8276 final int pos = TextView.this.getSelectionStart();
8277 Spannable spannable = (Spannable)TextView.this.mText;
satokb3fc1a52011-04-06 18:28:55 +09008278 SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
8279 final int nbSpans = suggestionSpans.length;
Gilles Debunne69340442011-03-31 13:37:51 -07008280
8281 ViewGroup viewGroup = getViewGroup(true);
8282 mContainer.setContentView(viewGroup);
8283
8284 int totalNbSuggestions = 0;
Gilles Debunne214a8622011-04-26 15:44:37 -07008285 int spanUnionStart = mText.length();
8286 int spanUnionEnd = 0;
8287
Gilles Debunne69340442011-03-31 13:37:51 -07008288 for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
satokb3fc1a52011-04-06 18:28:55 +09008289 SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
8290 final int spanStart = spannable.getSpanStart(suggestionSpan);
8291 final int spanEnd = spannable.getSpanEnd(suggestionSpan);
Gilles Debunne214a8622011-04-26 15:44:37 -07008292 spanUnionStart = Math.min(spanStart, spanUnionStart);
8293 spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
Gilles Debunne69340442011-03-31 13:37:51 -07008294
satokb3fc1a52011-04-06 18:28:55 +09008295 String[] suggestions = suggestionSpan.getSuggestions();
Gilles Debunne69340442011-03-31 13:37:51 -07008296 int nbSuggestions = suggestions.length;
8297 for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
8298 TextView textView = (TextView) viewGroup.getChildAt(totalNbSuggestions);
8299 textView.setText(suggestions[suggestionIndex]);
Gilles Debunne214a8622011-04-26 15:44:37 -07008300 SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8301 suggestionInfo.spanStart = spanStart;
8302 suggestionInfo.spanEnd = spanEnd;
Gilles Debunne69340442011-03-31 13:37:51 -07008303
8304 totalNbSuggestions++;
Gilles Debunne6e222bc2011-05-04 10:25:04 -07008305 if (totalNbSuggestions == MAX_NUMBER_SUGGESTIONS) {
Gilles Debunne214a8622011-04-26 15:44:37 -07008306 // Also end outer for loop
Gilles Debunne69340442011-03-31 13:37:51 -07008307 spanIndex = nbSpans;
8308 break;
8309 }
8310 }
8311 }
8312
8313 if (totalNbSuggestions == 0) {
8314 // TODO Replace by final text, use a dedicated layout, add a fade out timer...
8315 TextView textView = (TextView) viewGroup.getChildAt(0);
8316 textView.setText("No suggestions available");
Gilles Debunne214a8622011-04-26 15:44:37 -07008317 SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8318 suggestionInfo.spanStart = NO_SUGGESTIONS;
Gilles Debunne69340442011-03-31 13:37:51 -07008319 totalNbSuggestions++;
Gilles Debunne214a8622011-04-26 15:44:37 -07008320 } else {
8321 if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
8322 ((Editable) mText).setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
8323 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8324
8325 for (int i = 0; i < totalNbSuggestions; i++) {
8326 final TextView textView = (TextView) viewGroup.getChildAt(i);
8327 highlightTextDifferences(textView, spanUnionStart, spanUnionEnd);
8328 }
Gilles Debunne69340442011-03-31 13:37:51 -07008329 }
8330
8331 for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
8332 viewGroup.getChildAt(i).setVisibility(i < totalNbSuggestions ? VISIBLE : GONE);
8333 }
8334
8335 final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
8336 viewGroup.measure(size, size);
8337
8338 positionAtCursor();
8339 }
8340
Gilles Debunne214a8622011-04-26 15:44:37 -07008341 private long[] getWordLimits(CharSequence text) {
8342 if (mWordIterator == null) mWordIterator = new WordIterator(); // TODO locale
8343 mWordIterator.setCharSequence(text);
8344
8345 // First pass will simply count the number of words to be able to create an array
8346 // Not too expensive since previous break positions are cached by the BreakIterator
8347 int nbWords = 0;
8348 int position = mWordIterator.following(0);
8349 while (position != BreakIterator.DONE) {
8350 nbWords++;
8351 position = mWordIterator.following(position);
8352 }
8353
8354 int index = 0;
8355 long[] result = new long[nbWords];
8356
8357 position = mWordIterator.following(0);
8358 while (position != BreakIterator.DONE) {
8359 int wordStart = mWordIterator.getBeginning(position);
8360 result[index++] = packRangeInLong(wordStart, position);
8361 position = mWordIterator.following(position);
8362 }
8363
8364 return result;
8365 }
8366
8367 private TextAppearanceSpan highlightSpan(int index) {
8368 final int length = mHighlightSpans.length;
8369 if (index < length) {
8370 return mHighlightSpans[index];
8371 }
8372
8373 // Assumes indexes are requested in sequence: simply append one more item
8374 TextAppearanceSpan[] newArray = new TextAppearanceSpan[length + 1];
8375 System.arraycopy(mHighlightSpans, 0, newArray, 0, length);
8376 TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
8377 android.R.style.TextAppearance_SuggestionHighlight);
8378 newArray[length] = highlightSpan;
8379 mHighlightSpans = newArray;
8380 return highlightSpan;
8381 }
8382
8383 private void highlightTextDifferences(TextView textView, int unionStart, int unionEnd) {
8384 SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8385 final int spanStart = suggestionInfo.spanStart;
8386 final int spanEnd = suggestionInfo.spanEnd;
8387
8388 // Remove all text formating by converting to Strings
8389 final String text = textView.getText().toString();
8390 final String sourceText = mText.subSequence(spanStart, spanEnd).toString();
8391
8392 long[] sourceWordLimits = getWordLimits(sourceText);
8393 long[] wordLimits = getWordLimits(text);
8394
8395 SpannableStringBuilder ssb = new SpannableStringBuilder();
8396 // span [spanStart, spanEnd] is included in union [spanUnionStart, int spanUnionEnd]
8397 // The final result is made of 3 parts: the text before, between and after the span
8398 // This is the text before, provided for context
8399 ssb.append(mText.subSequence(unionStart, spanStart).toString());
8400
8401 // shift is used to offset spans positions wrt span's beginning
8402 final int shift = spanStart - unionStart;
8403 suggestionInfo.suggestionStart = shift;
8404 suggestionInfo.suggestionEnd = shift + text.length();
8405
8406 // This is the actual suggestion text, which will be highlighted by the following code
8407 ssb.append(text);
8408
8409 String[] words = new String[wordLimits.length];
8410 for (int i = 0; i < wordLimits.length; i++) {
8411 int wordStart = extractRangeStartFromLong(wordLimits[i]);
8412 int wordEnd = extractRangeEndFromLong(wordLimits[i]);
8413 words[i] = text.substring(wordStart, wordEnd);
8414 }
8415
8416 // Highlighted word algorithm is bases on word matching between source and text
8417 // Matching words are found from left to right. TODO: change for RTL languages
8418 // Characters between matching words are highlighted
8419 int previousCommonWordIndex = -1;
8420 int nbHighlightSpans = 0;
8421 for (int i = 0; i < sourceWordLimits.length; i++) {
8422 int wordStart = extractRangeStartFromLong(sourceWordLimits[i]);
8423 int wordEnd = extractRangeEndFromLong(sourceWordLimits[i]);
8424 String sourceWord = sourceText.substring(wordStart, wordEnd);
8425
8426 for (int j = previousCommonWordIndex + 1; j < words.length; j++) {
8427 if (sourceWord.equals(words[j])) {
8428 if (j != previousCommonWordIndex + 1) {
8429 int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
8430 extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
8431 int lastDifferentPosition = extractRangeStartFromLong(wordLimits[j]);
8432 ssb.setSpan(highlightSpan(nbHighlightSpans++),
8433 shift + firstDifferentPosition, shift + lastDifferentPosition,
8434 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8435 } else {
8436 // Compare characters between words
8437 int previousSourceWordEnd = i == 0 ? 0 :
8438 extractRangeEndFromLong(sourceWordLimits[i - 1]);
8439 int sourceWordStart = extractRangeStartFromLong(sourceWordLimits[i]);
8440 String sourceSpaces = sourceText.substring(previousSourceWordEnd,
8441 sourceWordStart);
8442
8443 int previousWordEnd = j == 0 ? 0 :
8444 extractRangeEndFromLong(wordLimits[j - 1]);
8445 int currentWordStart = extractRangeStartFromLong(wordLimits[j]);
8446 String textSpaces = text.substring(previousWordEnd, currentWordStart);
8447
8448 if (!sourceSpaces.equals(textSpaces)) {
8449 ssb.setSpan(highlightSpan(nbHighlightSpans++),
8450 shift + previousWordEnd, shift + currentWordStart,
8451 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8452 }
8453 }
8454 previousCommonWordIndex = j;
8455 break;
8456 }
8457 }
8458 }
8459
8460 // Finally, compare ends of Strings
8461 if (previousCommonWordIndex < words.length - 1) {
8462 int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
8463 extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
8464 int lastDifferentPosition = textView.length();
8465 ssb.setSpan(highlightSpan(nbHighlightSpans++),
8466 shift + firstDifferentPosition, shift + lastDifferentPosition,
8467 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8468 } else {
8469 int lastSourceWordEnd = sourceWordLimits.length == 0 ? 0 :
8470 extractRangeEndFromLong(sourceWordLimits[sourceWordLimits.length - 1]);
8471 String sourceSpaces = sourceText.substring(lastSourceWordEnd, sourceText.length());
8472
8473 int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 :
8474 extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
8475 String textSpaces = text.substring(lastCommonTextWordEnd, textView.length());
8476
8477 if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) {
8478 ssb.setSpan(highlightSpan(nbHighlightSpans++),
8479 shift + lastCommonTextWordEnd, shift + textView.length(),
8480 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8481 }
8482 }
8483
8484 // Final part, text after the current suggestion range.
8485 ssb.append(mText.subSequence(spanEnd, unionEnd).toString());
8486 textView.setText(ssb);
8487 }
8488
Gilles Debunne69340442011-03-31 13:37:51 -07008489 public void hide() {
Gilles Debunne214a8622011-04-26 15:44:37 -07008490 if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
8491 ((Editable) mText).removeSpan(mSuggestionRangeSpan);
8492 }
Gilles Debunne69340442011-03-31 13:37:51 -07008493 mContainer.dismiss();
8494 }
8495
8496 @Override
8497 public void onClick(View view) {
8498 if (view instanceof TextView) {
8499 TextView textView = (TextView) view;
Gilles Debunne214a8622011-04-26 15:44:37 -07008500 SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
8501 final int spanStart = suggestionInfo.spanStart;
8502 final int spanEnd = suggestionInfo.spanEnd;
8503 if (spanStart != NO_SUGGESTIONS) {
8504 final int suggestionStart = suggestionInfo.suggestionStart;
8505 final int suggestionEnd = suggestionInfo.suggestionEnd;
8506 final String suggestion = textView.getText().subSequence(
8507 suggestionStart, suggestionEnd).toString();
8508 ((Editable) mText).replace(spanStart, spanEnd, suggestion);
Gilles Debunne69340442011-03-31 13:37:51 -07008509 }
8510 }
8511 hide();
8512 }
8513
8514 void positionAtCursor() {
8515 View contentView = mContainer.getContentView();
8516 int width = contentView.getMeasuredWidth();
8517 int height = contentView.getMeasuredHeight();
8518 final int offset = TextView.this.getSelectionStart();
8519 final int line = mLayout.getLineForOffset(offset);
8520 final int lineBottom = mLayout.getLineBottom(line);
8521 float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
8522
8523 final Rect bounds = sCursorControllerTempRect;
8524 bounds.left = (int) (primaryHorizontal - width / 2.0f);
8525 bounds.top = lineBottom;
8526
8527 bounds.right = bounds.left + width;
8528 bounds.bottom = bounds.top + height;
8529
8530 convertFromViewportToContentCoordinates(bounds);
8531
8532 final int[] coords = mTempCoords;
8533 TextView.this.getLocationInWindow(coords);
8534 coords[0] += bounds.left;
8535 coords[1] += bounds.top;
8536
8537 final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
8538 final int screenHeight = displayMetrics.heightPixels;
8539
8540 // Vertical clipping
8541 if (coords[1] + height > screenHeight) {
8542 // Try to position above current line instead
8543 // TODO use top layout instead, reverse suggestion order,
8544 // try full screen vertical down if it still does not fit. TBD with designers.
8545
8546 // Update dimensions from new view
8547 contentView = mContainer.getContentView();
8548 width = contentView.getMeasuredWidth();
8549 height = contentView.getMeasuredHeight();
8550
8551 final int lineTop = mLayout.getLineTop(line);
8552 final int lineHeight = lineBottom - lineTop;
8553 coords[1] -= height + lineHeight;
8554 }
8555
8556 // Horizontal clipping
8557 coords[0] = Math.max(0, coords[0]);
8558 coords[0] = Math.min(displayMetrics.widthPixels - width, coords[0]);
8559
8560 mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
8561 }
8562 }
8563
8564 void showSuggestions() {
8565 if (mSuggestionsPopupWindow == null) {
8566 mSuggestionsPopupWindow = new SuggestionsPopupWindow();
8567 }
8568 hideControllers();
8569 mSuggestionsPopupWindow.show();
8570 }
8571
8572 void hideSuggestions() {
8573 if (mSuggestionsPopupWindow != null) {
8574 mSuggestionsPopupWindow.hide();
8575 }
8576 }
8577
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008578 /**
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008579 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8580 * selection is initiated in this View.
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008581 *
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008582 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8583 * Paste actions, depending on what this View supports.
8584 *
8585 * A custom implementation can add new entries in the default menu in its
8586 * {@link ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The default actions
8587 * can also be removed from the menu using {@link Menu#removeItem(int)} and passing
8588 * {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} or
8589 * {@link android.R.id#paste} ids as parameters.
8590 *
Gilles Debunneddd6f392011-01-27 09:48:01 -08008591 * Returning false from {@link ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will
8592 * prevent the action mode from being started.
8593 *
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008594 * Action click events should be handled by the custom implementation of
8595 * {@link ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8596 *
8597 * Note that text selection mode is not started when a TextView receives focus and the
8598 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8599 * that case, to allow for quick replacement.
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008600 */
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008601 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
8602 mCustomSelectionActionModeCallback = actionModeCallback;
8603 }
8604
8605 /**
8606 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8607 *
8608 * @return The current custom selection callback.
8609 */
8610 public ActionMode.Callback getCustomSelectionActionModeCallback() {
8611 return mCustomSelectionActionModeCallback;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008612 }
8613
8614 /**
8615 *
8616 * @return true if the selection mode was actually started.
8617 */
8618 private boolean startSelectionActionMode() {
8619 if (mSelectionActionMode != null) {
8620 // Selection action mode is already started
8621 return false;
8622 }
8623
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008624 if (!canSelectText() || !requestFocus()) {
8625 Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
8626 return false;
8627 }
8628
Gilles Debunnec59269f2011-04-22 11:46:09 -07008629 boolean currentWordSelected = selectCurrentWord();
8630 if (!currentWordSelected) {
8631 // No word found under cursor or text selection not permitted.
8632 return false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08008633 }
8634
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008635 ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
Gilles Debunne6da7e932010-12-07 14:28:14 -08008636 mSelectionActionMode = startActionMode(actionModeCallback);
Gilles Debunne17d31de2011-01-27 11:02:18 -08008637 final boolean selectionStarted = mSelectionActionMode != null;
8638
8639 if (selectionStarted && !mTextIsSelectable) {
8640 // Show the IME to be able to replace text, except when selecting non editable text.
8641 final InputMethodManager imm = InputMethodManager.peekInstance();
8642 if (imm != null) imm.showSoftInput(this, 0, null);
8643 }
8644
8645 return selectionStarted;
Gilles Debunne05336272010-07-09 20:13:45 -07008646 }
8647
Gilles Debunneed279f82010-08-18 21:24:35 -07008648 private void stopSelectionActionMode() {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008649 if (mSelectionActionMode != null) {
Gilles Debunned94f8c52011-01-10 11:29:15 -08008650 // This will hide the mSelectionModifierCursorController
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008651 mSelectionActionMode.finish();
8652 }
8653 }
8654
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008655 /**
8656 * Paste clipboard content between min and max positions.
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008657 */
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008658 private void paste(int min, int max) {
8659 ClipboardManager clipboard =
8660 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008661 ClipData clip = clipboard.getPrimaryClip();
8662 if (clip != null) {
Gilles Debunne75beb332011-04-29 11:40:22 -07008663 boolean didFirst = false;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008664 for (int i=0; i<clip.getItemCount(); i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08008665 CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008666 if (paste != null) {
Gilles Debunne75beb332011-04-29 11:40:22 -07008667 if (!didFirst) {
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008668 long minMax = prepareSpacesAroundPaste(min, max, paste);
8669 min = extractRangeStartFromLong(minMax);
8670 max = extractRangeEndFromLong(minMax);
8671 Selection.setSelection((Spannable) mText, max);
8672 ((Editable) mText).replace(min, max, paste);
Gilles Debunne75beb332011-04-29 11:40:22 -07008673 didFirst = true;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008674 } else {
8675 ((Editable) mText).insert(getSelectionEnd(), "\n");
8676 ((Editable) mText).insert(getSelectionEnd(), paste);
8677 }
8678 }
8679 }
8680 stopSelectionActionMode();
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008681 sLastCutOrCopyTime = 0;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008682 }
8683 }
8684
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008685 private void setPrimaryClip(ClipData clip) {
8686 ClipboardManager clipboard = (ClipboardManager) getContext().
8687 getSystemService(Context.CLIPBOARD_SERVICE);
8688 clipboard.setPrimaryClip(clip);
8689 sLastCutOrCopyTime = SystemClock.uptimeMillis();
8690 }
8691
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008692 /**
8693 * An ActionMode Callback class that is used to provide actions while in text selection mode.
8694 *
8695 * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
8696 * on which of these this TextView supports.
8697 */
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008698 private class SelectionActionModeCallback implements ActionMode.Callback {
8699
8700 @Override
8701 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Gilles Debunne78996c92010-10-12 16:01:47 -07008702 TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
8703
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008704 mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
8705 mode.setSubtitle(null);
8706
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008707 menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
Adam Powelld8404b22010-10-13 14:26:41 -07008708 setAlphabeticShortcut('a').
8709 setShowAsAction(
8710 MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008711
8712 if (canCut()) {
8713 menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008714 setIcon(styledAttributes.getResourceId(
8715 R.styleable.Theme_actionModeCutDrawable, 0)).
Adam Powelld8404b22010-10-13 14:26:41 -07008716 setAlphabeticShortcut('x').
8717 setShowAsAction(
8718 MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008719 }
8720
8721 if (canCopy()) {
8722 menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008723 setIcon(styledAttributes.getResourceId(
8724 R.styleable.Theme_actionModeCopyDrawable, 0)).
Adam Powelld8404b22010-10-13 14:26:41 -07008725 setAlphabeticShortcut('c').
8726 setShowAsAction(
8727 MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008728 }
8729
8730 if (canPaste()) {
8731 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008732 setIcon(styledAttributes.getResourceId(
8733 R.styleable.Theme_actionModePasteDrawable, 0)).
Adam Powelld8404b22010-10-13 14:26:41 -07008734 setAlphabeticShortcut('v').
8735 setShowAsAction(
8736 MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008737 }
8738
Gilles Debunne78996c92010-10-12 16:01:47 -07008739 styledAttributes.recycle();
8740
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008741 if (mCustomSelectionActionModeCallback != null) {
Gilles Debunneddd6f392011-01-27 09:48:01 -08008742 if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
8743 // The custom mode can choose to cancel the action mode
8744 return false;
8745 }
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008746 }
8747
8748 if (menu.hasVisibleItems() || mode.getCustomView() != null) {
Gilles Debunnee587d832010-11-23 20:20:11 -08008749 getSelectionController().show();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008750 return true;
8751 } else {
8752 return false;
8753 }
Gilles Debunne05336272010-07-09 20:13:45 -07008754 }
8755
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008756 @Override
8757 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008758 if (mCustomSelectionActionModeCallback != null) {
8759 return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
8760 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008761 return true;
8762 }
8763
8764 @Override
8765 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008766 if (mCustomSelectionActionModeCallback != null &&
8767 mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
8768 return true;
8769 }
Jeff Brownc1df9072010-12-21 16:38:50 -08008770 return onTextContextMenuItem(item.getItemId());
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008771 }
8772
8773 @Override
8774 public void onDestroyActionMode(ActionMode mode) {
Gilles Debunnef4dceb12010-12-01 15:54:20 -08008775 if (mCustomSelectionActionModeCallback != null) {
8776 mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
8777 }
Gilles Debunned94f8c52011-01-10 11:29:15 -08008778 Selection.setSelection((Spannable) mText, getSelectionEnd());
8779
8780 if (mSelectionModifierCursorController != null) {
8781 mSelectionModifierCursorController.hide();
8782 }
8783
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008784 mSelectionActionMode = null;
8785 }
8786 }
Gilles Debunne05336272010-07-09 20:13:45 -07008787
Gilles Debunne69340442011-03-31 13:37:51 -07008788 private class PastePopupWindow implements OnClickListener {
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008789 private final PopupWindow mContainer;
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008790 private final View[] mPasteViews = new View[4];
8791 private final int[] mPasteViewLayouts = new int[] {
8792 mTextEditPasteWindowLayout, mTextEditNoPasteWindowLayout,
8793 mTextEditSidePasteWindowLayout, mTextEditSideNoPasteWindowLayout };
8794
Gilles Debunne69340442011-03-31 13:37:51 -07008795 public PastePopupWindow() {
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008796 mContainer = new PopupWindow(TextView.this.mContext, null,
8797 com.android.internal.R.attr.textSelectHandleWindowStyle);
8798 mContainer.setSplitTouchEnabled(true);
8799 mContainer.setClippingEnabled(false);
8800 mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
8801
8802 mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
8803 mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
8804 }
8805
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008806 private int viewIndex(boolean onTop) {
8807 return (onTop ? 0 : 1<<1) + (canPaste() ? 0 : 1<<0);
8808 }
8809
8810 private void updateContent(boolean onTop) {
8811 final int viewIndex = viewIndex(onTop);
8812 View view = mPasteViews[viewIndex];
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008813
8814 if (view == null) {
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008815 final int layout = mPasteViewLayouts[viewIndex];
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008816 LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
8817 getSystemService(Context.LAYOUT_INFLATER_SERVICE);
8818 if (inflater != null) {
8819 view = inflater.inflate(layout, null);
8820 }
8821
8822 if (view == null) {
8823 throw new IllegalArgumentException("Unable to inflate TextEdit paste window");
8824 }
8825
8826 final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
8827 view.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
8828 ViewGroup.LayoutParams.WRAP_CONTENT));
8829 view.measure(size, size);
8830
8831 view.setOnClickListener(this);
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008832
8833 mPasteViews[viewIndex] = view;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008834 }
8835
8836 mContainer.setContentView(view);
8837 }
8838
8839 public void show() {
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008840 updateContent(true);
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008841 positionAtCursor();
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008842 }
8843
8844 public void hide() {
8845 mContainer.dismiss();
8846 }
8847
8848 public boolean isShowing() {
8849 return mContainer.isShowing();
8850 }
8851
8852 @Override
8853 public void onClick(View v) {
Gilles Debunnee1c14e62010-11-03 19:24:29 -07008854 if (canPaste()) {
Gilles Debunne0a2aa402010-11-24 17:57:46 -08008855 paste(getSelectionStart(), getSelectionEnd());
Gilles Debunnee1c14e62010-11-03 19:24:29 -07008856 }
8857 hide();
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008858 }
8859
8860 void positionAtCursor() {
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008861 View contentView = mContainer.getContentView();
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008862 int width = contentView.getMeasuredWidth();
8863 int height = contentView.getMeasuredHeight();
8864 final int offset = TextView.this.getSelectionStart();
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008865 final int line = mLayout.getLineForOffset(offset);
8866 final int lineTop = mLayout.getLineTop(line);
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008867 float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008868
8869 final Rect bounds = sCursorControllerTempRect;
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008870 bounds.left = (int) (primaryHorizontal - width / 2.0f);
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008871 bounds.top = lineTop - height;
8872
8873 bounds.right = bounds.left + width;
8874 bounds.bottom = bounds.top + height;
8875
8876 convertFromViewportToContentCoordinates(bounds);
8877
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008878 final int[] coords = mTempCoords;
8879 TextView.this.getLocationInWindow(coords);
Gilles Debunne69340442011-03-31 13:37:51 -07008880 coords[0] += bounds.left;
8881 coords[1] += bounds.top;
Gilles Debunnee60e1e52011-01-20 12:19:44 -08008882
8883 final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
8884 if (coords[1] < 0) {
8885 updateContent(false);
8886 // Update dimensions from new view
8887 contentView = mContainer.getContentView();
8888 width = contentView.getMeasuredWidth();
8889 height = contentView.getMeasuredHeight();
8890
8891 // Vertical clipping, move under edited line and to the side of insertion cursor
8892 // TODO bottom clipping in case there is no system bar
8893 coords[1] += height;
8894 final int lineBottom = mLayout.getLineBottom(line);
8895 final int lineHeight = lineBottom - lineTop;
8896 coords[1] += lineHeight;
8897
8898 // Move to right hand side of insertion cursor by default. TODO RTL text.
8899 final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
8900 final int handleHalfWidth = handle.getIntrinsicWidth() / 2;
8901
8902 if (primaryHorizontal + handleHalfWidth + width < screenWidth) {
8903 coords[0] += handleHalfWidth + width / 2;
8904 } else {
8905 coords[0] -= handleHalfWidth + width / 2;
8906 }
8907 } else {
8908 // Horizontal clipping
8909 coords[0] = Math.max(0, coords[0]);
8910 coords[0] = Math.min(screenWidth - width, coords[0]);
8911 }
8912
8913 mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008914 }
8915 }
8916
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008917 private abstract class HandleView extends View implements ViewTreeObserver.OnPreDrawListener {
8918 protected Drawable mDrawable;
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008919 private final PopupWindow mContainer;
8920 // Position with respect to the parent TextView
8921 private int mPositionX, mPositionY;
Adam Powell879fb6b2010-09-20 11:23:56 -07008922 private boolean mIsDragging;
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008923 // Offset from touch position to mPosition
8924 private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008925 protected float mHotspotX;
Gilles Debunneddf00b82011-02-23 17:25:13 -08008926 // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
Adam Powellfbb3b472010-10-06 21:04:35 -07008927 private float mTouchOffsetY;
Gilles Debunneddf00b82011-02-23 17:25:13 -08008928 // Where the touch position should be on the handle to ensure a maximum cursor visibility
8929 private float mIdealVerticalOffset;
Gilles Debunne2037b822011-04-22 13:07:33 -07008930 // Parent's (TextView) previous position in window
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008931 private int mLastParentX, mLastParentY;
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008932 // PopupWindow container absolute position with respect to the enclosing window
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008933 private int mContainerPositionX, mContainerPositionY;
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008934 // Visible or not (scrolled off screen), whether or not this handle should be visible
8935 private boolean mIsActive = false;
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008936
8937 public HandleView() {
8938 super(TextView.this.mContext);
8939 mContainer = new PopupWindow(TextView.this.mContext, null,
8940 com.android.internal.R.attr.textSelectHandleWindowStyle);
8941 mContainer.setSplitTouchEnabled(true);
8942 mContainer.setClippingEnabled(false);
8943 mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
8944 mContainer.setContentView(this);
8945
8946 initDrawable();
8947
8948 final int handleHeight = mDrawable.getIntrinsicHeight();
8949 mTouchOffsetY = -0.3f * handleHeight;
8950 mIdealVerticalOffset = 0.7f * handleHeight;
8951 }
8952
8953 protected abstract void initDrawable();
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008954
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008955 // Touch-up filter: number of previous positions remembered
8956 private static final int HISTORY_SIZE = 5;
Gilles Debunnebc7a4c82011-02-09 10:45:51 -08008957 private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
8958 private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008959 private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
8960 private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
8961 private int mPreviousOffsetIndex = 0;
8962 private int mNumberPreviousOffsets = 0;
8963
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008964 private void startTouchUpFilter(int offset) {
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008965 mNumberPreviousOffsets = 0;
8966 addPositionToTouchUpFilter(offset);
8967 }
8968
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008969 private void addPositionToTouchUpFilter(int offset) {
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008970 if (mNumberPreviousOffsets > 0 &&
8971 mPreviousOffsets[mPreviousOffsetIndex] == offset) {
8972 // Make sure only actual changes of position are recorded.
8973 return;
8974 }
8975
8976 mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
8977 mPreviousOffsets[mPreviousOffsetIndex] = offset;
8978 mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
8979 mNumberPreviousOffsets++;
8980 }
8981
Gilles Debunnecfc22c52011-03-07 15:50:47 -08008982 private void filterOnTouchUp() {
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008983 final long now = SystemClock.uptimeMillis();
8984 int i = 0;
Gilles Debunnebc7a4c82011-02-09 10:45:51 -08008985 int index = mPreviousOffsetIndex;
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008986 final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
Gilles Debunnebc7a4c82011-02-09 10:45:51 -08008987 while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008988 i++;
Gilles Debunnebc7a4c82011-02-09 10:45:51 -08008989 index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008990 }
8991
Gilles Debunnebc7a4c82011-02-09 10:45:51 -08008992 if (i > 0 && i < iMax &&
8993 (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008994 updateOffset(mPreviousOffsets[index]);
Gilles Debunnebc7a4c82011-02-09 10:45:51 -08008995 }
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08008996 }
8997
Adam Powell879fb6b2010-09-20 11:23:56 -07008998 @Override
Gilles Debunne7b9652b2010-10-26 16:27:12 -07008999 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009000 setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
Adam Powell879fb6b2010-09-20 11:23:56 -07009001 }
9002
9003 public void show() {
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009004 if (isShowing()) {
9005 mContainer.update(mContainerPositionX, mContainerPositionY,
9006 mRight - mLeft, mBottom - mTop);
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009007 } else {
9008 mContainer.showAtLocation(TextView.this, 0,
9009 mContainerPositionX, mContainerPositionY);
9010
9011 mIsActive = true;
9012
9013 ViewTreeObserver vto = TextView.this.getViewTreeObserver();
9014 vto.addOnPreDrawListener(this);
9015 }
9016 }
9017
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009018 protected void dismiss() {
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009019 mIsDragging = false;
9020 mContainer.dismiss();
Adam Powell879fb6b2010-09-20 11:23:56 -07009021 }
9022
9023 public void hide() {
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009024 dismiss();
9025
9026 mIsActive = false;
9027
Gilles Debunneb7012e842011-02-24 15:40:38 -08009028 ViewTreeObserver vto = TextView.this.getViewTreeObserver();
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009029 vto.removeOnPreDrawListener(this);
Adam Powell879fb6b2010-09-20 11:23:56 -07009030 }
9031
9032 public boolean isShowing() {
9033 return mContainer.isShowing();
9034 }
9035
Adam Powellabcbb1a2010-10-04 21:12:19 -07009036 private boolean isPositionVisible() {
9037 // Always show a dragging handle.
9038 if (mIsDragging) {
9039 return true;
9040 }
9041
Adam Powell965b9692010-10-21 18:44:32 -07009042 if (isInBatchEditMode()) {
9043 return false;
9044 }
9045
Adam Powell879fb6b2010-09-20 11:23:56 -07009046 final int extendedPaddingTop = getExtendedPaddingTop();
9047 final int extendedPaddingBottom = getExtendedPaddingBottom();
9048 final int compoundPaddingLeft = getCompoundPaddingLeft();
9049 final int compoundPaddingRight = getCompoundPaddingRight();
9050
Gilles Debunne2037b822011-04-22 13:07:33 -07009051 final TextView textView = TextView.this;
Adam Powell879fb6b2010-09-20 11:23:56 -07009052
Gilles Debunne716dbf62011-03-07 18:12:10 -08009053 if (mTempRect == null) mTempRect = new Rect();
Adam Powellabcbb1a2010-10-04 21:12:19 -07009054 final Rect clip = mTempRect;
Gilles Debunne81f08082011-02-17 14:07:19 -08009055 clip.left = compoundPaddingLeft;
9056 clip.top = extendedPaddingTop;
Gilles Debunne2037b822011-04-22 13:07:33 -07009057 clip.right = textView.getWidth() - compoundPaddingRight;
9058 clip.bottom = textView.getHeight() - extendedPaddingBottom;
Adam Powell879fb6b2010-09-20 11:23:56 -07009059
Gilles Debunne2037b822011-04-22 13:07:33 -07009060 final ViewParent parent = textView.getParent();
9061 if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
Adam Powellabcbb1a2010-10-04 21:12:19 -07009062 return false;
9063 }
9064
9065 final int[] coords = mTempCoords;
Gilles Debunne2037b822011-04-22 13:07:33 -07009066 textView.getLocationInWindow(coords);
Adam Powellabcbb1a2010-10-04 21:12:19 -07009067 final int posX = coords[0] + mPositionX + (int) mHotspotX;
Gilles Debunneddf00b82011-02-23 17:25:13 -08009068 final int posY = coords[1] + mPositionY;
Adam Powellabcbb1a2010-10-04 21:12:19 -07009069
Gilles Debunne81f08082011-02-17 14:07:19 -08009070 // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal.
9071 return posX >= clip.left - 1 && posX <= clip.right + 1 &&
Adam Powellfbb3b472010-10-06 21:04:35 -07009072 posY >= clip.top && posY <= clip.bottom;
Adam Powell879fb6b2010-09-20 11:23:56 -07009073 }
9074
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009075 public abstract int getCurrentCursorOffset();
9076
9077 public abstract void updateOffset(int offset);
9078
9079 public abstract void updatePosition(int x, int y);
9080
9081 protected void positionAtCursorOffset(int offset) {
9082 addPositionToTouchUpFilter(offset);
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009083 final int line = mLayout.getLineForOffset(offset);
9084 final int lineBottom = mLayout.getLineBottom(line);
9085
Gilles Debunne2037b822011-04-22 13:07:33 -07009086 mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
9087 mPositionY = lineBottom;
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009088
Gilles Debunne2037b822011-04-22 13:07:33 -07009089 // Take TextView's padding into account.
9090 mPositionX += viewportToContentHorizontalOffset();
9091 mPositionY += viewportToContentVerticalOffset();
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009092 }
9093
Gilles Debunne2037b822011-04-22 13:07:33 -07009094 protected boolean updateContainerPosition() {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009095 positionAtCursorOffset(getCurrentCursorOffset());
Gilles Debunne7b9652b2010-10-26 16:27:12 -07009096
Gilles Debunne2037b822011-04-22 13:07:33 -07009097 final int previousContainerPositionX = mContainerPositionX;
9098 final int previousContainerPositionY = mContainerPositionY;
9099
9100 TextView.this.getLocationInWindow(mTempCoords);
9101 mContainerPositionX = mTempCoords[0] + mPositionX;
9102 mContainerPositionY = mTempCoords[1] + mPositionY;
9103
9104 return (previousContainerPositionX != mContainerPositionX ||
9105 previousContainerPositionY != mContainerPositionY);
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009106 }
9107
9108 public boolean onPreDraw() {
9109 if (updateContainerPosition()) {
Gilles Debunne2037b822011-04-22 13:07:33 -07009110 if (mIsDragging) {
9111 if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
9112 mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
9113 mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
9114 mLastParentX = mTempCoords[0];
9115 mLastParentY = mTempCoords[1];
9116 }
9117 }
9118
9119 onHandleMoved();
9120
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009121 if (isPositionVisible()) {
9122 mContainer.update(mContainerPositionX, mContainerPositionY,
9123 mRight - mLeft, mBottom - mTop);
9124
9125 if (mIsActive && !isShowing()) {
9126 show();
Gilles Debunne7b9652b2010-10-26 16:27:12 -07009127 }
Adam Powell879fb6b2010-09-20 11:23:56 -07009128 } else {
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009129 if (isShowing()) {
9130 dismiss();
9131 }
Adam Powell879fb6b2010-09-20 11:23:56 -07009132 }
Adam Powell879fb6b2010-09-20 11:23:56 -07009133 }
Gilles Debunnecfc22c52011-03-07 15:50:47 -08009134 return true;
Adam Powell879fb6b2010-09-20 11:23:56 -07009135 }
9136
9137 @Override
Gilles Debunne7b9652b2010-10-26 16:27:12 -07009138 protected void onDraw(Canvas c) {
Adam Powell879fb6b2010-09-20 11:23:56 -07009139 mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08009140 mDrawable.draw(c);
Adam Powell879fb6b2010-09-20 11:23:56 -07009141 }
9142
9143 @Override
9144 public boolean onTouchEvent(MotionEvent ev) {
9145 switch (ev.getActionMasked()) {
Gilles Debunne874d77c2011-01-25 15:29:13 -08009146 case MotionEvent.ACTION_DOWN: {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009147 startTouchUpFilter(getCurrentCursorOffset());
9148 mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
9149 mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
Gilles Debunneddf00b82011-02-23 17:25:13 -08009150
Gilles Debunne874d77c2011-01-25 15:29:13 -08009151 final int[] coords = mTempCoords;
9152 TextView.this.getLocationInWindow(coords);
9153 mLastParentX = coords[0];
9154 mLastParentY = coords[1];
9155 mIsDragging = true;
9156 break;
9157 }
9158
9159 case MotionEvent.ACTION_MOVE: {
9160 final float rawX = ev.getRawX();
9161 final float rawY = ev.getRawY();
Gilles Debunneddf00b82011-02-23 17:25:13 -08009162
9163 // Vertical hysteresis: vertical down movement tends to snap to ideal offset
9164 final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
9165 final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
9166 float newVerticalOffset;
9167 if (previousVerticalOffset < mIdealVerticalOffset) {
9168 newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
9169 newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
9170 } else {
9171 newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
9172 newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
9173 }
9174 mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
9175
Gilles Debunne874d77c2011-01-25 15:29:13 -08009176 final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
Gilles Debunneddf00b82011-02-23 17:25:13 -08009177 final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
Gilles Debunne874d77c2011-01-25 15:29:13 -08009178
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009179 updatePosition(Math.round(newPosX), Math.round(newPosY));
Gilles Debunne874d77c2011-01-25 15:29:13 -08009180 break;
9181 }
9182
9183 case MotionEvent.ACTION_UP:
Gilles Debunne874d77c2011-01-25 15:29:13 -08009184 filterOnTouchUp();
9185 mIsDragging = false;
9186 break;
Gilles Debunneaa85a4c2010-12-06 18:27:17 -08009187
Gilles Debunne874d77c2011-01-25 15:29:13 -08009188 case MotionEvent.ACTION_CANCEL:
9189 mIsDragging = false;
9190 break;
Adam Powell879fb6b2010-09-20 11:23:56 -07009191 }
9192 return true;
9193 }
9194
9195 public boolean isDragging() {
9196 return mIsDragging;
Gilles Debunnea6d7ee12010-08-13 14:43:10 -07009197 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07009198
Gilles Debunne2037b822011-04-22 13:07:33 -07009199 void onHandleMoved() {
9200 // Does nothing by default
Gilles Debunnea6d7ee12010-08-13 14:43:10 -07009201 }
Gilles Debunne69340442011-03-31 13:37:51 -07009202
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009203 public void onDetached() {
9204 // Should be overriden to clean possible Runnable
Gilles Debunne81f08082011-02-17 14:07:19 -08009205 }
Gilles Debunnea6d7ee12010-08-13 14:43:10 -07009206 }
9207
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009208 private class InsertionHandleView extends HandleView {
Gilles Debunne81f08082011-02-17 14:07:19 -08009209 private static final int DELAY_BEFORE_FADE_OUT = 4000;
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009210 private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009211
Gilles Debunne69340442011-03-31 13:37:51 -07009212 // Used to detect taps on the insertion handle, which will affect the PastePopupWindow
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009213 private long mTouchTimer;
9214 private float mDownPositionX, mDownPositionY;
Gilles Debunne69340442011-03-31 13:37:51 -07009215 private PastePopupWindow mPastePopupWindow;
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009216 private Runnable mHider;
9217 private Runnable mPastePopupShower;
Gilles Debunne0a2aa402010-11-24 17:57:46 -08009218
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009219 @Override
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009220 public void show() {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009221 super.show();
9222 hideDelayed();
Gilles Debunne2037b822011-04-22 13:07:33 -07009223 hidePastePopupWindow();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009224 }
9225
Gilles Debunne0a2aa402010-11-24 17:57:46 -08009226 public void show(int delayBeforePaste) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009227 show();
9228
Gilles Debunnee60e1e52011-01-20 12:19:44 -08009229 final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
9230 if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
9231 delayBeforePaste = 0;
9232 }
9233 if (delayBeforePaste == 0 || canPaste()) {
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009234 if (mPastePopupShower == null) {
9235 mPastePopupShower = new Runnable() {
9236 public void run() {
Gilles Debunne2037b822011-04-22 13:07:33 -07009237 showPastePopupWindow();
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009238 }
9239 };
9240 }
Gilles Debunne2037b822011-04-22 13:07:33 -07009241 TextView.this.postDelayed(mPastePopupShower, delayBeforePaste);
Gilles Debunne0a2aa402010-11-24 17:57:46 -08009242 }
Gilles Debunne9948ad72010-11-24 14:00:46 -08009243 }
9244
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009245 @Override
9246 protected void dismiss() {
9247 super.dismiss();
9248 onDetached();
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009249 }
9250
Gilles Debunne2a7f3462010-11-24 16:31:26 -08009251 private void hideDelayed() {
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009252 removeHiderCallback();
9253 if (mHider == null) {
9254 mHider = new Runnable() {
9255 public void run() {
9256 hide();
9257 }
9258 };
9259 }
Gilles Debunne2037b822011-04-22 13:07:33 -07009260 TextView.this.postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009261 }
9262
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009263 private void removeHiderCallback() {
9264 if (mHider != null) {
Gilles Debunne2037b822011-04-22 13:07:33 -07009265 TextView.this.removeCallbacks(mHider);
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009266 }
9267 }
9268
9269 @Override
9270 protected void initDrawable() {
9271 if (mSelectHandleCenter == null) {
9272 mSelectHandleCenter = mContext.getResources().getDrawable(
9273 mTextSelectHandleRes);
9274 }
9275 mDrawable = mSelectHandleCenter;
9276 mHotspotX = mDrawable.getIntrinsicWidth() / 2.0f;
9277 }
9278
9279 @Override
9280 public boolean onTouchEvent(MotionEvent ev) {
9281 final boolean result = super.onTouchEvent(ev);
9282
9283 switch (ev.getActionMasked()) {
9284 case MotionEvent.ACTION_DOWN:
9285 mDownPositionX = ev.getRawX();
9286 mDownPositionY = ev.getRawY();
9287 mTouchTimer = SystemClock.uptimeMillis();
9288 break;
9289
9290 case MotionEvent.ACTION_UP:
9291 long delay = SystemClock.uptimeMillis() - mTouchTimer;
9292 if (delay < ViewConfiguration.getTapTimeout()) {
9293 final float deltaX = mDownPositionX - ev.getRawX();
9294 final float deltaY = mDownPositionY - ev.getRawY();
9295 final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
9296 if (distanceSquared < mSquaredTouchSlopDistance) {
9297 if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) {
9298 // Tapping on the handle dismisses the displayed paste view,
9299 mPastePopupWindow.hide();
9300 } else {
9301 show(0);
9302 }
9303 }
9304 }
Gilles Debunne2037b822011-04-22 13:07:33 -07009305 hideDelayed();
9306 break;
9307
9308 case MotionEvent.ACTION_CANCEL:
9309 hideDelayed();
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009310 break;
9311
9312 default:
9313 break;
9314 }
9315
9316 return result;
9317 }
9318
9319 @Override
9320 public int getCurrentCursorOffset() {
9321 return TextView.this.getSelectionStart();
9322 }
9323
9324 @Override
9325 public void updateOffset(int offset) {
9326 Selection.setSelection((Spannable) mText, offset);
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009327 }
9328
9329 @Override
9330 public void updatePosition(int x, int y) {
Gilles Debunne2037b822011-04-22 13:07:33 -07009331 updateOffset(getOffset(x, y));
Gilles Debunne05336272010-07-09 20:13:45 -07009332 }
9333
Gilles Debunne2037b822011-04-22 13:07:33 -07009334 void showPastePopupWindow() {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009335 if (mPastePopupWindow == null) {
Gilles Debunne69340442011-03-31 13:37:51 -07009336 mPastePopupWindow = new PastePopupWindow();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009337 }
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009338 mPastePopupWindow.show();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009339 }
9340
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009341 @Override
Gilles Debunne2037b822011-04-22 13:07:33 -07009342 void onHandleMoved() {
9343 removeHiderCallback();
9344 hidePastePopupWindow();
9345 }
9346
9347 void hidePastePopupWindow() {
9348 if (mPastePopupShower != null) {
9349 TextView.this.removeCallbacks(mPastePopupShower);
9350 }
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009351 if (mPastePopupWindow != null) {
9352 mPastePopupWindow.hide();
9353 }
9354 }
9355
9356 @Override
9357 public void onDetached() {
9358 removeHiderCallback();
Gilles Debunne2037b822011-04-22 13:07:33 -07009359 hidePastePopupWindow();
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009360 }
9361 }
9362
9363 private class SelectionStartHandleView extends HandleView {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009364 @Override
9365 protected void initDrawable() {
9366 if (mSelectHandleLeft == null) {
9367 mSelectHandleLeft = mContext.getResources().getDrawable(
9368 mTextSelectHandleLeftRes);
9369 }
9370 mDrawable = mSelectHandleLeft;
9371 mHotspotX = mDrawable.getIntrinsicWidth() * 3.0f / 4.0f;
9372 }
9373
9374 @Override
9375 public int getCurrentCursorOffset() {
9376 return TextView.this.getSelectionStart();
9377 }
9378
9379 @Override
9380 public void updateOffset(int offset) {
9381 Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009382 }
9383
9384 @Override
9385 public void updatePosition(int x, int y) {
9386 final int selectionStart = getSelectionStart();
9387 final int selectionEnd = getSelectionEnd();
9388
9389 int offset = getOffset(x, y);
9390
9391 // No need to redraw when the offset is unchanged
9392 if (offset == selectionStart) return;
9393 // Handles can not cross and selection is at least one character
9394 if (offset >= selectionEnd) offset = selectionEnd - 1;
9395
9396 Selection.setSelection((Spannable) mText, offset, selectionEnd);
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009397 }
9398 }
9399
9400 private class SelectionEndHandleView extends HandleView {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009401 @Override
9402 protected void initDrawable() {
9403 if (mSelectHandleRight == null) {
9404 mSelectHandleRight = mContext.getResources().getDrawable(
9405 mTextSelectHandleRightRes);
9406 }
9407 mDrawable = mSelectHandleRight;
9408 mHotspotX = mDrawable.getIntrinsicWidth() / 4.0f;
9409 }
9410
9411 @Override
9412 public int getCurrentCursorOffset() {
9413 return TextView.this.getSelectionEnd();
9414 }
9415
9416 @Override
9417 public void updateOffset(int offset) {
9418 Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009419 }
9420
9421 @Override
9422 public void updatePosition(int x, int y) {
9423 final int selectionStart = getSelectionStart();
9424 final int selectionEnd = getSelectionEnd();
9425
9426 int offset = getOffset(x, y);
9427
9428 // No need to redraw when the offset is unchanged
9429 if (offset == selectionEnd) return;
9430 // Handles can not cross and selection is at least one character
9431 if (offset <= selectionStart) offset = selectionStart + 1;
9432
9433 Selection.setSelection((Spannable) mText, selectionStart, offset);
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009434 }
9435 }
9436
9437 /**
9438 * A CursorController instance can be used to control a cursor in the text.
9439 * It is not used outside of {@link TextView}.
9440 * @hide
9441 */
9442 private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
9443 /**
9444 * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
9445 * See also {@link #hide()}.
9446 */
9447 public void show();
9448
9449 /**
9450 * Hide the cursor controller from screen.
9451 * See also {@link #show()}.
9452 */
9453 public void hide();
9454
9455 /**
9456 * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
9457 * a chance to become active and/or visible.
9458 * @param event The touch event
9459 */
9460 public boolean onTouchEvent(MotionEvent event);
9461
9462 /**
9463 * Called when the view is detached from window. Perform house keeping task, such as
9464 * stopping Runnable thread that would otherwise keep a reference on the context, thus
9465 * preventing the activity from being recycled.
9466 */
9467 public void onDetached();
9468 }
9469
9470 private class InsertionPointCursorController implements CursorController {
9471 private static final int DELAY_BEFORE_PASTE = 2000;
9472
9473 private InsertionHandleView mHandle;
9474
9475 public void show() {
9476 ((InsertionHandleView) getHandle()).show(DELAY_BEFORE_PASTE);
9477 }
9478
9479 public void showWithPaste() {
9480 ((InsertionHandleView) getHandle()).show(0);
9481 }
9482
9483 public void hide() {
9484 if (mHandle != null) {
9485 mHandle.hide();
9486 }
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08009487 }
9488
Adam Powell879fb6b2010-09-20 11:23:56 -07009489 public boolean onTouchEvent(MotionEvent ev) {
9490 return false;
9491 }
Adam Powell624380a2010-10-02 18:12:02 -07009492
9493 public void onTouchModeChanged(boolean isInTouchMode) {
9494 if (!isInTouchMode) {
9495 hide();
9496 }
9497 }
Gilles Debunnec4440f02010-11-24 14:40:48 -08009498
9499 private HandleView getHandle() {
9500 if (mHandle == null) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009501 mHandle = new InsertionHandleView();
Gilles Debunnec4440f02010-11-24 14:40:48 -08009502 }
9503 return mHandle;
9504 }
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009505
9506 @Override
9507 public void onDetached() {
Gilles Debunne6f72cf82011-03-24 16:35:28 -07009508 final ViewTreeObserver observer = getViewTreeObserver();
9509 observer.removeOnTouchModeChangeListener(this);
9510
Gilles Debunne6a855082011-03-11 16:51:24 -08009511 if (mHandle != null) mHandle.onDetached();
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009512 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009513 }
9514
Jeff Brown01ce2e92010-09-26 22:20:12 -07009515 private class SelectionModifierCursorController implements CursorController {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009516 // The cursor controller handles, lazily created when shown.
9517 private SelectionStartHandleView mStartHandle;
9518 private SelectionEndHandleView mEndHandle;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009519 // The offsets of that last touch down event. Remembered to start selection there.
9520 private int mMinTouchOffset, mMaxTouchOffset;
Gilles Debunne05336272010-07-09 20:13:45 -07009521
Gilles Debunne5347c582010-10-27 14:22:35 -07009522 // Double tap detection
9523 private long mPreviousTapUpTime = 0;
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009524 private int mPreviousTapPositionX, mPreviousTapPositionY;
Gilles Debunne5347c582010-10-27 14:22:35 -07009525
Gilles Debunne05336272010-07-09 20:13:45 -07009526 SelectionModifierCursorController() {
Gilles Debunne380b6042010-10-08 16:12:11 -07009527 resetTouchOffsets();
Gilles Debunne05336272010-07-09 20:13:45 -07009528 }
9529
9530 public void show() {
Adam Powell965b9692010-10-21 18:44:32 -07009531 if (isInBatchEditMode()) {
9532 return;
9533 }
9534
Gilles Debunnec4440f02010-11-24 14:40:48 -08009535 // Lazy object creation has to be done before updatePosition() is called.
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009536 if (mStartHandle == null) mStartHandle = new SelectionStartHandleView();
9537 if (mEndHandle == null) mEndHandle = new SelectionEndHandleView();
Gilles Debunnec4440f02010-11-24 14:40:48 -08009538
Adam Powell879fb6b2010-09-20 11:23:56 -07009539 mStartHandle.show();
9540 mEndHandle.show();
Gilles Debunnec4440f02010-11-24 14:40:48 -08009541
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009542 hideInsertionPointCursorController();
Gilles Debunne69340442011-03-31 13:37:51 -07009543 hideSuggestions();
Gilles Debunne05336272010-07-09 20:13:45 -07009544 }
9545
9546 public void hide() {
Gilles Debunnec4440f02010-11-24 14:40:48 -08009547 if (mStartHandle != null) mStartHandle.hide();
9548 if (mEndHandle != null) mEndHandle.hide();
Gilles Debunneaa8d73b2011-01-17 09:16:40 -08009549 }
9550
Adam Powellb08013c2010-09-16 16:28:11 -07009551 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne528c64882010-10-08 11:56:13 -07009552 // This is done even when the View does not have focus, so that long presses can start
9553 // selection and tap can move cursor from this tap position.
Gilles Debunnef076eeb2010-11-29 11:32:53 -08009554 if (isTextEditable() || mTextIsSelectable) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009555 switch (event.getActionMasked()) {
9556 case MotionEvent.ACTION_DOWN:
9557 final int x = (int) event.getX();
9558 final int y = (int) event.getY();
Gilles Debunne05336272010-07-09 20:13:45 -07009559
Gilles Debunnea6d7ee12010-08-13 14:43:10 -07009560 // Remember finger down position, to be able to start selection from there
Gilles Debunne528c64882010-10-08 11:56:13 -07009561 mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
Gilles Debunne05336272010-07-09 20:13:45 -07009562
Gilles Debunne5347c582010-10-27 14:22:35 -07009563 // Double tap detection
9564 long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
Gilles Debunne28253662010-12-01 13:40:43 -08009565 if (duration <= ViewConfiguration.getDoubleTapTimeout() &&
9566 isPositionOnText(x, y)) {
Gilles Debunne5347c582010-10-27 14:22:35 -07009567 final int deltaX = x - mPreviousTapPositionX;
9568 final int deltaY = y - mPreviousTapPositionY;
9569 final int distanceSquared = deltaX * deltaX + deltaY * deltaY;
Gilles Debunneaa85a4c2010-12-06 18:27:17 -08009570 if (distanceSquared < mSquaredTouchSlopDistance) {
Gilles Debunne69340442011-03-31 13:37:51 -07009571 showSuggestions();
Gilles Debunne0eb704c2010-11-30 12:50:54 -08009572 mDiscardNextActionUp = true;
Gilles Debunne5347c582010-10-27 14:22:35 -07009573 }
9574 }
9575
9576 mPreviousTapPositionX = x;
9577 mPreviousTapPositionY = y;
9578
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009579 break;
9580
9581 case MotionEvent.ACTION_POINTER_DOWN:
9582 case MotionEvent.ACTION_POINTER_UP:
9583 // Handle multi-point gestures. Keep min and max offset positions.
9584 // Only activated for devices that correctly handle multi-touch.
9585 if (mContext.getPackageManager().hasSystemFeature(
9586 PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
9587 updateMinAndMaxOffsets(event);
9588 }
9589 break;
Gilles Debunne5347c582010-10-27 14:22:35 -07009590
9591 case MotionEvent.ACTION_UP:
9592 mPreviousTapUpTime = SystemClock.uptimeMillis();
9593 break;
Gilles Debunne05336272010-07-09 20:13:45 -07009594 }
9595 }
Adam Powellb08013c2010-09-16 16:28:11 -07009596 return false;
Gilles Debunne05336272010-07-09 20:13:45 -07009597 }
9598
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009599 /**
9600 * @param event
9601 */
9602 private void updateMinAndMaxOffsets(MotionEvent event) {
9603 int pointerCount = event.getPointerCount();
9604 for (int index = 0; index < pointerCount; index++) {
9605 final int x = (int) event.getX(index);
9606 final int y = (int) event.getY(index);
9607 int offset = getOffset(x, y);
9608 if (offset < mMinTouchOffset) mMinTouchOffset = offset;
9609 if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
9610 }
9611 }
9612
9613 public int getMinTouchOffset() {
9614 return mMinTouchOffset;
9615 }
9616
9617 public int getMaxTouchOffset() {
9618 return mMaxTouchOffset;
Gilles Debunne05336272010-07-09 20:13:45 -07009619 }
9620
Gilles Debunne380b6042010-10-08 16:12:11 -07009621 public void resetTouchOffsets() {
9622 mMinTouchOffset = mMaxTouchOffset = -1;
9623 }
9624
Gilles Debunne05336272010-07-09 20:13:45 -07009625 /**
9626 * @return true iff this controller is currently used to move the selection start.
9627 */
9628 public boolean isSelectionStartDragged() {
Gilles Debunnec4440f02010-11-24 14:40:48 -08009629 return mStartHandle != null && mStartHandle.isDragging();
Gilles Debunne05336272010-07-09 20:13:45 -07009630 }
Adam Powell624380a2010-10-02 18:12:02 -07009631
9632 public void onTouchModeChanged(boolean isInTouchMode) {
9633 if (!isInTouchMode) {
9634 hide();
9635 }
9636 }
Gilles Debunne7eeba5f2010-12-10 16:55:55 -08009637
9638 @Override
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009639 public void onDetached() {
Gilles Debunne6f72cf82011-03-24 16:35:28 -07009640 final ViewTreeObserver observer = getViewTreeObserver();
9641 observer.removeOnTouchModeChangeListener(this);
9642
Gilles Debunne180bb1b2011-03-10 11:14:00 -08009643 if (mStartHandle != null) mStartHandle.onDetached();
9644 if (mEndHandle != null) mEndHandle.onDetached();
9645 }
Gilles Debunne05336272010-07-09 20:13:45 -07009646 }
9647
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009648 private void hideInsertionPointCursorController() {
Gilles Debunnee587d832010-11-23 20:20:11 -08009649 // No need to create the controller to hide it.
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009650 if (mInsertionPointCursorController != null) {
9651 mInsertionPointCursorController.hide();
9652 }
9653 }
9654
Gilles Debunned94f8c52011-01-10 11:29:15 -08009655 /**
9656 * Hides the insertion controller and stops text selection mode, hiding the selection controller
9657 */
Gilles Debunnea6d7ee12010-08-13 14:43:10 -07009658 private void hideControllers() {
9659 hideInsertionPointCursorController();
Gilles Debunned94f8c52011-01-10 11:29:15 -08009660 stopSelectionActionMode();
Gilles Debunne69340442011-03-31 13:37:51 -07009661 hideSuggestions();
Gilles Debunnea6d7ee12010-08-13 14:43:10 -07009662 }
9663
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009664 /**
9665 * Get the offset character closest to the specified absolute position.
9666 *
9667 * @param x The horizontal absolute position of a point on screen
9668 * @param y The vertical absolute position of a point on screen
9669 * @return the character offset for the character whose position is closest to the specified
9670 * position. Returns -1 if there is no layout.
9671 *
9672 * @hide
9673 */
9674 public int getOffset(int x, int y) {
Gilles Debunne3e05a0b2010-08-23 14:55:06 -07009675 if (getLayout() == null) return -1;
Gilles Debunne9948ad72010-11-24 14:00:46 -08009676 final int line = getLineAtCoordinate(y);
9677 final int offset = getOffsetAtCoordinate(line, x);
Gilles Debunne3e05a0b2010-08-23 14:55:06 -07009678 return offset;
9679 }
9680
Gilles Debunne9948ad72010-11-24 14:00:46 -08009681 private int convertToLocalHorizontalCoordinate(int x) {
9682 x -= getTotalPaddingLeft();
9683 // Clamp the position to inside of the view.
9684 x = Math.max(0, x);
9685 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9686 x += getScrollX();
9687 return x;
9688 }
9689
9690 private int getLineAtCoordinate(int y) {
9691 y -= getTotalPaddingTop();
9692 // Clamp the position to inside of the view.
9693 y = Math.max(0, y);
9694 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9695 y += getScrollY();
9696 return getLayout().getLineForVertical(y);
9697 }
9698
9699 private int getOffsetAtCoordinate(int line, int x) {
9700 x = convertToLocalHorizontalCoordinate(x);
9701 return getLayout().getOffsetForHorizontal(line, x);
9702 }
9703
9704 /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
9705 * in the view. Returns false when the position is in the empty space of left/right of text.
9706 */
9707 private boolean isPositionOnText(int x, int y) {
9708 if (getLayout() == null) return false;
9709
9710 final int line = getLineAtCoordinate(y);
9711 x = convertToLocalHorizontalCoordinate(x);
9712
9713 if (x < getLayout().getLineLeft(line)) return false;
9714 if (x > getLayout().getLineRight(line)) return false;
9715 return true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009716 }
9717
Gilles Debunnef170a342010-11-11 11:08:59 -08009718 @Override
9719 public boolean onDragEvent(DragEvent event) {
9720 switch (event.getAction()) {
9721 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunnee587d832010-11-23 20:20:11 -08009722 return hasInsertionController();
Gilles Debunnef170a342010-11-11 11:08:59 -08009723
9724 case DragEvent.ACTION_DRAG_ENTERED:
9725 TextView.this.requestFocus();
9726 return true;
9727
Gilles Debunne2226a192010-11-30 18:50:51 -08009728 case DragEvent.ACTION_DRAG_LOCATION:
Gilles Debunne9948ad72010-11-24 14:00:46 -08009729 final int offset = getOffset((int) event.getX(), (int) event.getY());
Gilles Debunnef170a342010-11-11 11:08:59 -08009730 Selection.setSelection((Spannable)mText, offset);
9731 return true;
Gilles Debunnef170a342010-11-11 11:08:59 -08009732
Gilles Debunne2226a192010-11-30 18:50:51 -08009733 case DragEvent.ACTION_DROP:
9734 onDrop(event);
Gilles Debunnef170a342010-11-11 11:08:59 -08009735 return true;
Gilles Debunnef170a342010-11-11 11:08:59 -08009736
Gilles Debunnef170a342010-11-11 11:08:59 -08009737 case DragEvent.ACTION_DRAG_ENDED:
Gilles Debunne4ae0f292010-11-29 14:56:39 -08009738 case DragEvent.ACTION_DRAG_EXITED:
Gilles Debunnef170a342010-11-11 11:08:59 -08009739 default:
9740 return true;
9741 }
9742 }
9743
Gilles Debunne2226a192010-11-30 18:50:51 -08009744 private void onDrop(DragEvent event) {
9745 StringBuilder content = new StringBuilder("");
9746 ClipData clipData = event.getClipData();
9747 final int itemCount = clipData.getItemCount();
9748 for (int i=0; i < itemCount; i++) {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08009749 Item item = clipData.getItemAt(i);
Gilles Debunne2226a192010-11-30 18:50:51 -08009750 content.append(item.coerceToText(TextView.this.mContext));
9751 }
9752
9753 final int offset = getOffset((int) event.getX(), (int) event.getY());
9754
Gilles Debunneaaa84792010-12-03 11:10:14 -08009755 Object localState = event.getLocalState();
9756 DragLocalState dragLocalState = null;
9757 if (localState instanceof DragLocalState) {
9758 dragLocalState = (DragLocalState) localState;
9759 }
9760 boolean dragDropIntoItself = dragLocalState != null &&
9761 dragLocalState.sourceTextView == this;
9762
9763 if (dragDropIntoItself) {
9764 if (offset >= dragLocalState.start && offset < dragLocalState.end) {
Gilles Debunne2226a192010-11-30 18:50:51 -08009765 // A drop inside the original selection discards the drop.
9766 return;
9767 }
9768 }
9769
9770 final int originalLength = mText.length();
9771 long minMax = prepareSpacesAroundPaste(offset, offset, content);
9772 int min = extractRangeStartFromLong(minMax);
9773 int max = extractRangeEndFromLong(minMax);
9774
9775 Selection.setSelection((Spannable) mText, max);
9776 ((Editable) mText).replace(min, max, content);
9777
Gilles Debunneaaa84792010-12-03 11:10:14 -08009778 if (dragDropIntoItself) {
9779 int dragSourceStart = dragLocalState.start;
9780 int dragSourceEnd = dragLocalState.end;
Gilles Debunne2226a192010-11-30 18:50:51 -08009781 if (max <= dragSourceStart) {
9782 // Inserting text before selection has shifted positions
9783 final int shift = mText.length() - originalLength;
9784 dragSourceStart += shift;
9785 dragSourceEnd += shift;
9786 }
9787
9788 // Delete original selection
9789 ((Editable) mText).delete(dragSourceStart, dragSourceEnd);
9790
9791 // Make sure we do not leave two adjacent spaces.
9792 if ((dragSourceStart == 0 ||
9793 Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
9794 (dragSourceStart == mText.length() ||
9795 Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
9796 final int pos = dragSourceStart == mText.length() ?
9797 dragSourceStart - 1 : dragSourceStart;
9798 ((Editable) mText).delete(pos, pos + 1);
9799 }
9800 }
9801 }
9802
Adam Powell965b9692010-10-21 18:44:32 -07009803 /**
9804 * @return True if this view supports insertion handles.
9805 */
9806 boolean hasInsertionController() {
9807 return mInsertionControllerEnabled;
9808 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009809
Adam Powell965b9692010-10-21 18:44:32 -07009810 /**
9811 * @return True if this view supports selection handles.
9812 */
9813 boolean hasSelectionController() {
9814 return mSelectionControllerEnabled;
9815 }
9816
Gilles Debunne9948ad72010-11-24 14:00:46 -08009817 InsertionPointCursorController getInsertionController() {
Adam Powell965b9692010-10-21 18:44:32 -07009818 if (!mInsertionControllerEnabled) {
9819 return null;
9820 }
9821
9822 if (mInsertionPointCursorController == null) {
9823 mInsertionPointCursorController = new InsertionPointCursorController();
9824
9825 final ViewTreeObserver observer = getViewTreeObserver();
Gilles Debunne81f08082011-02-17 14:07:19 -08009826 observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
Adam Powell965b9692010-10-21 18:44:32 -07009827 }
9828
9829 return mInsertionPointCursorController;
9830 }
9831
Gilles Debunnee587d832010-11-23 20:20:11 -08009832 SelectionModifierCursorController getSelectionController() {
Adam Powell965b9692010-10-21 18:44:32 -07009833 if (!mSelectionControllerEnabled) {
9834 return null;
9835 }
9836
9837 if (mSelectionModifierCursorController == null) {
9838 mSelectionModifierCursorController = new SelectionModifierCursorController();
9839
9840 final ViewTreeObserver observer = getViewTreeObserver();
Gilles Debunne81f08082011-02-17 14:07:19 -08009841 observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
Adam Powell965b9692010-10-21 18:44:32 -07009842 }
9843
9844 return mSelectionModifierCursorController;
9845 }
9846
9847 boolean isInBatchEditMode() {
9848 final InputMethodState ims = mInputMethodState;
9849 if (ims != null) {
9850 return ims.mBatchEditNesting > 0;
9851 }
9852 return mInBatchEditControllers;
9853 }
9854
Romain Guy6d956622010-11-29 11:38:05 -08009855 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009856 private CharSequence mText;
9857 private CharSequence mTransformed;
9858 private BufferType mBufferType = BufferType.NORMAL;
9859
9860 private int mInputType = EditorInfo.TYPE_NULL;
9861 private CharSequence mHint;
9862 private Layout mHintLayout;
9863
9864 private KeyListener mInput;
9865
9866 private MovementMethod mMovement;
9867 private TransformationMethod mTransformation;
9868 private ChangeWatcher mChangeWatcher;
9869
9870 private ArrayList<TextWatcher> mListeners = null;
9871
9872 // display attributes
Gilles Debunneb6ca7232010-06-24 11:45:21 -07009873 private final TextPaint mTextPaint;
Romain Guy939151f2009-04-08 14:22:40 -07009874 private boolean mUserSetTextScaleX;
Gilles Debunneb6ca7232010-06-24 11:45:21 -07009875 private final Paint mHighlightPaint;
Gilles Debunnea6d7ee12010-08-13 14:43:10 -07009876 private int mHighlightColor = 0xCC475925;
Leon Scroggins1ca56262010-11-18 14:03:03 -05009877 /**
9878 * This is temporarily visible to fix bug 3085564 in webView. Do not rely on
9879 * this field being protected. Will be restored as private when lineHeight
9880 * feature request 3215097 is implemented
9881 * @hide
9882 */
9883 protected Layout mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009884
9885 private long mShowCursor;
9886 private Blink mBlink;
9887 private boolean mCursorVisible = true;
9888
Adam Powell965b9692010-10-21 18:44:32 -07009889 // Cursor Controllers.
Gilles Debunne9948ad72010-11-24 14:00:46 -08009890 private InsertionPointCursorController mInsertionPointCursorController;
Gilles Debunnee587d832010-11-23 20:20:11 -08009891 private SelectionModifierCursorController mSelectionModifierCursorController;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009892 private ActionMode mSelectionActionMode;
Adam Powell965b9692010-10-21 18:44:32 -07009893 private boolean mInsertionControllerEnabled;
9894 private boolean mSelectionControllerEnabled;
9895 private boolean mInBatchEditControllers;
9896
Gilles Debunnecf1e9252010-10-07 20:46:03 -07009897 // These are needed to desambiguate a long click. If the long click comes from ones of these, we
9898 // select from the current cursor position. Otherwise, select from long pressed position.
9899 private boolean mDPadCenterIsDown = false;
9900 private boolean mEnterKeyIsDown = false;
Gilles Debunne528c64882010-10-08 11:56:13 -07009901 private boolean mContextMenuTriggeredByKey = false;
Gilles Debunne05336272010-07-09 20:13:45 -07009902 // Created once and shared by different CursorController helper methods.
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009903 // Only one cursor controller is active at any time which prevent race conditions.
9904 private static Rect sCursorControllerTempRect = new Rect();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07009905
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009906 private boolean mSelectAllOnFocus = false;
9907
9908 private int mGravity = Gravity.TOP | Gravity.LEFT;
9909 private boolean mHorizontallyScrolling;
9910
9911 private int mAutoLinkMask;
9912 private boolean mLinksClickable = true;
9913
9914 private float mSpacingMult = 1;
9915 private float mSpacingAdd = 0;
Gilles Debunne86b9c782010-11-11 10:43:48 -08009916 private boolean mTextIsSelectable = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009917
9918 private static final int LINES = 1;
9919 private static final int EMS = LINES;
9920 private static final int PIXELS = 2;
9921
9922 private int mMaximum = Integer.MAX_VALUE;
9923 private int mMaxMode = LINES;
9924 private int mMinimum = 0;
9925 private int mMinMode = LINES;
9926
9927 private int mMaxWidth = Integer.MAX_VALUE;
9928 private int mMaxWidthMode = PIXELS;
9929 private int mMinWidth = 0;
9930 private int mMinWidthMode = PIXELS;
9931
9932 private boolean mSingleLine;
9933 private int mDesiredHeightAtMeasure = -1;
9934 private boolean mIncludePad = true;
9935
9936 // tmp primitives, so we don't alloc them on each draw
9937 private Path mHighlightPath;
9938 private boolean mHighlightPathBogus = true;
9939 private static final RectF sTempRect = new RectF();
9940
9941 // XXX should be much larger
9942 private static final int VERY_WIDE = 16384;
9943
9944 private static final int BLINK = 500;
9945
9946 private static final int ANIMATED_SCROLL_GAP = 250;
9947 private long mLastScroll;
9948 private Scroller mScroller = null;
9949
9950 private BoringLayout.Metrics mBoring;
9951 private BoringLayout.Metrics mHintBoring;
9952
9953 private BoringLayout mSavedLayout, mSavedHintLayout;
9954
9955 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
9956 private InputFilter[] mFilters = NO_FILTERS;
9957 private static final Spanned EMPTY_SPANNED = new SpannedString("");
Christopher Tate36d4c3f2011-01-07 13:34:24 -08009958 private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
Gilles Debunne0a2aa402010-11-24 17:57:46 -08009959 // System wide time for last cut or copy action.
9960 private static long sLastCutOrCopyTime;
Gilles Debunne12d91ce2010-12-10 11:36:29 -08009961 // Used to highlight a word when it is corrected by the IME
9962 private CorrectionHighlighter mCorrectionHighlighter;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08009963 // New state used to change background based on whether this TextView is multiline.
9964 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009965}