Home | History | Annotate | Download | only in text
      1 /*
      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 
     17 package android.text;
     18 
     19 import android.annotation.NonNull;
     20 
     21 import com.android.internal.util.Preconditions;
     22 
     23 import java.util.Locale;
     24 
     25 /**
     26  * InputFilters can be attached to {@link Editable}s to constrain the
     27  * changes that can be made to them.
     28  */
     29 public interface InputFilter
     30 {
     31     /**
     32      * This method is called when the buffer is going to replace the
     33      * range <code>dstart &hellip; dend</code> of <code>dest</code>
     34      * with the new text from the range <code>start &hellip; end</code>
     35      * of <code>source</code>.  Return the CharSequence that you would
     36      * like to have placed there instead, including an empty string
     37      * if appropriate, or <code>null</code> to accept the original
     38      * replacement.  Be careful to not to reject 0-length replacements,
     39      * as this is what happens when you delete text.  Also beware that
     40      * you should not attempt to make any changes to <code>dest</code>
     41      * from this method; you may only examine it for context.
     42      *
     43      * Note: If <var>source</var> is an instance of {@link Spanned} or
     44      * {@link Spannable}, the span objects in the <var>source</var> should be
     45      * copied into the filtered result (i.e. the non-null return value).
     46      * {@link TextUtils#copySpansFrom} can be used for convenience if the
     47      * span boundary indices would be remaining identical relative to the source.
     48      */
     49     public CharSequence filter(CharSequence source, int start, int end,
     50                                Spanned dest, int dstart, int dend);
     51 
     52     /**
     53      * This filter will capitalize all the lowercase and titlecase letters that are added
     54      * through edits. (Note that if there are no lowercase or titlecase letters in the input, the
     55      * text would not be transformed, even if the result of capitalization of the string is
     56      * different from the string.)
     57      */
     58     public static class AllCaps implements InputFilter {
     59         private final Locale mLocale;
     60 
     61         public AllCaps() {
     62             mLocale = null;
     63         }
     64 
     65         /**
     66          * Constructs a locale-specific AllCaps filter, to make sure capitalization rules of that
     67          * locale are used for transforming the sequence.
     68          */
     69         public AllCaps(@NonNull Locale locale) {
     70             Preconditions.checkNotNull(locale);
     71             mLocale = locale;
     72         }
     73 
     74         public CharSequence filter(CharSequence source, int start, int end,
     75                                    Spanned dest, int dstart, int dend) {
     76             final CharSequence wrapper = new CharSequenceWrapper(source, start, end);
     77 
     78             boolean lowerOrTitleFound = false;
     79             final int length = end - start;
     80             for (int i = 0, cp; i < length; i += Character.charCount(cp)) {
     81                 // We access 'wrapper' instead of 'source' to make sure no code unit beyond 'end' is
     82                 // ever accessed.
     83                 cp = Character.codePointAt(wrapper, i);
     84                 if (Character.isLowerCase(cp) || Character.isTitleCase(cp)) {
     85                     lowerOrTitleFound = true;
     86                     break;
     87                 }
     88             }
     89             if (!lowerOrTitleFound) {
     90                 return null; // keep original
     91             }
     92 
     93             final boolean copySpans = source instanceof Spanned;
     94             final CharSequence upper = TextUtils.toUpperCase(mLocale, wrapper, copySpans);
     95             if (upper == wrapper) {
     96                 // Nothing was changed in the uppercasing operation. This is weird, since
     97                 // we had found at least one lowercase or titlecase character. But we can't
     98                 // do anything better than keeping the original in this case.
     99                 return null; // keep original
    100             }
    101             // Return a SpannableString or String for backward compatibility.
    102             return copySpans ? new SpannableString(upper) : upper.toString();
    103         }
    104 
    105         private static class CharSequenceWrapper implements CharSequence, Spanned {
    106             private final CharSequence mSource;
    107             private final int mStart, mEnd;
    108             private final int mLength;
    109 
    110             CharSequenceWrapper(CharSequence source, int start, int end) {
    111                 mSource = source;
    112                 mStart = start;
    113                 mEnd = end;
    114                 mLength = end - start;
    115             }
    116 
    117             public int length() {
    118                 return mLength;
    119             }
    120 
    121             public char charAt(int index) {
    122                 if (index < 0 || index >= mLength) {
    123                     throw new IndexOutOfBoundsException();
    124                 }
    125                 return mSource.charAt(mStart + index);
    126             }
    127 
    128             public CharSequence subSequence(int start, int end) {
    129                 if (start < 0 || end < 0 || end > mLength || start > end) {
    130                     throw new IndexOutOfBoundsException();
    131                 }
    132                 return new CharSequenceWrapper(mSource, mStart + start, mStart + end);
    133             }
    134 
    135             public String toString() {
    136                 return mSource.subSequence(mStart, mEnd).toString();
    137             }
    138 
    139             public <T> T[] getSpans(int start, int end, Class<T> type) {
    140                 return ((Spanned) mSource).getSpans(mStart + start, mStart + end, type);
    141             }
    142 
    143             public int getSpanStart(Object tag) {
    144                 return ((Spanned) mSource).getSpanStart(tag) - mStart;
    145             }
    146 
    147             public int getSpanEnd(Object tag) {
    148                 return ((Spanned) mSource).getSpanEnd(tag) - mStart;
    149             }
    150 
    151             public int getSpanFlags(Object tag) {
    152                 return ((Spanned) mSource).getSpanFlags(tag);
    153             }
    154 
    155             public int nextSpanTransition(int start, int limit, Class type) {
    156                 return ((Spanned) mSource).nextSpanTransition(mStart + start, mStart + limit, type)
    157                         - mStart;
    158             }
    159         }
    160     }
    161 
    162     /**
    163      * This filter will constrain edits not to make the length of the text
    164      * greater than the specified length.
    165      */
    166     public static class LengthFilter implements InputFilter {
    167         private final int mMax;
    168 
    169         public LengthFilter(int max) {
    170             mMax = max;
    171         }
    172 
    173         public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
    174                 int dstart, int dend) {
    175             int keep = mMax - (dest.length() - (dend - dstart));
    176             if (keep <= 0) {
    177                 return "";
    178             } else if (keep >= end - start) {
    179                 return null; // keep original
    180             } else {
    181                 keep += start;
    182                 if (Character.isHighSurrogate(source.charAt(keep - 1))) {
    183                     --keep;
    184                     if (keep == start) {
    185                         return "";
    186                     }
    187                 }
    188                 return source.subSequence(start, keep);
    189             }
    190         }
    191 
    192         /**
    193          * @return the maximum length enforced by this input filter
    194          */
    195         public int getMax() {
    196             return mMax;
    197         }
    198     }
    199 }
    200