package com.xtremelabs.robolectric.shadows; import android.content.Context; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.SpannableStringBuilder; import android.text.TextPaint; import android.text.TextWatcher; import android.text.method.MovementMethod; import android.text.method.TransformationMethod; import android.text.style.URLSpan; import android.text.util.Linkify; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; import android.widget.TextView; import com.xtremelabs.robolectric.internal.Implementation; import com.xtremelabs.robolectric.internal.Implements; import java.util.ArrayList; import java.util.List; import static android.view.View.VISIBLE; import static com.xtremelabs.robolectric.Robolectric.shadowOf; import static com.xtremelabs.robolectric.Robolectric.shadowOf_; @SuppressWarnings({"UnusedDeclaration"}) @Implements(TextView.class) public class ShadowTextView extends ShadowView { private CharSequence text = ""; private CompoundDrawables compoundDrawablesImpl = new CompoundDrawables(0, 0, 0, 0); private Integer textColorHexValue; private Integer hintColorHexValue; private float textSize = 14.0f; private boolean autoLinkPhoneNumbers; private int autoLinkMask; private CharSequence hintText; private CharSequence errorText; private int compoundDrawablePadding; private MovementMethod movementMethod; private boolean linksClickable; private int gravity; private TextView.OnEditorActionListener onEditorActionListener; private int imeOptions = EditorInfo.IME_NULL; private int textAppearanceId; private TransformationMethod transformationMethod; private int inputType; protected int selectionStart = 0; protected int selectionEnd = 0; private Typeface typeface; private List watchers = new ArrayList(); private List previousKeyCodes = new ArrayList(); private List previousKeyEvents = new ArrayList(); private Layout layout; @Override public void applyAttributes() { super.applyAttributes(); applyTextAttribute(); applyTextColorAttribute(); applyHintAttribute(); applyHintColorAttribute(); applyCompoundDrawablesWithIntrinsicBoundsAttributes(); } @Implementation(i18nSafe = false) public void setText(CharSequence text) { if (text == null) { text = ""; } sendBeforeTextChanged(text); CharSequence oldValue = this.text; this.text = text; sendOnTextChanged(oldValue); sendAfterTextChanged(); } @Implementation public final void append(CharSequence text) { boolean isSelectStartAtEnd = selectionStart == this.text.length(); boolean isSelectEndAtEnd = selectionEnd == this.text.length(); CharSequence oldValue = this.text; StringBuffer sb = new StringBuffer(this.text); sb.append(text); sendBeforeTextChanged(sb.toString()); this.text = sb.toString(); if (isSelectStartAtEnd) { selectionStart = this.text.length(); } if (isSelectEndAtEnd) { selectionEnd = this.text.length(); } sendOnTextChanged(oldValue); sendAfterTextChanged(); } @Implementation public void setText(int textResourceId) { sendBeforeTextChanged(text); CharSequence oldValue = this.text; this.text = getResources().getText(textResourceId); sendOnTextChanged(oldValue); sendAfterTextChanged(); } private void sendAfterTextChanged() { for (TextWatcher watcher : watchers) { watcher.afterTextChanged(new SpannableStringBuilder(getText())); } } private void sendOnTextChanged(CharSequence oldValue) { for (TextWatcher watcher : watchers) { watcher.onTextChanged(text, 0, oldValue.length(), text.length()); } } private void sendBeforeTextChanged(CharSequence newValue) { for (TextWatcher watcher : watchers) { watcher.beforeTextChanged(this.text, 0, this.text.length(), newValue.length()); } } @Implementation public CharSequence getText() { return text; } @Implementation public Typeface getTypeface() { return typeface; } @Implementation public void setTypeface(Typeface typeface) { this.typeface = typeface; } @Implementation public int length() { return text.length(); } @Implementation public void setTextColor(int color) { textColorHexValue = color; } @Implementation public void setTextSize(float size) { textSize = size; } @Implementation public void setTextAppearance(Context context, int resid) { textAppearanceId = resid; } @Implementation public void setInputType(int type) { this.inputType = type; } @Implementation public int getInputType() { return this.inputType; } @Implementation public final void setHint(int resId) { this.hintText = getResources().getText(resId); } @Implementation(i18nSafe = false) public final void setHint(CharSequence hintText) { this.hintText = hintText; } @Implementation public CharSequence getHint() { return hintText; } @Implementation public final void setHintTextColor(int color) { hintColorHexValue = color; } @Implementation public final boolean getLinksClickable() { return linksClickable; } @Implementation public final void setLinksClickable(boolean whether) { linksClickable = whether; } @Implementation public final MovementMethod getMovementMethod() { return movementMethod; } @Implementation public final void setMovementMethod(MovementMethod movement) { movementMethod = movement; } @Implementation public URLSpan[] getUrls() { String[] words = text.toString().split("\\s+"); List urlSpans = new ArrayList(); for (String word : words) { if (word.startsWith("http://")) { urlSpans.add(new URLSpan(word)); } } return urlSpans.toArray(new URLSpan[urlSpans.size()]); } @Implementation public final void setAutoLinkMask(int mask) { autoLinkMask = mask; autoLinkPhoneNumbers = (mask & Linkify.PHONE_NUMBERS) != 0; } @Implementation public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom); } @Implementation public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom) { compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom); } @Implementation public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom); } @Implementation public Drawable[] getCompoundDrawables() { if (compoundDrawablesImpl == null) { return new Drawable[]{null, null, null, null}; } return new Drawable[]{ compoundDrawablesImpl.leftDrawable, compoundDrawablesImpl.topDrawable, compoundDrawablesImpl.rightDrawable, compoundDrawablesImpl.bottomDrawable }; } @Implementation public void setCompoundDrawablePadding(int compoundDrawablePadding) { this.compoundDrawablePadding = compoundDrawablePadding; } @Implementation public int getCompoundDrawablePadding() { return compoundDrawablePadding; } @Implementation public boolean onKeyDown(int keyCode, KeyEvent event) { previousKeyCodes.add(keyCode); previousKeyEvents.add(event); if (onKeyListener != null) { return onKeyListener.onKey(realView, keyCode, event); } else { return false; } } @Implementation public boolean onKeyUp(int keyCode, KeyEvent event) { previousKeyCodes.add(keyCode); previousKeyEvents.add(event); if (onKeyListener != null) { return onKeyListener.onKey(realView, keyCode, event); } else { return false; } } public int getPreviousKeyCode(int index) { return previousKeyCodes.get(index); } public KeyEvent getPreviousKeyEvent(int index) { return previousKeyEvents.get(index); } @Implementation public int getGravity() { return gravity; } @Implementation public void setGravity(int gravity) { this.gravity = gravity; } @Implementation public int getImeOptions() { return imeOptions; } @Implementation public void setImeOptions(int imeOptions) { this.imeOptions = imeOptions; } /** * Returns the text string of this {@code TextView}. *

* Robolectric extension. */ @Override public String innerText() { return (text == null || getVisibility() != VISIBLE) ? "" : text.toString(); } @Implementation public void setError(CharSequence error) { errorText = error; } @Implementation public CharSequence getError() { return errorText; } @Override @Implementation public boolean equals(Object o) { return super.equals(shadowOf_(o)); } @Override @Implementation public int hashCode() { return super.hashCode(); } public CompoundDrawables getCompoundDrawablesImpl() { return compoundDrawablesImpl; } void setCompoundDrawablesImpl(CompoundDrawables compoundDrawablesImpl) { this.compoundDrawablesImpl = compoundDrawablesImpl; } public Integer getTextColorHexValue() { return textColorHexValue; } public int getTextAppearanceId() { return textAppearanceId; } public Integer getHintColorHexValue() { return hintColorHexValue; } @Implementation public float getTextSize() { return textSize; } public boolean isAutoLinkPhoneNumbers() { return autoLinkPhoneNumbers; } private void applyTextAttribute() { String text = attributeSet.getAttributeValue("android", "text"); if (text != null) { if (text.startsWith("@string/")) { int textResId = attributeSet.getAttributeResourceValue("android", "text", 0); text = context.getResources().getString(textResId); } setText(text); } } private void applyTextColorAttribute() { String colorValue = attributeSet.getAttributeValue("android", "textColor"); if (colorValue != null) { if (colorValue.startsWith("@color/") || colorValue.startsWith("@android:color/")) { int colorResId = attributeSet.getAttributeResourceValue("android", "textColor", 0); setTextColor(context.getResources().getColor(colorResId)); } else if (colorValue.startsWith("#")) { int colorFromHex = (int) Long.valueOf(colorValue.replaceAll("#", ""), 16).longValue(); setTextColor(colorFromHex); } } } private void applyHintAttribute() { String hint = attributeSet.getAttributeValue("android", "hint"); if (hint != null) { if (hint.startsWith("@string/")) { int textResId = attributeSet.getAttributeResourceValue("android", "hint", 0); hint = context.getResources().getString(textResId); } setHint(hint); } } private void applyHintColorAttribute() { String colorValue = attributeSet.getAttributeValue("android", "hintColor"); if (colorValue != null) { if (colorValue.startsWith("@color/") || colorValue.startsWith("@android:color/")) { int colorResId = attributeSet.getAttributeResourceValue("android", "hintColor", 0); setHintTextColor(context.getResources().getColor(colorResId)); } else if (colorValue.startsWith("#")) { int colorFromHex = (int) Long.valueOf(colorValue.replaceAll("#", ""), 16).longValue(); setHintTextColor(colorFromHex); } } } private void applyCompoundDrawablesWithIntrinsicBoundsAttributes() { setCompoundDrawablesWithIntrinsicBounds( attributeSet.getAttributeResourceValue("android", "drawableLeft", 0), attributeSet.getAttributeResourceValue("android", "drawableTop", 0), attributeSet.getAttributeResourceValue("android", "drawableRight", 0), attributeSet.getAttributeResourceValue("android", "drawableBottom", 0)); } @Implementation public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener onEditorActionListener) { this.onEditorActionListener = onEditorActionListener; } public boolean triggerEditorAction(int imeAction) { if (onEditorActionListener != null) { return onEditorActionListener.onEditorAction((TextView) realView, imeAction, null); } return false; } @Implementation public void setTransformationMethod(TransformationMethod transformationMethod) { this.transformationMethod = transformationMethod; } @Implementation public TransformationMethod getTransformationMethod() { return transformationMethod; } @Implementation public void addTextChangedListener(TextWatcher watcher) { this.watchers.add(watcher); } @Implementation public void removeTextChangedListener(TextWatcher watcher) { this.watchers.remove(watcher); } @Implementation public TextPaint getPaint() { return new TextPaint(); } @Implementation public Layout getLayout() { return this.layout; } public void setSelection(int index) { setSelection(index, index); } public void setSelection(int start, int end) { selectionStart = start; selectionEnd = end; } @Implementation public int getSelectionStart() { return selectionStart; } @Implementation public int getSelectionEnd() { return selectionEnd; } @Implementation public boolean onTouchEvent(MotionEvent event) { boolean superResult = super.onTouchEvent(event); if (movementMethod != null) { boolean handled = movementMethod.onTouchEvent(null, null, event); if (handled) { return true; } } return superResult; } /** * @return the list of currently registered watchers/listeners */ public List getWatchers() { return watchers; } public void setLayout(Layout layout) { this.layout = layout; } public static class CompoundDrawables { public Drawable leftDrawable; public Drawable topDrawable; public Drawable rightDrawable; public Drawable bottomDrawable; public CompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { leftDrawable = left; topDrawable = top; rightDrawable = right; bottomDrawable = bottom; } public CompoundDrawables(int left, int top, int right, int bottom) { leftDrawable = left != 0 ? ShadowDrawable.createFromResourceId(left) : null; topDrawable = top != 0 ? ShadowDrawable.createFromResourceId(top) : null; rightDrawable = right != 0 ? ShadowDrawable.createFromResourceId(right) : null; bottomDrawable = bottom != 0 ? ShadowDrawable.createFromResourceId(bottom) : null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CompoundDrawables that = (CompoundDrawables) o; if (getBottom() != that.getBottom()) return false; if (getLeft() != that.getLeft()) return false; if (getRight() != that.getRight()) return false; if (getTop() != that.getTop()) return false; return true; } @Override public int hashCode() { int result = getLeft(); result = 31 * result + getTop(); result = 31 * result + getRight(); result = 31 * result + getBottom(); return result; } @Override public String toString() { return "CompoundDrawables{" + "left=" + getLeft() + ", top=" + getTop() + ", right=" + getRight() + ", bottom=" + getBottom() + '}'; } public int getLeft() { return shadowOf(leftDrawable).getLoadedFromResourceId(); } public int getTop() { return shadowOf(topDrawable).getLoadedFromResourceId(); } public int getRight() { return shadowOf(rightDrawable).getLoadedFromResourceId(); } public int getBottom() { return shadowOf(bottomDrawable).getLoadedFromResourceId(); } } }