Home | History | Annotate | Download | only in shadows
      1 package com.xtremelabs.robolectric.shadows;
      2 
      3 import android.content.Context;
      4 import android.graphics.Typeface;
      5 import android.graphics.drawable.Drawable;
      6 import android.text.Layout;
      7 import android.text.SpannableStringBuilder;
      8 import android.text.TextPaint;
      9 import android.text.TextWatcher;
     10 import android.text.method.MovementMethod;
     11 import android.text.method.TransformationMethod;
     12 import android.text.style.URLSpan;
     13 import android.text.util.Linkify;
     14 import android.view.KeyEvent;
     15 import android.view.MotionEvent;
     16 import android.view.inputmethod.EditorInfo;
     17 import android.widget.TextView;
     18 import com.xtremelabs.robolectric.internal.Implementation;
     19 import com.xtremelabs.robolectric.internal.Implements;
     20 
     21 import java.util.ArrayList;
     22 import java.util.List;
     23 
     24 import static android.view.View.VISIBLE;
     25 import static com.xtremelabs.robolectric.Robolectric.shadowOf;
     26 import static com.xtremelabs.robolectric.Robolectric.shadowOf_;
     27 
     28 @SuppressWarnings({"UnusedDeclaration"})
     29 @Implements(TextView.class)
     30 public class ShadowTextView extends ShadowView {
     31     private CharSequence text = "";
     32     private CompoundDrawables compoundDrawablesImpl = new CompoundDrawables(0, 0, 0, 0);
     33     private Integer textColorHexValue;
     34     private Integer hintColorHexValue;
     35     private float textSize = 14.0f;
     36     private boolean autoLinkPhoneNumbers;
     37     private int autoLinkMask;
     38     private CharSequence hintText;
     39     private CharSequence errorText;
     40     private int compoundDrawablePadding;
     41     private MovementMethod movementMethod;
     42     private boolean linksClickable;
     43     private int gravity;
     44     private TextView.OnEditorActionListener onEditorActionListener;
     45     private int imeOptions = EditorInfo.IME_NULL;
     46     private int textAppearanceId;
     47     private TransformationMethod transformationMethod;
     48     private int inputType;
     49     protected int selectionStart = 0;
     50     protected int selectionEnd = 0;
     51     private Typeface typeface;
     52 
     53     private List<TextWatcher> watchers = new ArrayList<TextWatcher>();
     54     private List<Integer> previousKeyCodes = new ArrayList<Integer>();
     55     private List<KeyEvent> previousKeyEvents = new ArrayList<KeyEvent>();
     56     private Layout layout;
     57 
     58     @Override
     59     public void applyAttributes() {
     60         super.applyAttributes();
     61         applyTextAttribute();
     62         applyTextColorAttribute();
     63         applyHintAttribute();
     64         applyHintColorAttribute();
     65         applyCompoundDrawablesWithIntrinsicBoundsAttributes();
     66     }
     67 
     68     @Implementation(i18nSafe = false)
     69     public void setText(CharSequence text) {
     70         if (text == null) {
     71             text = "";
     72         }
     73 
     74         sendBeforeTextChanged(text);
     75 
     76         CharSequence oldValue = this.text;
     77         this.text = text;
     78 
     79         sendOnTextChanged(oldValue);
     80         sendAfterTextChanged();
     81     }
     82 
     83     @Implementation
     84     public final void append(CharSequence text) {
     85         boolean isSelectStartAtEnd = selectionStart == this.text.length();
     86         boolean isSelectEndAtEnd = selectionEnd == this.text.length();
     87         CharSequence oldValue = this.text;
     88         StringBuffer sb = new StringBuffer(this.text);
     89         sb.append(text);
     90 
     91         sendBeforeTextChanged(sb.toString());
     92         this.text = sb.toString();
     93 
     94         if (isSelectStartAtEnd) {
     95             selectionStart = this.text.length();
     96         }
     97         if (isSelectEndAtEnd) {
     98             selectionEnd = this.text.length();
     99         }
    100 
    101         sendOnTextChanged(oldValue);
    102         sendAfterTextChanged();
    103     }
    104 
    105     @Implementation
    106     public void setText(int textResourceId) {
    107         sendBeforeTextChanged(text);
    108 
    109         CharSequence oldValue = this.text;
    110         this.text = getResources().getText(textResourceId);
    111 
    112         sendOnTextChanged(oldValue);
    113         sendAfterTextChanged();
    114     }
    115 
    116     private void sendAfterTextChanged() {
    117         for (TextWatcher watcher : watchers) {
    118             watcher.afterTextChanged(new SpannableStringBuilder(getText()));
    119         }
    120     }
    121 
    122     private void sendOnTextChanged(CharSequence oldValue) {
    123         for (TextWatcher watcher : watchers) {
    124             watcher.onTextChanged(text, 0, oldValue.length(), text.length());
    125         }
    126     }
    127 
    128     private void sendBeforeTextChanged(CharSequence newValue) {
    129         for (TextWatcher watcher : watchers) {
    130             watcher.beforeTextChanged(this.text, 0, this.text.length(), newValue.length());
    131         }
    132     }
    133 
    134     @Implementation
    135     public CharSequence getText() {
    136         return text;
    137     }
    138 
    139     @Implementation
    140     public Typeface getTypeface() {
    141         return typeface;
    142     }
    143 
    144     @Implementation
    145     public void setTypeface(Typeface typeface) {
    146         this.typeface = typeface;
    147     }
    148 
    149     @Implementation
    150     public int length() {
    151         return text.length();
    152     }
    153 
    154     @Implementation
    155     public void setTextColor(int color) {
    156         textColorHexValue = color;
    157     }
    158 
    159     @Implementation
    160     public void setTextSize(float size) {
    161         textSize = size;
    162     }
    163 
    164     @Implementation
    165     public void setTextAppearance(Context context, int resid) {
    166         textAppearanceId = resid;
    167     }
    168 
    169     @Implementation
    170     public void setInputType(int type) {
    171         this.inputType = type;
    172     }
    173 
    174     @Implementation
    175     public int getInputType() {
    176         return this.inputType;
    177     }
    178 
    179     @Implementation
    180     public final void setHint(int resId) {
    181         this.hintText = getResources().getText(resId);
    182     }
    183 
    184     @Implementation(i18nSafe = false)
    185     public final void setHint(CharSequence hintText) {
    186         this.hintText = hintText;
    187     }
    188 
    189     @Implementation
    190     public CharSequence getHint() {
    191         return hintText;
    192     }
    193 
    194     @Implementation
    195     public final void setHintTextColor(int color) {
    196         hintColorHexValue = color;
    197     }
    198 
    199     @Implementation
    200     public final boolean getLinksClickable() {
    201         return linksClickable;
    202     }
    203 
    204     @Implementation
    205     public final void setLinksClickable(boolean whether) {
    206         linksClickable = whether;
    207     }
    208 
    209     @Implementation
    210     public final MovementMethod getMovementMethod() {
    211         return movementMethod;
    212     }
    213 
    214     @Implementation
    215     public final void setMovementMethod(MovementMethod movement) {
    216         movementMethod = movement;
    217     }
    218 
    219     @Implementation
    220     public URLSpan[] getUrls() {
    221         String[] words = text.toString().split("\\s+");
    222         List<URLSpan> urlSpans = new ArrayList<URLSpan>();
    223         for (String word : words) {
    224             if (word.startsWith("http://")) {
    225                 urlSpans.add(new URLSpan(word));
    226             }
    227         }
    228         return urlSpans.toArray(new URLSpan[urlSpans.size()]);
    229     }
    230 
    231     @Implementation
    232     public final void setAutoLinkMask(int mask) {
    233         autoLinkMask = mask;
    234 
    235         autoLinkPhoneNumbers = (mask & Linkify.PHONE_NUMBERS) != 0;
    236     }
    237 
    238     @Implementation
    239     public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
    240         compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom);
    241     }
    242 
    243     @Implementation
    244     public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
    245                                                         Drawable right, Drawable bottom) {
    246         compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom);
    247     }
    248 
    249     @Implementation
    250     public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
    251         compoundDrawablesImpl = new CompoundDrawables(left, top, right, bottom);
    252     }
    253 
    254     @Implementation
    255     public Drawable[] getCompoundDrawables() {
    256         if (compoundDrawablesImpl == null) {
    257             return new Drawable[]{null, null, null, null};
    258         }
    259         return new Drawable[]{
    260                 compoundDrawablesImpl.leftDrawable,
    261                 compoundDrawablesImpl.topDrawable,
    262                 compoundDrawablesImpl.rightDrawable,
    263                 compoundDrawablesImpl.bottomDrawable
    264         };
    265     }
    266 
    267     @Implementation
    268     public void setCompoundDrawablePadding(int compoundDrawablePadding) {
    269         this.compoundDrawablePadding = compoundDrawablePadding;
    270     }
    271 
    272     @Implementation
    273     public int getCompoundDrawablePadding() {
    274         return compoundDrawablePadding;
    275     }
    276 
    277     @Implementation
    278     public boolean onKeyDown(int keyCode, KeyEvent event) {
    279         previousKeyCodes.add(keyCode);
    280         previousKeyEvents.add(event);
    281         if (onKeyListener != null) {
    282             return onKeyListener.onKey(realView, keyCode, event);
    283         } else {
    284             return false;
    285         }
    286     }
    287 
    288     @Implementation
    289     public boolean onKeyUp(int keyCode, KeyEvent event) {
    290         previousKeyCodes.add(keyCode);
    291         previousKeyEvents.add(event);
    292         if (onKeyListener != null) {
    293             return onKeyListener.onKey(realView, keyCode, event);
    294         } else {
    295             return false;
    296         }
    297     }
    298 
    299     public int getPreviousKeyCode(int index) {
    300         return previousKeyCodes.get(index);
    301     }
    302 
    303     public KeyEvent getPreviousKeyEvent(int index) {
    304         return previousKeyEvents.get(index);
    305     }
    306 
    307     @Implementation
    308     public int getGravity() {
    309         return gravity;
    310     }
    311 
    312     @Implementation
    313     public void setGravity(int gravity) {
    314         this.gravity = gravity;
    315     }
    316 
    317 
    318     @Implementation
    319     public int getImeOptions() {
    320         return imeOptions;
    321     }
    322 
    323     @Implementation
    324     public void setImeOptions(int imeOptions) {
    325         this.imeOptions = imeOptions;
    326     }
    327 
    328     /**
    329      * Returns the text string of this {@code TextView}.
    330      * <p/>
    331      * Robolectric extension.
    332      */
    333     @Override
    334     public String innerText() {
    335         return (text == null || getVisibility() != VISIBLE) ? "" : text.toString();
    336     }
    337 
    338     @Implementation
    339     public void setError(CharSequence error) {
    340       errorText = error;
    341     }
    342 
    343     @Implementation
    344     public CharSequence getError() {
    345       return errorText;
    346     }
    347 
    348     @Override
    349     @Implementation
    350     public boolean equals(Object o) {
    351         return super.equals(shadowOf_(o));
    352     }
    353 
    354     @Override
    355     @Implementation
    356     public int hashCode() {
    357         return super.hashCode();
    358     }
    359 
    360     public CompoundDrawables getCompoundDrawablesImpl() {
    361         return compoundDrawablesImpl;
    362     }
    363 
    364     void setCompoundDrawablesImpl(CompoundDrawables compoundDrawablesImpl) {
    365         this.compoundDrawablesImpl = compoundDrawablesImpl;
    366     }
    367 
    368     public Integer getTextColorHexValue() {
    369         return textColorHexValue;
    370     }
    371 
    372     public int getTextAppearanceId() {
    373         return textAppearanceId;
    374     }
    375 
    376     public Integer getHintColorHexValue() {
    377         return hintColorHexValue;
    378     }
    379 
    380     @Implementation
    381     public float getTextSize() {
    382         return textSize;
    383     }
    384 
    385     public boolean isAutoLinkPhoneNumbers() {
    386         return autoLinkPhoneNumbers;
    387     }
    388 
    389     private void applyTextAttribute() {
    390         String text = attributeSet.getAttributeValue("android", "text");
    391         if (text != null) {
    392             if (text.startsWith("@string/")) {
    393                 int textResId = attributeSet.getAttributeResourceValue("android", "text", 0);
    394                 text = context.getResources().getString(textResId);
    395             }
    396             setText(text);
    397         }
    398     }
    399 
    400     private void applyTextColorAttribute() {
    401         String colorValue = attributeSet.getAttributeValue("android", "textColor");
    402         if (colorValue != null) {
    403             if (colorValue.startsWith("@color/") || colorValue.startsWith("@android:color/")) {
    404                 int colorResId = attributeSet.getAttributeResourceValue("android", "textColor", 0);
    405                 setTextColor(context.getResources().getColor(colorResId));
    406             } else if (colorValue.startsWith("#")) {
    407                 int colorFromHex = (int) Long.valueOf(colorValue.replaceAll("#", ""), 16).longValue();
    408                 setTextColor(colorFromHex);
    409             }
    410         }
    411     }
    412 
    413     private void applyHintAttribute() {
    414         String hint = attributeSet.getAttributeValue("android", "hint");
    415         if (hint != null) {
    416             if (hint.startsWith("@string/")) {
    417                 int textResId = attributeSet.getAttributeResourceValue("android", "hint", 0);
    418                 hint = context.getResources().getString(textResId);
    419 
    420             }
    421             setHint(hint);
    422         }
    423     }
    424 
    425     private void applyHintColorAttribute() {
    426         String colorValue = attributeSet.getAttributeValue("android", "hintColor");
    427         if (colorValue != null) {
    428             if (colorValue.startsWith("@color/") || colorValue.startsWith("@android:color/")) {
    429                 int colorResId = attributeSet.getAttributeResourceValue("android", "hintColor", 0);
    430                 setHintTextColor(context.getResources().getColor(colorResId));
    431             } else if (colorValue.startsWith("#")) {
    432                 int colorFromHex = (int) Long.valueOf(colorValue.replaceAll("#", ""), 16).longValue();
    433                 setHintTextColor(colorFromHex);
    434             }
    435         }
    436     }
    437 
    438     private void applyCompoundDrawablesWithIntrinsicBoundsAttributes() {
    439         setCompoundDrawablesWithIntrinsicBounds(
    440                 attributeSet.getAttributeResourceValue("android", "drawableLeft", 0),
    441                 attributeSet.getAttributeResourceValue("android", "drawableTop", 0),
    442                 attributeSet.getAttributeResourceValue("android", "drawableRight", 0),
    443                 attributeSet.getAttributeResourceValue("android", "drawableBottom", 0));
    444     }
    445 
    446     @Implementation
    447     public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener onEditorActionListener) {
    448         this.onEditorActionListener = onEditorActionListener;
    449     }
    450 
    451     public boolean triggerEditorAction(int imeAction) {
    452         if (onEditorActionListener != null) {
    453             return onEditorActionListener.onEditorAction((TextView) realView, imeAction, null);
    454         }
    455         return false;
    456     }
    457 
    458     @Implementation
    459     public void setTransformationMethod(TransformationMethod transformationMethod) {
    460         this.transformationMethod = transformationMethod;
    461     }
    462 
    463     @Implementation
    464     public TransformationMethod getTransformationMethod() {
    465         return transformationMethod;
    466     }
    467 
    468     @Implementation
    469     public void addTextChangedListener(TextWatcher watcher) {
    470         this.watchers.add(watcher);
    471     }
    472 
    473     @Implementation
    474     public void removeTextChangedListener(TextWatcher watcher) {
    475         this.watchers.remove(watcher);
    476     }
    477 
    478     @Implementation
    479     public TextPaint getPaint() {
    480         return new TextPaint();
    481     }
    482 
    483     @Implementation
    484     public Layout getLayout() {
    485         return this.layout;
    486     }
    487 
    488     public void setSelection(int index) {
    489         setSelection(index, index);
    490     }
    491 
    492     public void setSelection(int start, int end) {
    493         selectionStart = start;
    494         selectionEnd = end;
    495     }
    496 
    497     @Implementation
    498     public int getSelectionStart() {
    499         return selectionStart;
    500     }
    501 
    502     @Implementation
    503     public int getSelectionEnd() {
    504         return selectionEnd;
    505     }
    506 
    507     @Implementation
    508     public boolean onTouchEvent(MotionEvent event) {
    509         boolean superResult = super.onTouchEvent(event);
    510 
    511         if (movementMethod != null) {
    512             boolean handled = movementMethod.onTouchEvent(null, null, event);
    513 
    514             if (handled) {
    515                 return true;
    516             }
    517         }
    518 
    519         return superResult;
    520     }
    521 
    522     /**
    523      * @return the list of currently registered watchers/listeners
    524      */
    525     public List<TextWatcher> getWatchers() {
    526         return watchers;
    527     }
    528 
    529     public void setLayout(Layout layout) {
    530         this.layout = layout;
    531     }
    532 
    533     public static class CompoundDrawables {
    534         public Drawable leftDrawable;
    535         public Drawable topDrawable;
    536         public Drawable rightDrawable;
    537         public Drawable bottomDrawable;
    538 
    539         public CompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
    540             leftDrawable = left;
    541             topDrawable = top;
    542             rightDrawable = right;
    543             bottomDrawable = bottom;
    544         }
    545 
    546         public CompoundDrawables(int left, int top, int right, int bottom) {
    547             leftDrawable = left != 0 ? ShadowDrawable.createFromResourceId(left) : null;
    548             topDrawable = top != 0 ? ShadowDrawable.createFromResourceId(top) : null;
    549             rightDrawable = right != 0 ? ShadowDrawable.createFromResourceId(right) : null;
    550             bottomDrawable = bottom != 0 ? ShadowDrawable.createFromResourceId(bottom) : null;
    551         }
    552 
    553         @Override
    554         public boolean equals(Object o) {
    555             if (this == o) return true;
    556             if (o == null || getClass() != o.getClass()) return false;
    557 
    558             CompoundDrawables that = (CompoundDrawables) o;
    559 
    560             if (getBottom() != that.getBottom()) return false;
    561             if (getLeft() != that.getLeft()) return false;
    562             if (getRight() != that.getRight()) return false;
    563             if (getTop() != that.getTop()) return false;
    564 
    565             return true;
    566         }
    567 
    568         @Override
    569         public int hashCode() {
    570             int result = getLeft();
    571             result = 31 * result + getTop();
    572             result = 31 * result + getRight();
    573             result = 31 * result + getBottom();
    574             return result;
    575         }
    576 
    577         @Override
    578         public String toString() {
    579             return "CompoundDrawables{" +
    580                     "left=" + getLeft() +
    581                     ", top=" + getTop() +
    582                     ", right=" + getRight() +
    583                     ", bottom=" + getBottom() +
    584                     '}';
    585         }
    586 
    587         public int getLeft() {
    588             return shadowOf(leftDrawable).getLoadedFromResourceId();
    589         }
    590 
    591         public int getTop() {
    592             return shadowOf(topDrawable).getLoadedFromResourceId();
    593         }
    594 
    595         public int getRight() {
    596             return shadowOf(rightDrawable).getLoadedFromResourceId();
    597         }
    598 
    599         public int getBottom() {
    600             return shadowOf(bottomDrawable).getLoadedFromResourceId();
    601         }
    602     }
    603 }
    604