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 */
25package javax.swing.text;
26
27import java.lang.reflect.*;
28import java.text.*;
29import 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 */
92public 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}