001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package javax.swing;
018:
019: import java.awt.Toolkit;
020: import java.awt.event.ActionEvent;
021: import java.awt.event.FocusEvent;
022: import java.io.Serializable;
023: import java.text.DateFormat;
024: import java.text.Format;
025: import java.text.NumberFormat;
026: import java.text.ParseException;
027: import java.util.Date;
028: import javax.swing.event.DocumentEvent;
029: import javax.swing.event.DocumentListener;
030: import javax.swing.plaf.TextUI;
031: import javax.swing.plaf.UIResource;
032: import javax.swing.text.AbstractDocument;
033: import javax.swing.text.DateFormatter;
034: import javax.swing.text.DefaultFormatter;
035: import javax.swing.text.DefaultFormatterFactory;
036: import javax.swing.text.Document;
037: import javax.swing.text.DocumentFilter;
038: import javax.swing.text.InternationalFormatter;
039: import javax.swing.text.JTextComponent;
040: import javax.swing.text.NavigationFilter;
041: import javax.swing.text.NumberFormatter;
042: import javax.swing.text.TextAction;
043:
044: import org.apache.harmony.x.swing.internal.nls.Messages;
045:
046: /**
047: * <p>
048: * <i>JFormattedTextField</i>
049: * </p>
050: * <h3>Implementation Notes:</h3>
051: * <ul>
052: * <li>The <code>serialVersionUID</code> fields are explicitly declared as a performance
053: * optimization, not as a guarantee of serialization compatibility.</li>
054: * </ul>
055: */
056: public class JFormattedTextField extends JTextField {
057: private static final long serialVersionUID = 7685569367944517634L;
058:
059: public abstract static class AbstractFormatter implements
060: Serializable {
061: private JFormattedTextField textField;
062:
063: private class ActionMapFormattersResource extends ActionMap {
064: private static final long serialVersionUID = 1L;
065: }
066:
067: @Override
068: protected Object clone() throws CloneNotSupportedException {
069: AbstractFormatter formatter = (AbstractFormatter) super
070: .clone();
071: formatter.install(null);
072: return formatter;
073: }
074:
075: protected Action[] getActions() {
076: return null;
077: }
078:
079: protected DocumentFilter getDocumentFilter() {
080: return null;
081: }
082:
083: protected JFormattedTextField getFormattedTextField() {
084: return textField;
085: }
086:
087: protected NavigationFilter getNavigationFilter() {
088: return null;
089: }
090:
091: public void install(final JFormattedTextField ftf) {
092: if (textField != null) {
093: removeFormattersActionMap();
094: setNavigationFilter(null);
095: setDocumentFilter(null);
096: }
097: textField = ftf;
098: if (ftf == null) {
099: return;
100: }
101: setActionMap(getActions());
102: setNavigationFilter(getNavigationFilter());
103: setDocumentFilter(getDocumentFilter());
104: updateInternalValue();
105: updateText();
106: }
107:
108: private void updateText() {
109: String result = "";
110: try {
111: result = valueToString(textField.getValue());
112: } catch (ParseException e) {
113: setEditValid(false);
114: }
115: textField.setText(result);
116: }
117:
118: protected void invalidEdit() {
119: textField.invalidEdit();
120: }
121:
122: protected void setEditValid(final boolean isEditValid) {
123: textField.setEditValid(isEditValid);
124: }
125:
126: public abstract Object stringToValue(final String text)
127: throws ParseException;
128:
129: public void uninstall() {
130: removeFormattersActionMap();
131: setNavigationFilter(null);
132: setDocumentFilter(null);
133: }
134:
135: public abstract String valueToString(final Object value)
136: throws ParseException;
137:
138: private ActionMap createActionMap(final Action[] actions) {
139: ActionMap result = new ActionMapFormattersResource();
140: for (int i = 0; i < actions.length; i++) {
141: Action action = actions[i];
142: result.put(action.getValue(Action.NAME), action);
143: }
144: return result;
145: }
146:
147: private void setActionMap(final Action[] actions) {
148: if (actions == null) {
149: return;
150: }
151: ActionMap formattersActionMap = createActionMap(actions);
152: ActionMap actionMap = textField.getActionMap();
153: ActionMap parent = actionMap.getParent();
154: while (!(parent instanceof UIResource)) {
155: actionMap = parent;
156: parent = actionMap.getParent();
157: }
158: actionMap.setParent(formattersActionMap);
159: formattersActionMap.setParent(parent);
160: }
161:
162: private void removeFormattersActionMap() {
163: ActionMap actionMap = textField.getActionMap();
164: ActionMap parent = actionMap.getParent();
165: while (!(parent instanceof ActionMapFormattersResource)) {
166: actionMap = parent;
167: parent = actionMap.getParent();
168: if (parent instanceof UIResource) {
169: return;
170: }
171: }
172: actionMap.setParent(parent.getParent());
173: parent.setParent(null);
174: }
175:
176: private void setDocumentFilter(final DocumentFilter filter) {
177: Document doc = textField.getDocument();
178: if (doc instanceof AbstractDocument) {
179: ((AbstractDocument) doc).setDocumentFilter(filter);
180: }
181: }
182:
183: private void setNavigationFilter(final NavigationFilter filter) {
184: textField.setNavigationFilter(filter);
185: }
186:
187: private void updateInternalValue() {
188: String text = textField.getText();
189: boolean result = false;
190: try {
191: stringToValue(text);
192: result = true;
193: } catch (ParseException e) {
194: }
195: if (!result) {
196: invalidEdit();
197: }
198: setEditValid(result);
199: }
200: }
201:
202: public abstract static class AbstractFormatterFactory {
203: public abstract AbstractFormatter getFormatter(
204: final JFormattedTextField tf);
205: }
206:
207: static class CommitAction extends NotifyAction {
208: private static final long serialVersionUID = 1L;
209:
210: public CommitAction(final String name) {
211: super (name);
212: }
213:
214: @Override
215: public void actionPerformed(final ActionEvent e) {
216: JTextComponent source = getTextComponent(e);
217: if (source instanceof JFormattedTextField) {
218: JFormattedTextField tf = (JFormattedTextField) source;
219: if (tf.commitText()) {
220: tf.revertValue();
221: }
222: }
223: super .actionPerformed(e);
224: }
225:
226: @Override
227: public boolean isEnabled() {
228: final JTextComponent focused = getFocusedComponent();
229: return focused instanceof JFormattedTextField;
230: }
231: }
232:
233: private static class CancelAction extends TextAction {
234: private static final long serialVersionUID = 1L;
235:
236: public CancelAction(final String name) {
237: super (name);
238: }
239:
240: public void actionPerformed(final ActionEvent e) {
241: JTextComponent source = getTextComponent(e);
242: if (source instanceof JFormattedTextField) {
243: JFormattedTextField tf = (JFormattedTextField) source;
244: tf.revertValue();
245: }
246: }
247: }
248:
249: public static final int COMMIT = 0;
250:
251: public static final int COMMIT_OR_REVERT = 1;
252:
253: public static final int PERSIST = 3;
254:
255: public static final int REVERT = 2;
256:
257: private static final String UI_CLASS_ID = "FormattedTextFieldUI";
258:
259: private static final String FORMATTER_FACTORY_PROPERTY_NAME = "formatterFactory";
260:
261: private static final String TEXT_FORMATTER_PROPERTY_NAME = "textFormatter";
262:
263: private static final String VALUE_PROPERTY_NAME = "value";
264:
265: private static final String EDIT_VALID_PROPERTY_NAME = "editValid";
266:
267: private static final String COMMIT_ACTION_NAME = "notify-field-accept";
268:
269: private static final String CANCEL_ACTION_NAME = "reset-field-edit";
270:
271: private static final TextAction COMMIT_ACTION = new CommitAction(
272: COMMIT_ACTION_NAME);
273:
274: private static final TextAction CANCEL_ACTION = new CancelAction(
275: CANCEL_ACTION_NAME);
276:
277: private static Action[] actions;
278:
279: private AbstractFormatterFactory factory;
280:
281: private Object value;
282:
283: private int focusLostBehaviour = COMMIT_OR_REVERT;
284:
285: private AbstractFormatter formatter;
286:
287: private boolean isEditValid;
288:
289: private DocumentListenerImpl docListener = new DocumentListenerImpl();
290:
291: private String lastSuccessfullyCommittedText;
292:
293: private class DocumentListenerImpl implements DocumentListener {
294: public void changedUpdate(final DocumentEvent e) {
295: if (!SwingUtilities.isEventDispatchThread()) {
296: SwingUtilities.invokeLater(new Runnable() {
297: public void run() {
298: changedUpdate(e);
299: }
300: });
301: return;
302: }
303: updateFormatterInternalValue();
304: }
305:
306: public void insertUpdate(final DocumentEvent e) {
307: if (!SwingUtilities.isEventDispatchThread()) {
308: SwingUtilities.invokeLater(new Runnable() {
309: public void run() {
310: insertUpdate(e);
311: }
312: });
313: return;
314: }
315: updateFormatterInternalValue();
316: }
317:
318: public void removeUpdate(final DocumentEvent e) {
319: if (!SwingUtilities.isEventDispatchThread()) {
320: SwingUtilities.invokeLater(new Runnable() {
321: public void run() {
322: removeUpdate(e);
323: }
324: });
325: return;
326: }
327: updateFormatterInternalValue();
328: }
329: }
330:
331: private void updateFormatterInternalValue() {
332: if (formatter != null) {
333: formatter.updateInternalValue();
334: }
335: }
336:
337: public JFormattedTextField() {
338: }
339:
340: public JFormattedTextField(final AbstractFormatterFactory factory) {
341: this .factory = factory;
342: }
343:
344: public JFormattedTextField(final Format format) {
345: setFormatter(createFormatter(format));
346: setFormatterFactory(createFactory(formatter));
347: }
348:
349: public JFormattedTextField(final AbstractFormatter formatter) {
350: setFormatter(formatter);
351: setFormatterFactory(createFactory(formatter));
352: }
353:
354: public JFormattedTextField(final AbstractFormatterFactory factory,
355: final Object value) {
356: setFormatterFactory(factory);
357: setValue(value);
358: }
359:
360: public JFormattedTextField(final Object value) {
361: setValue(value);
362: }
363:
364: public void commitEdit() throws ParseException {
365: String text = getText();
366: if (formatter != null) {
367: setValue(formatter.stringToValue(text));
368: }
369: lastSuccessfullyCommittedText = text;
370: }
371:
372: @Override
373: public Action[] getActions() {
374: if (actions == null) {
375: Action[] editorKitActions = ((TextUI) ui)
376: .getEditorKit(this ).getActions();
377: int length = editorKitActions.length;
378: actions = new Action[length + 2];
379: System.arraycopy(editorKitActions, 0, actions, 0, length);
380: actions[length] = COMMIT_ACTION;
381: actions[length + 1] = CANCEL_ACTION;
382: }
383: return actions.clone();
384: }
385:
386: public int getFocusLostBehavior() {
387: return focusLostBehaviour;
388: }
389:
390: public AbstractFormatter getFormatter() {
391: return formatter;
392: }
393:
394: public AbstractFormatterFactory getFormatterFactory() {
395: return factory;
396: }
397:
398: @Override
399: public String getUIClassID() {
400: return UI_CLASS_ID;
401: }
402:
403: public Object getValue() {
404: return value;
405: }
406:
407: protected void invalidEdit() {
408: Toolkit.getDefaultToolkit().beep();
409: }
410:
411: public boolean isEditValid() {
412: return isEditValid;
413: }
414:
415: @Override
416: protected void processFocusEvent(final FocusEvent event) {
417: if (event.getID() == FocusEvent.FOCUS_LOST
418: && !event.isTemporary()) {
419: switch (getFocusLostBehavior()) {
420: case REVERT:
421: revertValue();
422: break;
423: case COMMIT:
424: if (commitText()) {
425: revertValue();
426: }
427: break;
428: case COMMIT_OR_REVERT:
429: if (!commitText()) {
430: revertValue();
431: } else {
432: revertValue();
433: }
434: break;
435: default:
436: break;
437: }
438: }
439: if (!changeQueryFactoryPolicy()) {
440: updateFormatter(false);
441: }
442: super .processFocusEvent(event);
443: }
444:
445: private boolean changeQueryFactoryPolicy() {
446: int focusLostBehaviour = getFocusLostBehavior();
447: return (focusLostBehaviour == PERSIST || focusLostBehaviour == COMMIT_OR_REVERT)
448: && lastSuccessfullyCommittedText != getText();
449: }
450:
451: @Override
452: public void setDocument(final Document doc) {
453: Document oldValue = getDocument();
454: if (oldValue != null) {
455: oldValue.removeDocumentListener(docListener);
456: }
457: if (doc != null) {
458: initDocumentListener();
459: doc.addDocumentListener(docListener);
460: }
461: super .setDocument(doc);
462: if (formatter != null) {
463: formatter.install(this );
464: }
465: }
466:
467: public void setFocusLostBehavior(final int behavior) {
468: if (behavior != COMMIT && behavior != COMMIT_OR_REVERT
469: && behavior != PERSIST && behavior != REVERT) {
470: throw new IllegalArgumentException(
471: "setFocusLostBehavior" + Messages.getString("swing.13") //$NON-NLS-1$ //$NON-NLS-2$
472: + "JFormattedTextField.COMMIT, " + "JFormattedTextField.COMMIT_OR_" //$NON-NLS-1$ //$NON-NLS-2$
473: + "REVERT, " + "JFormattedTextField.PERSIST " //$NON-NLS-1$ //$NON-NLS-2$
474: + "or JFormattedTextField.REVERT"); //$NON-NLS-1$
475: }
476: focusLostBehaviour = behavior;
477: }
478:
479: /**
480: * A PropertyChange event ("textFormatter") is propagated to each listener
481: */
482: protected void setFormatter(final AbstractFormatter formatter) {
483: Object oldValue = this .formatter;
484: if (this .formatter != null) {
485: this .formatter.uninstall();
486: }
487: if (formatter != null) {
488: formatter.install(this );
489: }
490: this .formatter = formatter;
491: firePropertyChange(TEXT_FORMATTER_PROPERTY_NAME, oldValue,
492: formatter);
493: }
494:
495: /**
496: * A PropertyChange event ("formattedFactory") is propagated to each
497: * listener.
498: */
499: public void setFormatterFactory(
500: final AbstractFormatterFactory factory) {
501: Object oldValue = this .factory;
502: this .factory = factory;
503: updateFormatter(true);
504: firePropertyChange(FORMATTER_FACTORY_PROPERTY_NAME, oldValue,
505: factory);
506: }
507:
508: /**
509: * A PropertyChange event ("value") is propagated to each listener.
510: */
511: public void setValue(final Object value) {
512: Object oldValue = this .value;
513: this .value = value;
514: updateFactory();
515: updateFormatter(true);
516: revertValue();
517: firePropertyChange(VALUE_PROPERTY_NAME, oldValue, value);
518: }
519:
520: private AbstractFormatterFactory createFactory(final Object value) {
521: DefaultFormatterFactory factory = new DefaultFormatterFactory();
522: if (value instanceof Number) {
523: factory.setDefaultFormatter(new NumberFormatter());
524: } else if (value instanceof Date) {
525: factory.setDefaultFormatter(new DateFormatter());
526: } else {
527: factory.setDefaultFormatter(new DefaultFormatter());
528: }
529: return factory;
530: }
531:
532: private AbstractFormatterFactory createFactory(
533: final AbstractFormatter formatter) {
534: DefaultFormatterFactory factory = new DefaultFormatterFactory();
535: factory.setDefaultFormatter(formatter);
536: return factory;
537: }
538:
539: private AbstractFormatter createFormatter(final Format format) {
540: if (format instanceof DateFormat) {
541: return new DateFormatter((DateFormat) format);
542: } else if (format instanceof NumberFormat) {
543: return new NumberFormatter((NumberFormat) format);
544: } else {
545: return new InternationalFormatter(format);
546: }
547: }
548:
549: private boolean revertValue() {
550: if (formatter == null) {
551: return false;
552: }
553: String newText = null;
554: try {
555: newText = formatter.valueToString(value);
556: } catch (ParseException e) {
557: }
558: if (newText == null) {
559: return false;
560: }
561: if (!newText.equals(getText())) {
562: setText(newText);
563: }
564: return true;
565: }
566:
567: private boolean commitText() {
568: boolean result = false;
569: try {
570: commitEdit();
571: result = true;
572: } catch (ParseException e) {
573: }
574: return result;
575: }
576:
577: private void setEditValid(final boolean isEditValid) {
578: boolean oldValue = this .isEditValid;
579: this .isEditValid = isEditValid;
580: firePropertyChange(EDIT_VALID_PROPERTY_NAME, oldValue,
581: isEditValid);
582: }
583:
584: private void updateFactory() {
585: if (factory == null) {
586: factory = createFactory(getValue());
587: }
588: }
589:
590: private void updateFormatter(final boolean conditionalUpdate) {
591: if (factory == null) {
592: return;
593: }
594: AbstractFormatter formatter = factory.getFormatter(this );
595: if (!conditionalUpdate || this .formatter != formatter) {
596: setFormatter(factory.getFormatter(this ));
597: }
598: }
599:
600: private void initDocumentListener() {
601: if (docListener == null) {
602: docListener = new DocumentListenerImpl();
603: }
604: }
605: }
|