History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/NumberFormatter.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/text/NumberFormatter.java+466−0
@@ -0,0 +1,466 @@
1+/*
2+ * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+package javax.swing.text;
26+
27+import java.lang.reflect.*;
28+import java.text.*;
29+import java.util.*;
30+
31+/**
32+ * <code>NumberFormatter</code> subclasses <code>InternationalFormatter</code>
33+ * adding special behavior for numbers. Among the specializations are
34+ * (these are only used if the <code>NumberFormatter</code> does not display
35+ * invalid nubers, eg <code>setAllowsInvalid(false)</code>):
36+ * <ul>
37+ * <li>Pressing +/- (- is determined from the
38+ * <code>DecimalFormatSymbols</code> associated with the
39+ * <code>DecimalFormat</code>) in any field but the exponent
40+ * field will attempt to change the sign of the number to
41+ * positive/negative.
42+ * <li>Pressing +/- (- is determined from the
43+ * <code>DecimalFormatSymbols</code> associated with the
44+ * <code>DecimalFormat</code>) in the exponent field will
45+ * attemp to change the sign of the exponent to positive/negative.
46+ * </ul>
47+ * <p>
48+ * If you are displaying scientific numbers, you may wish to turn on
49+ * overwrite mode, <code>setOverwriteMode(true)</code>. For example:
50+ * <pre>
51+ * DecimalFormat decimalFormat = new DecimalFormat("0.000E0");
52+ * NumberFormatter textFormatter = new NumberFormatter(decimalFormat);
53+ * textFormatter.setOverwriteMode(true);
54+ * textFormatter.setAllowsInvalid(false);
55+ * </pre>
56+ * <p>
57+ * If you are going to allow the user to enter decimal
58+ * values, you should either force the DecimalFormat to contain at least
59+ * one decimal (<code>#.0###</code>), or allow the value to be invalid
60+ * <code>setAllowsInvalid(true)</code>. Otherwise users may not be able to
61+ * input decimal values.
62+ * <p>
63+ * <code>NumberFormatter</code> provides slightly different behavior to
64+ * <code>stringToValue</code> than that of its superclass. If you have
65+ * specified a Class for values, {@link #setValueClass}, that is one of
66+ * of <code>Integer</code>, <code>Long</code>, <code>Float</code>,
67+ * <code>Double</code>, <code>Byte</code> or <code>Short</code> and
68+ * the Format's <code>parseObject</code> returns an instance of
69+ * <code>Number</code>, the corresponding instance of the value class
70+ * will be created using the constructor appropriate for the primitive
71+ * type the value class represents. For example:
72+ * <code>setValueClass(Integer.class)</code> will cause the resulting
73+ * value to be created via
74+ * <code>new Integer(((Number)formatter.parseObject(string)).intValue())</code>.
75+ * This is typically useful if you
76+ * wish to set a min/max value as the various <code>Number</code>
77+ * implementations are generally not comparable to each other. This is also
78+ * useful if for some reason you need a specific <code>Number</code>
79+ * implementation for your values.
80+ * <p>
81+ * <strong>Warning:</strong>
82+ * Serialized objects of this class will not be compatible with
83+ * future Swing releases. The current serialization support is
84+ * appropriate for short term storage or RMI between applications running
85+ * the same version of Swing. As of 1.4, support for long term storage
86+ * of all JavaBeans<sup><font size="-2">TM</font></sup>
87+ * has been added to the <code>java.beans</code> package.
88+ * Please see {@link java.beans.XMLEncoder}.
89+ *
90+ * @since 1.4
91+ */
92+public class NumberFormatter extends InternationalFormatter {
93+ /** The special characters from the Format instance. */
94+ private String specialChars;
95+
96+ /**
97+ * Creates a <code>NumberFormatter</code> with the a default
98+ * <code>NumberFormat</code> instance obtained from
99+ * <code>NumberFormat.getNumberInstance()</code>.
100+ */
101+ public NumberFormatter() {
102+ this(NumberFormat.getNumberInstance());
103+ }
104+
105+ /**
106+ * Creates a NumberFormatter with the specified Format instance.
107+ *
108+ * @param format Format used to dictate legal values
109+ */
110+ public NumberFormatter(NumberFormat format) {
111+ super(format);
112+ setFormat(format);
113+ setAllowsInvalid(true);
114+ setCommitsOnValidEdit(false);
115+ setOverwriteMode(false);
116+ }
117+
118+ /**
119+ * Sets the format that dictates the legal values that can be edited
120+ * and displayed.
121+ * <p>
122+ * If you have used the nullary constructor the value of this property
123+ * will be determined for the current locale by way of the
124+ * <code>NumberFormat.getNumberInstance()</code> method.
125+ *
126+ * @param format NumberFormat instance used to dictate legal values
127+ */
128+ public void setFormat(Format format) {
129+ super.setFormat(format);
130+
131+ DecimalFormatSymbols dfs = getDecimalFormatSymbols();
132+
133+ if (dfs != null) {
134+ StringBuilder sb = new StringBuilder();
135+
136+ sb.append(dfs.getCurrencySymbol());
137+ sb.append(dfs.getDecimalSeparator());
138+ sb.append(dfs.getGroupingSeparator());
139+ sb.append(dfs.getInfinity());
140+ sb.append(dfs.getInternationalCurrencySymbol());
141+ sb.append(dfs.getMinusSign());
142+ sb.append(dfs.getMonetaryDecimalSeparator());
143+ sb.append(dfs.getNaN());
144+ sb.append(dfs.getPercent());
145+ sb.append('+');
146+ specialChars = sb.toString();
147+ }
148+ else {
149+ specialChars = "";
150+ }
151+ }
152+
153+ /**
154+ * Invokes <code>parseObject</code> on <code>f</code>, returning
155+ * its value.
156+ */
157+ Object stringToValue(String text, Format f) throws ParseException {
158+ if (f == null) {
159+ return text;
160+ }
161+ Object value = f.parseObject(text);
162+
163+ return convertValueToValueClass(value, getValueClass());
164+ }
165+
166+ /**
167+ * Converts the passed in value to the passed in class. This only
168+ * works if <code>valueClass</code> is one of <code>Integer</code>,
169+ * <code>Long</code>, <code>Float</code>, <code>Double</code>,
170+ * <code>Byte</code> or <code>Short</code> and <code>value</code>
171+ * is an instanceof <code>Number</code>.
172+ */
173+ private Object convertValueToValueClass(Object value, Class valueClass) {
174+ if (valueClass != null && (value instanceof Number)) {
175+ Number numberValue = (Number)value;
176+ if (valueClass == Integer.class) {
177+ return Integer.valueOf(numberValue.intValue());
178+ }
179+ else if (valueClass == Long.class) {
180+ return Long.valueOf(numberValue.longValue());
181+ }
182+ else if (valueClass == Float.class) {
183+ return Float.valueOf(numberValue.floatValue());
184+ }
185+ else if (valueClass == Double.class) {
186+ return Double.valueOf(numberValue.doubleValue());
187+ }
188+ else if (valueClass == Byte.class) {
189+ return Byte.valueOf(numberValue.byteValue());
190+ }
191+ else if (valueClass == Short.class) {
192+ return Short.valueOf(numberValue.shortValue());
193+ }
194+ }
195+ return value;
196+ }
197+
198+ /**
199+ * Returns the character that is used to toggle to positive values.
200+ */
201+ private char getPositiveSign() {
202+ return '+';
203+ }
204+
205+ /**
206+ * Returns the character that is used to toggle to negative values.
207+ */
208+ private char getMinusSign() {
209+ DecimalFormatSymbols dfs = getDecimalFormatSymbols();
210+
211+ if (dfs != null) {
212+ return dfs.getMinusSign();
213+ }
214+ return '-';
215+ }
216+
217+ /**
218+ * Returns the character that is used to toggle to negative values.
219+ */
220+ private char getDecimalSeparator() {
221+ DecimalFormatSymbols dfs = getDecimalFormatSymbols();
222+
223+ if (dfs != null) {
224+ return dfs.getDecimalSeparator();
225+ }
226+ return '.';
227+ }
228+
229+ /**
230+ * Returns the DecimalFormatSymbols from the Format instance.
231+ */
232+ private DecimalFormatSymbols getDecimalFormatSymbols() {
233+ Format f = getFormat();
234+
235+ if (f instanceof DecimalFormat) {
236+ return ((DecimalFormat)f).getDecimalFormatSymbols();
237+ }
238+ return null;
239+ }
240+
241+ /**
242+ * Subclassed to return false if <code>text</code> contains in an invalid
243+ * character to insert, that is, it is not a digit
244+ * (<code>Character.isDigit()</code>) and
245+ * not one of the characters defined by the DecimalFormatSymbols.
246+ */
247+ boolean isLegalInsertText(String text) {
248+ if (getAllowsInvalid()) {
249+ return true;
250+ }
251+ for (int counter = text.length() - 1; counter >= 0; counter--) {
252+ char aChar = text.charAt(counter);
253+
254+ if (!Character.isDigit(aChar) &&
255+ specialChars.indexOf(aChar) == -1){
256+ return false;
257+ }
258+ }
259+ return true;
260+ }
261+
262+ /**
263+ * Subclassed to treat the decimal separator, grouping separator,
264+ * exponent symbol, percent, permille, currency and sign as literals.
265+ */
266+ boolean isLiteral(Map attrs) {
267+ if (!super.isLiteral(attrs)) {
268+ if (attrs == null) {
269+ return false;
270+ }
271+ int size = attrs.size();
272+
273+ if (attrs.get(NumberFormat.Field.GROUPING_SEPARATOR) != null) {
274+ size--;
275+ if (attrs.get(NumberFormat.Field.INTEGER) != null) {
276+ size--;
277+ }
278+ }
279+ if (attrs.get(NumberFormat.Field.EXPONENT_SYMBOL) != null) {
280+ size--;
281+ }
282+ if (attrs.get(NumberFormat.Field.PERCENT) != null) {
283+ size--;
284+ }
285+ if (attrs.get(NumberFormat.Field.PERMILLE) != null) {
286+ size--;
287+ }
288+ if (attrs.get(NumberFormat.Field.CURRENCY) != null) {
289+ size--;
290+ }
291+ if (attrs.get(NumberFormat.Field.SIGN) != null) {
292+ size--;
293+ }
294+ return size == 0;
295+ }
296+ return true;
297+ }
298+
299+ /**
300+ * Subclassed to make the decimal separator navigatable, as well
301+ * as making the character between the integer field and the next
302+ * field navigatable.
303+ */
304+ boolean isNavigatable(int index) {
305+ if (!super.isNavigatable(index)) {
306+ // Don't skip the decimal, it causes wierd behavior
307+ return getBufferedChar(index) == getDecimalSeparator();
308+ }
309+ return true;
310+ }
311+
312+ /**
313+ * Returns the first <code>NumberFormat.Field</code> starting
314+ * <code>index</code> incrementing by <code>direction</code>.
315+ */
316+ private NumberFormat.Field getFieldFrom(int index, int direction) {
317+ if (isValidMask()) {
318+ int max = getFormattedTextField().getDocument().getLength();
319+ AttributedCharacterIterator iterator = getIterator();
320+
321+ if (index >= max) {
322+ index += direction;
323+ }
324+ while (index >= 0 && index < max) {
325+ iterator.setIndex(index);
326+
327+ Map attrs = iterator.getAttributes();
328+
329+ if (attrs != null && attrs.size() > 0) {
330+ for (Object key : attrs.keySet()) {
331+ if (key instanceof NumberFormat.Field) {
332+ return (NumberFormat.Field)key;
333+ }
334+ }
335+ }
336+ index += direction;
337+ }
338+ }
339+ return null;
340+ }
341+
342+ /**
343+ * Overriden to toggle the value if the positive/minus sign
344+ * is inserted.
345+ */
346+ void replace(DocumentFilter.FilterBypass fb, int offset, int length,
347+ String string, AttributeSet attr) throws BadLocationException {
348+ if (!getAllowsInvalid() && length == 0 && string != null &&
349+ string.length() == 1 &&
350+ toggleSignIfNecessary(fb, offset, string.charAt(0))) {
351+ return;
352+ }
353+ super.replace(fb, offset, length, string, attr);
354+ }
355+
356+ /**
357+ * Will change the sign of the integer or exponent field if
358+ * <code>aChar</code> is the positive or minus sign. Returns
359+ * true if a sign change was attempted.
360+ */
361+ private boolean toggleSignIfNecessary(DocumentFilter.FilterBypass fb,
362+ int offset, char aChar) throws
363+ BadLocationException {
364+ if (aChar == getMinusSign() || aChar == getPositiveSign()) {
365+ NumberFormat.Field field = getFieldFrom(offset, -1);
366+ Object newValue;
367+
368+ try {
369+ if (field == null ||
370+ (field != NumberFormat.Field.EXPONENT &&
371+ field != NumberFormat.Field.EXPONENT_SYMBOL &&
372+ field != NumberFormat.Field.EXPONENT_SIGN)) {
373+ newValue = toggleSign((aChar == getPositiveSign()));
374+ }
375+ else {
376+ // exponent
377+ newValue = toggleExponentSign(offset, aChar);
378+ }
379+ if (newValue != null && isValidValue(newValue, false)) {
380+ int lc = getLiteralCountTo(offset);
381+ String string = valueToString(newValue);
382+
383+ fb.remove(0, fb.getDocument().getLength());
384+ fb.insertString(0, string, null);
385+ updateValue(newValue);
386+ repositionCursor(getLiteralCountTo(offset) -
387+ lc + offset, 1);
388+ return true;
389+ }
390+ } catch (ParseException pe) {
391+ invalidEdit();
392+ }
393+ }
394+ return false;
395+ }
396+
397+ /**
398+ * Invoked to toggle the sign. For this to work the value class
399+ * must have a single arg constructor that takes a String.
400+ */
401+ private Object toggleSign(boolean positive) throws ParseException {
402+ Object value = stringToValue(getFormattedTextField().getText());
403+
404+ if (value != null) {
405+ // toString isn't localized, so that using +/- should work
406+ // correctly.
407+ String string = value.toString();
408+
409+ if (string != null && string.length() > 0) {
410+ if (positive) {
411+ if (string.charAt(0) == '-') {
412+ string = string.substring(1);
413+ }
414+ }
415+ else {
416+ if (string.charAt(0) == '+') {
417+ string = string.substring(1);
418+ }
419+ if (string.length() > 0 && string.charAt(0) != '-') {
420+ string = "-" + string;
421+ }
422+ }
423+ if (string != null) {
424+ Class<?> valueClass = getValueClass();
425+
426+ if (valueClass == null) {
427+ valueClass = value.getClass();
428+ }
429+ try {
430+ Constructor cons = valueClass.getConstructor(
431+ new Class[] { String.class });
432+
433+ if (cons != null) {
434+ return cons.newInstance(new Object[]{string});
435+ }
436+ } catch (Throwable ex) { }
437+ }
438+ }
439+ }
440+ return null;
441+ }
442+
443+ /**
444+ * Invoked to toggle the sign of the exponent (for scientific
445+ * numbers).
446+ */
447+ private Object toggleExponentSign(int offset, char aChar) throws
448+ BadLocationException, ParseException {
449+ String string = getFormattedTextField().getText();
450+ int replaceLength = 0;
451+ int loc = getAttributeStart(NumberFormat.Field.EXPONENT_SIGN);
452+
453+ if (loc >= 0) {
454+ replaceLength = 1;
455+ offset = loc;
456+ }
457+ if (aChar == getPositiveSign()) {
458+ string = getReplaceString(offset, replaceLength, null);
459+ }
460+ else {
461+ string = getReplaceString(offset, replaceLength,
462+ new String(new char[] { aChar }));
463+ }
464+ return stringToValue(string);
465+ }
466+}