DigitsKeyListener.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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
17package android.text.method;
18
19import android.text.InputType;
20import android.text.Spanned;
21import android.text.SpannableStringBuilder;
22import android.view.KeyEvent;
23
24
25/**
26 * For digits-only text entry
27 */
28public class DigitsKeyListener extends NumberKeyListener
29{
30    private char[] mAccepted;
31    private boolean mSign;
32    private boolean mDecimal;
33
34    private static final int SIGN = 1;
35    private static final int DECIMAL = 2;
36
37    @Override
38    protected char[] getAcceptedChars() {
39        return mAccepted;
40    }
41
42    /**
43     * The characters that are used.
44     *
45     * @see KeyEvent#getMatch
46     * @see #getAcceptedChars
47     */
48    private static final char[][] CHARACTERS = new char[][] {
49        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
50        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
51        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
52        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' },
53    };
54
55    /**
56     * Allocates a DigitsKeyListener that accepts the digits 0 through 9.
57     */
58    public DigitsKeyListener() {
59        this(false, false);
60    }
61
62    /**
63     * Allocates a DigitsKeyListener that accepts the digits 0 through 9,
64     * plus the minus sign (only at the beginning) and/or decimal point
65     * (only one per field) if specified.
66     */
67    public DigitsKeyListener(boolean sign, boolean decimal) {
68        mSign = sign;
69        mDecimal = decimal;
70
71        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
72        mAccepted = CHARACTERS[kind];
73    }
74
75    /**
76     * Returns a DigitsKeyListener that accepts the digits 0 through 9.
77     */
78    public static DigitsKeyListener getInstance() {
79        return getInstance(false, false);
80    }
81
82    /**
83     * Returns a DigitsKeyListener that accepts the digits 0 through 9,
84     * plus the minus sign (only at the beginning) and/or decimal point
85     * (only one per field) if specified.
86     */
87    public static DigitsKeyListener getInstance(boolean sign, boolean decimal) {
88        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
89
90        if (sInstance[kind] != null)
91            return sInstance[kind];
92
93        sInstance[kind] = new DigitsKeyListener(sign, decimal);
94        return sInstance[kind];
95    }
96
97    /**
98     * Returns a DigitsKeyListener that accepts only the characters
99     * that appear in the specified String.  Note that not all characters
100     * may be available on every keyboard.
101     */
102    public static DigitsKeyListener getInstance(String accepted) {
103        // TODO: do we need a cache of these to avoid allocating?
104
105        DigitsKeyListener dim = new DigitsKeyListener();
106
107        dim.mAccepted = new char[accepted.length()];
108        accepted.getChars(0, accepted.length(), dim.mAccepted, 0);
109
110        return dim;
111    }
112
113    public int getInputType() {
114        int contentType = InputType.TYPE_CLASS_NUMBER;
115        if (mSign) {
116            contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
117        }
118        if (mDecimal) {
119            contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
120        }
121        return contentType;
122    }
123
124    @Override
125    public CharSequence filter(CharSequence source, int start, int end,
126                               Spanned dest, int dstart, int dend) {
127        CharSequence out = super.filter(source, start, end, dest, dstart, dend);
128
129        if (mSign == false && mDecimal == false) {
130            return out;
131        }
132
133        if (out != null) {
134            source = out;
135            start = 0;
136            end = out.length();
137        }
138
139        int sign = -1;
140        int decimal = -1;
141        int dlen = dest.length();
142
143        /*
144         * Find out if the existing text has '-' or '.' characters.
145         */
146
147        for (int i = 0; i < dstart; i++) {
148            char c = dest.charAt(i);
149
150            if (c == '-') {
151                sign = i;
152            } else if (c == '.') {
153                decimal = i;
154            }
155        }
156        for (int i = dend; i < dlen; i++) {
157            char c = dest.charAt(i);
158
159            if (c == '-') {
160                return "";    // Nothing can be inserted in front of a '-'.
161            } else if (c == '.') {
162                decimal = i;
163            }
164        }
165
166        /*
167         * If it does, we must strip them out from the source.
168         * In addition, '-' must be the very first character,
169         * and nothing can be inserted before an existing '-'.
170         * Go in reverse order so the offsets are stable.
171         */
172
173        SpannableStringBuilder stripped = null;
174
175        for (int i = end - 1; i >= start; i--) {
176            char c = source.charAt(i);
177            boolean strip = false;
178
179            if (c == '-') {
180                if (i != start || dstart != 0) {
181                    strip = true;
182                } else if (sign >= 0) {
183                    strip = true;
184                } else {
185                    sign = i;
186                }
187            } else if (c == '.') {
188                if (decimal >= 0) {
189                    strip = true;
190                } else {
191                    decimal = i;
192                }
193            }
194
195            if (strip) {
196                if (end == start + 1) {
197                    return "";  // Only one character, and it was stripped.
198                }
199
200                if (stripped == null) {
201                    stripped = new SpannableStringBuilder(source, start, end);
202                }
203
204                stripped.delete(i - start, i + 1 - start);
205            }
206        }
207
208        if (stripped != null) {
209            return stripped;
210        } else if (out != null) {
211            return out;
212        } else {
213            return null;
214        }
215    }
216
217    private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4];
218}
219