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:
018: package javax.swing;
019:
020: import java.awt.Component;
021: import java.awt.Container;
022: import java.awt.Dimension;
023: import java.awt.LayoutManager;
024: import java.awt.Point;
025: import java.awt.Rectangle;
026: import java.awt.event.ActionEvent;
027: import java.beans.PropertyChangeEvent;
028: import java.beans.PropertyChangeListener;
029: import java.io.Serializable;
030: import java.text.DecimalFormat;
031: import java.text.ParseException;
032: import java.text.SimpleDateFormat;
033: import java.util.List;
034: import javax.accessibility.Accessible;
035: import javax.accessibility.AccessibleAction;
036: import javax.accessibility.AccessibleContext;
037: import javax.accessibility.AccessibleEditableText;
038: import javax.accessibility.AccessibleRole;
039: import javax.accessibility.AccessibleText;
040: import javax.accessibility.AccessibleValue;
041: import javax.swing.JFormattedTextField.AbstractFormatter;
042: import javax.swing.event.ChangeEvent;
043: import javax.swing.event.ChangeListener;
044: import javax.swing.plaf.SpinnerUI;
045: import javax.swing.text.AttributeSet;
046: import javax.swing.text.BadLocationException;
047: import javax.swing.text.DateFormatter;
048: import javax.swing.text.DefaultFormatterFactory;
049: import javax.swing.text.DocumentFilter;
050: import javax.swing.text.NumberFormatter;
051: import org.apache.harmony.luni.util.NotImplementedException;
052: import org.apache.harmony.x.swing.StringConstants;
053: import org.apache.harmony.x.swing.Utilities;
054:
055: import org.apache.harmony.x.swing.internal.nls.Messages;
056:
057: /**
058: * <p>
059: * <i>JSpinner</i>
060: * </p>
061: * <h3>Implementation Notes:</h3>
062: * <ul>
063: * <li>The <code>serialVersionUID</code> fields are explicitly declared as a performance
064: * optimization, not as a guarantee of serialization compatibility.</li>
065: * </ul>
066: */
067: public class JSpinner extends JComponent implements Accessible {
068: private static final long serialVersionUID = 5455034942343575490L;
069:
070: protected class AccessibleJSpinner extends
071: JComponent.AccessibleJComponent implements AccessibleValue,
072: AccessibleAction, AccessibleText, AccessibleEditableText,
073: ChangeListener {
074: private static final long serialVersionUID = -8871493856204319541L;
075:
076: protected AccessibleJSpinner() {
077: }
078:
079: public void stateChanged(ChangeEvent e)
080: throws NotImplementedException {
081: throw new NotImplementedException();
082: }
083:
084: @Override
085: public AccessibleRole getAccessibleRole()
086: throws NotImplementedException {
087: throw new NotImplementedException();
088: }
089:
090: @Override
091: public int getAccessibleChildrenCount()
092: throws NotImplementedException {
093: throw new NotImplementedException();
094: }
095:
096: @Override
097: public Accessible getAccessibleChild(int i)
098: throws NotImplementedException {
099: throw new NotImplementedException();
100: }
101:
102: @Override
103: public AccessibleAction getAccessibleAction()
104: throws NotImplementedException {
105: throw new NotImplementedException();
106: }
107:
108: @Override
109: public AccessibleText getAccessibleText()
110: throws NotImplementedException {
111: throw new NotImplementedException();
112: }
113:
114: @Override
115: public AccessibleValue getAccessibleValue()
116: throws NotImplementedException {
117: throw new NotImplementedException();
118: }
119:
120: public Number getCurrentAccessibleValue()
121: throws NotImplementedException {
122: throw new NotImplementedException();
123: }
124:
125: public boolean setCurrentAccessibleValue(Number n)
126: throws NotImplementedException {
127: throw new NotImplementedException();
128: }
129:
130: public Number getMinimumAccessibleValue()
131: throws NotImplementedException {
132: throw new NotImplementedException();
133: }
134:
135: public Number getMaximumAccessibleValue()
136: throws NotImplementedException {
137: throw new NotImplementedException();
138: }
139:
140: public int getAccessibleActionCount()
141: throws NotImplementedException {
142: throw new NotImplementedException();
143: }
144:
145: public String getAccessibleActionDescription(int i)
146: throws NotImplementedException {
147: throw new NotImplementedException();
148: }
149:
150: public boolean doAccessibleAction(int i)
151: throws NotImplementedException {
152: throw new NotImplementedException();
153: }
154:
155: public int getIndexAtPoint(Point p)
156: throws NotImplementedException {
157: throw new NotImplementedException();
158: }
159:
160: public Rectangle getCharacterBounds(int i)
161: throws NotImplementedException {
162: throw new NotImplementedException();
163: }
164:
165: public int getCharCount() throws NotImplementedException {
166: throw new NotImplementedException();
167: }
168:
169: public int getCaretPosition() throws NotImplementedException {
170: throw new NotImplementedException();
171: }
172:
173: public String getAtIndex(int part, int index)
174: throws NotImplementedException {
175: throw new NotImplementedException();
176: }
177:
178: public String getAfterIndex(int part, int index)
179: throws NotImplementedException {
180: throw new NotImplementedException();
181: }
182:
183: public String getBeforeIndex(int part, int index)
184: throws NotImplementedException {
185: throw new NotImplementedException();
186: }
187:
188: public AttributeSet getCharacterAttribute(int i)
189: throws NotImplementedException {
190: throw new NotImplementedException();
191: }
192:
193: public int getSelectionStart() throws NotImplementedException {
194: throw new NotImplementedException();
195: }
196:
197: public int getSelectionEnd() throws NotImplementedException {
198: throw new NotImplementedException();
199: }
200:
201: public String getSelectedText() throws NotImplementedException {
202: throw new NotImplementedException();
203: }
204:
205: public void setTextContents(String s)
206: throws NotImplementedException {
207: throw new NotImplementedException();
208: }
209:
210: public void insertTextAtIndex(int index, String s)
211: throws NotImplementedException {
212: throw new NotImplementedException();
213: }
214:
215: public String getTextRange(int startIndex, int endIndex)
216: throws NotImplementedException {
217: throw new NotImplementedException();
218: }
219:
220: public void delete(int startIndex, int endIndex)
221: throws NotImplementedException {
222: throw new NotImplementedException();
223: }
224:
225: public void cut(int startIndex, int endIndex)
226: throws NotImplementedException {
227: throw new NotImplementedException();
228: }
229:
230: public void paste(int startIndex)
231: throws NotImplementedException {
232: throw new NotImplementedException();
233: }
234:
235: public void replaceText(int startIndex, int endIndex, String s)
236: throws NotImplementedException {
237: throw new NotImplementedException();
238: }
239:
240: public void selectText(int startIndex, int endIndex)
241: throws NotImplementedException {
242: throw new NotImplementedException();
243: }
244:
245: public void setAttributes(int startIndex, int endIndex,
246: AttributeSet as) throws NotImplementedException {
247: throw new NotImplementedException();
248: }
249: }
250:
251: public static class DefaultEditor extends JPanel implements
252: ChangeListener, PropertyChangeListener, LayoutManager {
253: private static final long serialVersionUID = 6977154593437159148L;
254:
255: private JFormattedTextField text;
256:
257: private JSpinner spinner;
258:
259: public DefaultEditor(JSpinner spinner) {
260: this .spinner = spinner;
261: setLayout(this );
262: spinner.addChangeListener(this );
263: text = new JFormattedTextField();
264: text.setEditable(false);
265: text.setValue(spinner.getModel().getValue());
266: text.addPropertyChangeListener(this );
267: text.getActionMap().put("increment", disabledAction);
268: text.getActionMap().put("decrement", disabledAction);
269: add(text);
270: }
271:
272: public JSpinner getSpinner() {
273: return spinner;
274: }
275:
276: public JFormattedTextField getTextField() {
277: return text;
278: }
279:
280: public void stateChanged(ChangeEvent e) {
281: if (this != spinner.getEditor()) {
282: return;
283: }
284: text.setValue(spinner.getValue());
285: }
286:
287: public void propertyChange(PropertyChangeEvent e) {
288: if (e.getSource() != text || this != spinner.getEditor()) {
289: return;
290: }
291: if (StringConstants.VALUE_PROPERTY_NAME.equals(e
292: .getPropertyName())) {
293: try {
294: spinner.setValue(e.getNewValue());
295: } catch (IllegalArgumentException ex) {
296: text.setValue(e.getOldValue());
297: }
298: }
299: }
300:
301: public void addLayoutComponent(String name, Component child) {
302: }
303:
304: public void removeLayoutComponent(Component child) {
305: }
306:
307: public Dimension preferredLayoutSize(Container parent) {
308: return Utilities.addInsets(text.getPreferredSize(), parent
309: .getInsets());
310: }
311:
312: public Dimension minimumLayoutSize(Container parent) {
313: return Utilities.addInsets(text.getMinimumSize(), parent
314: .getInsets());
315: }
316:
317: public void layoutContainer(Container parent) {
318: text.setBounds(0, 0, parent.getWidth(), parent.getHeight());
319: }
320:
321: public void commitEdit() throws ParseException {
322: text.commitEdit();
323: }
324:
325: public void dismiss(JSpinner spinner) {
326: spinner.removeChangeListener(this );
327: }
328: }
329:
330: @SuppressWarnings("unchecked")
331: private static class SpinnerDateFormatter extends DateFormatter {
332: private static final long serialVersionUID = 1L;
333:
334: private SpinnerDateModel model;
335:
336: public SpinnerDateFormatter(final SimpleDateFormat format,
337: final SpinnerDateModel model) {
338: super (format);
339: this .model = model;
340: }
341:
342: @Override
343: public void setMaximum(Comparable max) {
344: super .setMaximum(max);
345: model.setEnd(max);
346: }
347:
348: @Override
349: public void setMinimum(Comparable min) {
350: super .setMinimum(min);
351: model.setStart(min);
352: }
353:
354: @Override
355: public Comparable getMaximum() {
356: Comparable max = model.getEnd();
357: super .setMaximum(max);
358: return max;
359: }
360:
361: @Override
362: public Comparable getMinimum() {
363: Comparable min = model.getStart();
364: super .setMinimum(min);
365: return min;
366: }
367: }
368:
369: public static class DateEditor extends DefaultEditor {
370: private static final long serialVersionUID = 1L;
371:
372: private SimpleDateFormat format;
373:
374: public DateEditor(JSpinner spinner) {
375: super (spinner);
376: if (!(spinner.getModel() instanceof SpinnerDateModel)) {
377: throw new IllegalArgumentException(Messages.getString(
378: "swing.2C", "SpinnerDateModel")); //$NON-NLS-1$ //$NON-NLS-2$
379: }
380: format = new SimpleDateFormat();
381: initTextField();
382: }
383:
384: public DateEditor(JSpinner spinner, String dateFormatPattern) {
385: super (spinner);
386: if (!(spinner.getModel() instanceof SpinnerDateModel)) {
387: throw new IllegalArgumentException(Messages.getString(
388: "swing.2C", "SpinnerDateModel")); //$NON-NLS-1$ //$NON-NLS-2$
389: }
390: format = new SimpleDateFormat(dateFormatPattern);
391: initTextField();
392: }
393:
394: public SimpleDateFormat getFormat() {
395: return format;
396: }
397:
398: public SpinnerDateModel getModel() {
399: return (SpinnerDateModel) this .getSpinner().getModel();
400: }
401:
402: private void initTextField() {
403: SpinnerDateFormatter formatter = new SpinnerDateFormatter(
404: format, getModel());
405: JFormattedTextField textField = getTextField();
406: textField.setFormatterFactory(new DefaultFormatterFactory(
407: formatter));
408: textField.setEditable(true);
409: }
410: }
411:
412: private static class SpinnerListFormatter extends AbstractFormatter {
413: private static final long serialVersionUID = 1L;
414:
415: private class ListFilter extends DocumentFilter {
416: private JFormattedTextField textField;
417:
418: public ListFilter(JFormattedTextField textField) {
419: this .textField = textField;
420: }
421:
422: @Override
423: public void insertString(FilterBypass fb, int offset,
424: String text, AttributeSet attrs)
425: throws BadLocationException {
426: super .insertString(fb, offset, text, attrs);
427: }
428:
429: @Override
430: public void replace(FilterBypass fb, int offset,
431: int length, String text, AttributeSet attrs)
432: throws BadLocationException {
433: String str = textField.getText().substring(0, offset)
434: + text;
435: String replace = findElementText(str);
436: if (!"".equals(replace)) {
437: fb.replace(0, textField.getText().length(),
438: replace, attrs);
439: textField.setCaretPosition(offset + text.length());
440: textField.moveCaretPosition(textField.getText()
441: .length());
442: } else {
443: super .replace(fb, offset, length, text, attrs);
444: }
445: }
446:
447: private String findElementText(String text) {
448: Object findElement = findElement(text);
449: if (findElement == null) {
450: return "";
451: }
452: String result = findElement.toString();
453: return (result.indexOf(text) == 0) ? result : "";
454: }
455: }
456:
457: private SpinnerListModel model;
458:
459: private ListFilter filter;
460:
461: public SpinnerListFormatter(SpinnerListModel model,
462: JFormattedTextField textField) {
463: this .model = model;
464: filter = new ListFilter(textField);
465: }
466:
467: @Override
468: public Object stringToValue(String text) throws ParseException {
469: return ("".equals(text)) ? null : findElement(text);
470: }
471:
472: @Override
473: public String valueToString(Object value) throws ParseException {
474: return (value == null ? "" : value.toString());
475: }
476:
477: @Override
478: protected DocumentFilter getDocumentFilter() {
479: return filter;
480: }
481:
482: private Object findElement(String text) {
483: List<?> modelList = model.getList();
484: for (int i = 0; i < modelList.size(); i++) {
485: Object obj = modelList.get(i);
486: if (obj != null && obj.toString().indexOf(text) == 0) {
487: return obj;
488: }
489: }
490: return modelList.get(0);
491: }
492: }
493:
494: public static class ListEditor extends DefaultEditor {
495: private static final long serialVersionUID = 1L;
496:
497: public ListEditor(JSpinner spinner) {
498: super (spinner);
499: if (!(spinner.getModel() instanceof SpinnerListModel)) {
500: throw new IllegalArgumentException(Messages.getString(
501: "swing.2C", "SpinnerListModel")); //$NON-NLS-1$ //$NON-NLS-2$
502: }
503: SpinnerListFormatter formatter = new SpinnerListFormatter(
504: this .getModel(), this .getTextField());
505: JFormattedTextField textField = this .getTextField();
506: textField.setFormatterFactory(new DefaultFormatterFactory(
507: formatter));
508: textField.setEditable(true);
509: }
510:
511: public SpinnerListModel getModel() {
512: return (SpinnerListModel) this .getSpinner().getModel();
513: }
514: }
515:
516: @SuppressWarnings("unchecked")
517: private static class SpinnerNumberFormatter extends NumberFormatter {
518: private static final long serialVersionUID = 1L;
519:
520: private SpinnerNumberModel model;
521:
522: public SpinnerNumberFormatter(DecimalFormat format,
523: SpinnerNumberModel model) {
524: super (format);
525: this .model = model;
526: setValueClass(model.getValue().getClass());
527: }
528:
529: @Override
530: public void setMaximum(Comparable max) {
531: super .setMaximum(max);
532: model.setMaximum(max);
533: }
534:
535: @Override
536: public void setMinimum(Comparable min) {
537: super .setMinimum(min);
538: model.setMinimum(min);
539: }
540:
541: @Override
542: public Comparable getMaximum() {
543: Comparable max = model.getMaximum();
544: super .setMaximum(max);
545: return max;
546: }
547:
548: @Override
549: public Comparable getMinimum() {
550: Comparable min = model.getMinimum();
551: super .setMinimum(min);
552: return min;
553: }
554: }
555:
556: public static class NumberEditor extends DefaultEditor {
557: private static final long serialVersionUID = 1L;
558:
559: private DecimalFormat format;
560:
561: public NumberEditor(JSpinner spinner) {
562: super (spinner);
563: if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
564: throw new IllegalArgumentException(Messages.getString(
565: "swing.2C", "SpinnerNumberModel")); //$NON-NLS-1$ //$NON-NLS-2$
566: }
567: format = new DecimalFormat();
568: initTextField();
569: }
570:
571: public NumberEditor(JSpinner spinner,
572: String decimalFormatPattern) {
573: super (spinner);
574: if (!(spinner.getModel() instanceof SpinnerNumberModel)) {
575: throw new IllegalArgumentException(Messages.getString(
576: "swing.2C", "SpinnerNumberModel")); //$NON-NLS-1$ //$NON-NLS-2$
577: }
578: format = new DecimalFormat(decimalFormatPattern);
579: initTextField();
580: }
581:
582: public DecimalFormat getFormat() {
583: return format;
584: }
585:
586: public SpinnerNumberModel getModel() {
587: return (SpinnerNumberModel) this .getSpinner().getModel();
588: }
589:
590: private void initTextField() {
591: SpinnerNumberFormatter numberFormatter = new SpinnerNumberFormatter(
592: format, this .getModel());
593: JFormattedTextField textField = this .getTextField();
594: textField.setFormatterFactory(new DefaultFormatterFactory(
595: numberFormatter));
596: textField.setHorizontalAlignment(SwingConstants.RIGHT);
597: textField.setEditable(true);
598: }
599: }
600:
601: private class ModelChangeListener implements ChangeListener,
602: Serializable {
603: private static final long serialVersionUID = 1L;
604:
605: public void stateChanged(ChangeEvent e) {
606: fireStateChanged();
607: }
608: }
609:
610: private static Action disabledAction = new AbstractAction() {
611: private static final long serialVersionUID = 1L;
612:
613: public void actionPerformed(ActionEvent e) {
614: }
615:
616: @Override
617: public boolean isEnabled() {
618: return false;
619: }
620: };
621:
622: private static final String UI_CLASS_ID = "SpinnerUI";
623:
624: private SpinnerModel model;
625:
626: private boolean editorSet;
627:
628: private JComponent editor;
629:
630: private ChangeListener changeListener = new ModelChangeListener();
631:
632: private ChangeEvent changeEvent;
633:
634: public JSpinner(SpinnerModel model) {
635: this .model = model;
636: model.addChangeListener(changeListener);
637: editor = createEditor(model);
638: updateUI();
639: }
640:
641: public JSpinner() {
642: this (new SpinnerNumberModel());
643: }
644:
645: public SpinnerUI getUI() {
646: return (SpinnerUI) ui;
647: }
648:
649: public void setUI(SpinnerUI ui) {
650: super .setUI(ui);
651: }
652:
653: @Override
654: public String getUIClassID() {
655: return UI_CLASS_ID;
656: }
657:
658: @Override
659: public void updateUI() {
660: setUI((SpinnerUI) UIManager.getUI(this ));
661: }
662:
663: protected JComponent createEditor(SpinnerModel model) {
664: if (model instanceof SpinnerNumberModel) {
665: return new NumberEditor(this );
666: }
667: if (model instanceof SpinnerDateModel) {
668: return new DateEditor(this );
669: }
670: if (model instanceof SpinnerListModel) {
671: return new ListEditor(this );
672: }
673: return new DefaultEditor(this );
674: }
675:
676: public void setModel(SpinnerModel model) {
677: if (model == null) {
678: throw new IllegalArgumentException(Messages
679: .getString("swing.2F")); //$NON-NLS-1$
680: }
681: SpinnerModel oldModel = this .model;
682: oldModel.removeChangeListener(changeListener);
683: this .model = model;
684: model.addChangeListener(changeListener);
685: firePropertyChange(StringConstants.MODEL_PROPERTY_CHANGED,
686: oldModel, model);
687: if (!editorSet) {
688: setEditor(createEditor(model));
689: editorSet = false;
690: }
691: }
692:
693: public SpinnerModel getModel() {
694: return model;
695: }
696:
697: public Object getValue() {
698: return model.getValue();
699: }
700:
701: public void setValue(Object value) {
702: model.setValue(value);
703: }
704:
705: public Object getNextValue() {
706: return model.getNextValue();
707: }
708:
709: public Object getPreviousValue() {
710: return model.getPreviousValue();
711: }
712:
713: public void addChangeListener(ChangeListener listener) {
714: listenerList.add(ChangeListener.class, listener);
715: }
716:
717: public void removeChangeListener(ChangeListener listener) {
718: listenerList.remove(ChangeListener.class, listener);
719: }
720:
721: public ChangeListener[] getChangeListeners() {
722: return getListeners(ChangeListener.class);
723: }
724:
725: protected void fireStateChanged() {
726: if (changeEvent == null) {
727: changeEvent = new ChangeEvent(this );
728: }
729: ChangeListener[] listeners = getChangeListeners();
730: for (int i = 0; i < listeners.length; i++) {
731: listeners[i].stateChanged(changeEvent);
732: }
733: }
734:
735: public void setEditor(JComponent editor) {
736: if (editor == null) {
737: throw new IllegalArgumentException(Messages
738: .getString("swing.30")); //$NON-NLS-1$
739: }
740: JComponent oldEditor = this .editor;
741: if (oldEditor == editor) {
742: return;
743: }
744: if (oldEditor instanceof DefaultEditor) {
745: DefaultEditor def = (DefaultEditor) oldEditor;
746: def.dismiss(this );
747: }
748: this .editor = editor;
749: editorSet = true;
750: firePropertyChange(StringConstants.EDITOR_PROPERTY_CHANGED,
751: oldEditor, editor);
752: }
753:
754: public JComponent getEditor() {
755: return editor;
756: }
757:
758: public void commitEdit() throws ParseException {
759: if (editor instanceof DefaultEditor) {
760: ((DefaultEditor) editor).commitEdit();
761: }
762: }
763:
764: @Override
765: public AccessibleContext getAccessibleContext() {
766: if (accessibleContext == null) {
767: accessibleContext = new AccessibleJSpinner();
768: }
769: return accessibleContext;
770: }
771: }
|