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/plaf/basic/BasicSpinnerUI.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/plaf/basic/BasicSpinnerUI.java+1062−0
@@ -0,0 +1,1062 @@
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+
26+package javax.swing.plaf.basic;
27+
28+import java.awt.*;
29+import java.awt.event.*;
30+import java.text.ParseException;
31+
32+import javax.swing.*;
33+import javax.swing.border.*;
34+import javax.swing.event.*;
35+import javax.swing.plaf.*;
36+import javax.swing.text.*;
37+
38+import java.beans.*;
39+import java.text.*;
40+import java.util.*;
41+import sun.swing.DefaultLookup;
42+
43+
44+/**
45+ * The default Spinner UI delegate.
46+ *
47+ * @author Hans Muller
48+ * @since 1.4
49+ */
50+public class BasicSpinnerUI extends SpinnerUI
51+{
52+ /**
53+ * The spinner that we're a UI delegate for. Initialized by
54+ * the <code>installUI</code> method, and reset to null
55+ * by <code>uninstallUI</code>.
56+ *
57+ * @see #installUI
58+ * @see #uninstallUI
59+ */
60+ protected JSpinner spinner;
61+ private Handler handler;
62+
63+
64+ /**
65+ * The mouse/action listeners that are added to the spinner's
66+ * arrow buttons. These listeners are shared by all
67+ * spinner arrow buttons.
68+ *
69+ * @see #createNextButton
70+ * @see #createPreviousButton
71+ */
72+ private static final ArrowButtonHandler nextButtonHandler = new ArrowButtonHandler("increment", true);
73+ private static final ArrowButtonHandler previousButtonHandler = new ArrowButtonHandler("decrement", false);
74+ private PropertyChangeListener propertyChangeListener;
75+
76+
77+ /**
78+ * Used by the default LayoutManager class - SpinnerLayout for
79+ * missing (null) editor/nextButton/previousButton children.
80+ */
81+ private static final Dimension zeroSize = new Dimension(0, 0);
82+
83+
84+ /**
85+ * Returns a new instance of BasicSpinnerUI. SpinnerListUI
86+ * delegates are allocated one per JSpinner.
87+ *
88+ * @param c the JSpinner (not used)
89+ * @see ComponentUI#createUI
90+ * @return a new BasicSpinnerUI object
91+ */
92+ public static ComponentUI createUI(JComponent c) {
93+ return new BasicSpinnerUI();
94+ }
95+
96+
97+ private void maybeAdd(Component c, String s) {
98+ if (c != null) {
99+ spinner.add(c, s);
100+ }
101+ }
102+
103+
104+ /**
105+ * Calls <code>installDefaults</code>, <code>installListeners</code>,
106+ * and then adds the components returned by <code>createNextButton</code>,
107+ * <code>createPreviousButton</code>, and <code>createEditor</code>.
108+ *
109+ * @param c the JSpinner
110+ * @see #installDefaults
111+ * @see #installListeners
112+ * @see #createNextButton
113+ * @see #createPreviousButton
114+ * @see #createEditor
115+ */
116+ public void installUI(JComponent c) {
117+ this.spinner = (JSpinner)c;
118+ installDefaults();
119+ installListeners();
120+ maybeAdd(createNextButton(), "Next");
121+ maybeAdd(createPreviousButton(), "Previous");
122+ maybeAdd(createEditor(), "Editor");
123+ updateEnabledState();
124+ installKeyboardActions();
125+ }
126+
127+
128+ /**
129+ * Calls <code>uninstallDefaults</code>, <code>uninstallListeners</code>,
130+ * and then removes all of the spinners children.
131+ *
132+ * @param c the JSpinner (not used)
133+ */
134+ public void uninstallUI(JComponent c) {
135+ uninstallDefaults();
136+ uninstallListeners();
137+ this.spinner = null;
138+ c.removeAll();
139+ }
140+
141+
142+ /**
143+ * Initializes <code>PropertyChangeListener</code> with
144+ * a shared object that delegates interesting PropertyChangeEvents
145+ * to protected methods.
146+ * <p>
147+ * This method is called by <code>installUI</code>.
148+ *
149+ * @see #replaceEditor
150+ * @see #uninstallListeners
151+ */
152+ protected void installListeners() {
153+ propertyChangeListener = createPropertyChangeListener();
154+ spinner.addPropertyChangeListener(propertyChangeListener);
155+ if (DefaultLookup.getBoolean(spinner, this,
156+ "Spinner.disableOnBoundaryValues", false)) {
157+ spinner.addChangeListener(getHandler());
158+ }
159+ JComponent editor = spinner.getEditor();
160+ if (editor != null && editor instanceof JSpinner.DefaultEditor) {
161+ JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField();
162+ if (tf != null) {
163+ tf.addFocusListener(nextButtonHandler);
164+ tf.addFocusListener(previousButtonHandler);
165+ }
166+ }
167+ }
168+
169+
170+ /**
171+ * Removes the <code>PropertyChangeListener</code> added
172+ * by installListeners.
173+ * <p>
174+ * This method is called by <code>uninstallUI</code>.
175+ *
176+ * @see #installListeners
177+ */
178+ protected void uninstallListeners() {
179+ spinner.removePropertyChangeListener(propertyChangeListener);
180+ spinner.removeChangeListener(handler);
181+ JComponent editor = spinner.getEditor();
182+ removeEditorBorderListener(editor);
183+ if (editor instanceof JSpinner.DefaultEditor) {
184+ JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField();
185+ if (tf != null) {
186+ tf.removeFocusListener(nextButtonHandler);
187+ tf.removeFocusListener(previousButtonHandler);
188+ }
189+ }
190+ propertyChangeListener = null;
191+ handler = null;
192+ }
193+
194+
195+ /**
196+ * Initialize the <code>JSpinner</code> <code>border</code>,
197+ * <code>foreground</code>, and <code>background</code>, properties
198+ * based on the corresponding "Spinner.*" properties from defaults table.
199+ * The <code>JSpinners</code> layout is set to the value returned by
200+ * <code>createLayout</code>. This method is called by <code>installUI</code>.
201+ *
202+ * @see #uninstallDefaults
203+ * @see #installUI
204+ * @see #createLayout
205+ * @see LookAndFeel#installBorder
206+ * @see LookAndFeel#installColors
207+ */
208+ protected void installDefaults() {
209+ spinner.setLayout(createLayout());
210+ LookAndFeel.installBorder(spinner, "Spinner.border");
211+ LookAndFeel.installColorsAndFont(spinner, "Spinner.background", "Spinner.foreground", "Spinner.font");
212+ LookAndFeel.installProperty(spinner, "opaque", Boolean.TRUE);
213+ }
214+
215+
216+ /**
217+ * Sets the <code>JSpinner's</code> layout manager to null. This
218+ * method is called by <code>uninstallUI</code>.
219+ *
220+ * @see #installDefaults
221+ * @see #uninstallUI
222+ */
223+ protected void uninstallDefaults() {
224+ spinner.setLayout(null);
225+ }
226+
227+
228+ private Handler getHandler() {
229+ if (handler == null) {
230+ handler = new Handler();
231+ }
232+ return handler;
233+ }
234+
235+
236+ /**
237+ * Installs the necessary listeners on the next button, <code>c</code>,
238+ * to update the <code>JSpinner</code> in response to a user gesture.
239+ *
240+ * @param c Component to install the listeners on
241+ * @throws NullPointerException if <code>c</code> is null.
242+ * @see #createNextButton
243+ * @since 1.5
244+ */
245+ protected void installNextButtonListeners(Component c) {
246+ installButtonListeners(c, nextButtonHandler);
247+ }
248+
249+ /**
250+ * Installs the necessary listeners on the previous button, <code>c</code>,
251+ * to update the <code>JSpinner</code> in response to a user gesture.
252+ *
253+ * @param c Component to install the listeners on.
254+ * @throws NullPointerException if <code>c</code> is null.
255+ * @see #createPreviousButton
256+ * @since 1.5
257+ */
258+ protected void installPreviousButtonListeners(Component c) {
259+ installButtonListeners(c, previousButtonHandler);
260+ }
261+
262+ private void installButtonListeners(Component c,
263+ ArrowButtonHandler handler) {
264+ if (c instanceof JButton) {
265+ ((JButton)c).addActionListener(handler);
266+ }
267+ c.addMouseListener(handler);
268+ }
269+
270+ /**
271+ * Creates a <code>LayoutManager</code> that manages the <code>editor</code>,
272+ * <code>nextButton</code>, and <code>previousButton</code>
273+ * children of the JSpinner. These three children must be
274+ * added with a constraint that identifies their role:
275+ * "Editor", "Next", and "Previous". The default layout manager
276+ * can handle the absence of any of these children.
277+ *
278+ * @return a LayoutManager for the editor, next button, and previous button.
279+ * @see #createNextButton
280+ * @see #createPreviousButton
281+ * @see #createEditor
282+ */
283+ protected LayoutManager createLayout() {
284+ return getHandler();
285+ }
286+
287+
288+ /**
289+ * Creates a <code>PropertyChangeListener</code> that can be
290+ * added to the JSpinner itself. Typically, this listener
291+ * will call replaceEditor when the "editor" property changes,
292+ * since it's the <code>SpinnerUI's</code> responsibility to
293+ * add the editor to the JSpinner (and remove the old one).
294+ * This method is called by <code>installListeners</code>.
295+ *
296+ * @return A PropertyChangeListener for the JSpinner itself
297+ * @see #installListeners
298+ */
299+ protected PropertyChangeListener createPropertyChangeListener() {
300+ return getHandler();
301+ }
302+
303+
304+ /**
305+ * Creates a decrement button, i.e. component that replaces the spinner
306+ * value with the object returned by <code>spinner.getPreviousValue</code>.
307+ * By default the <code>previousButton</code> is a {@code JButton}. If the
308+ * decrement button is not needed this method should return {@code null}.
309+ *
310+ * @return a component that will replace the spinner's value with the
311+ * previous value in the sequence, or {@code null}
312+ * @see #installUI
313+ * @see #createNextButton
314+ * @see #installPreviousButtonListeners
315+ */
316+ protected Component createPreviousButton() {
317+ Component c = createArrowButton(SwingConstants.SOUTH);
318+ c.setName("Spinner.previousButton");
319+ installPreviousButtonListeners(c);
320+ return c;
321+ }
322+
323+
324+ /**
325+ * Creates an increment button, i.e. component that replaces the spinner
326+ * value with the object returned by <code>spinner.getNextValue</code>.
327+ * By default the <code>nextButton</code> is a {@code JButton}. If the
328+ * increment button is not needed this method should return {@code null}.
329+ *
330+ * @return a component that will replace the spinner's value with the
331+ * next value in the sequence, or {@code null}
332+ * @see #installUI
333+ * @see #createPreviousButton
334+ * @see #installNextButtonListeners
335+ */
336+ protected Component createNextButton() {
337+ Component c = createArrowButton(SwingConstants.NORTH);
338+ c.setName("Spinner.nextButton");
339+ installNextButtonListeners(c);
340+ return c;
341+ }
342+
343+ private Component createArrowButton(int direction) {
344+ JButton b = new BasicArrowButton(direction);
345+ Border buttonBorder = UIManager.getBorder("Spinner.arrowButtonBorder");
346+ if (buttonBorder instanceof UIResource) {
347+ // Wrap the border to avoid having the UIResource be replaced by
348+ // the ButtonUI. This is the opposite of using BorderUIResource.
349+ b.setBorder(new CompoundBorder(buttonBorder, null));
350+ } else {
351+ b.setBorder(buttonBorder);
352+ }
353+ b.setInheritsPopupMenu(true);
354+ return b;
355+ }
356+
357+
358+ /**
359+ * This method is called by installUI to get the editor component
360+ * of the <code>JSpinner</code>. By default it just returns
361+ * <code>JSpinner.getEditor()</code>. Subclasses can override
362+ * <code>createEditor</code> to return a component that contains
363+ * the spinner's editor or null, if they're going to handle adding
364+ * the editor to the <code>JSpinner</code> in an
365+ * <code>installUI</code> override.
366+ * <p>
367+ * Typically this method would be overridden to wrap the editor
368+ * with a container with a custom border, since one can't assume
369+ * that the editors border can be set directly.
370+ * <p>
371+ * The <code>replaceEditor</code> method is called when the spinners
372+ * editor is changed with <code>JSpinner.setEditor</code>. If you've
373+ * overriden this method, then you'll probably want to override
374+ * <code>replaceEditor</code> as well.
375+ *
376+ * @return the JSpinners editor JComponent, spinner.getEditor() by default
377+ * @see #installUI
378+ * @see #replaceEditor
379+ * @see JSpinner#getEditor
380+ */
381+ protected JComponent createEditor() {
382+ JComponent editor = spinner.getEditor();
383+ maybeRemoveEditorBorder(editor);
384+ installEditorBorderListener(editor);
385+ editor.setInheritsPopupMenu(true);
386+ updateEditorAlignment(editor);
387+ return editor;
388+ }
389+
390+
391+ /**
392+ * Called by the <code>PropertyChangeListener</code> when the
393+ * <code>JSpinner</code> editor property changes. It's the responsibility
394+ * of this method to remove the old editor and add the new one. By
395+ * default this operation is just:
396+ * <pre>
397+ * spinner.remove(oldEditor);
398+ * spinner.add(newEditor, "Editor");
399+ * </pre>
400+ * The implementation of <code>replaceEditor</code> should be coordinated
401+ * with the <code>createEditor</code> method.
402+ *
403+ * @see #createEditor
404+ * @see #createPropertyChangeListener
405+ */
406+ protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
407+ spinner.remove(oldEditor);
408+ maybeRemoveEditorBorder(newEditor);
409+ installEditorBorderListener(newEditor);
410+ newEditor.setInheritsPopupMenu(true);
411+ spinner.add(newEditor, "Editor");
412+ }
413+
414+ private void updateEditorAlignment(JComponent editor) {
415+ if (editor instanceof JSpinner.DefaultEditor) {
416+ // if editor alignment isn't set in LAF, we get 0 (CENTER) here
417+ int alignment = UIManager.getInt("Spinner.editorAlignment");
418+ JTextField text = ((JSpinner.DefaultEditor)editor).getTextField();
419+ text.setHorizontalAlignment(alignment);
420+ }
421+ }
422+
423+ /**
424+ * Remove the border around the inner editor component for LaFs
425+ * that install an outside border around the spinner,
426+ */
427+ private void maybeRemoveEditorBorder(JComponent editor) {
428+ if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
429+ if (editor instanceof JPanel &&
430+ editor.getBorder() == null &&
431+ editor.getComponentCount() > 0) {
432+
433+ editor = (JComponent)editor.getComponent(0);
434+ }
435+
436+ if (editor != null && editor.getBorder() instanceof UIResource) {
437+ editor.setBorder(null);
438+ }
439+ }
440+ }
441+
442+ /**
443+ * Remove the border around the inner editor component for LaFs
444+ * that install an outside border around the spinner,
445+ */
446+ private void installEditorBorderListener(JComponent editor) {
447+ if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
448+ if (editor instanceof JPanel &&
449+ editor.getBorder() == null &&
450+ editor.getComponentCount() > 0) {
451+
452+ editor = (JComponent)editor.getComponent(0);
453+ }
454+ if (editor != null &&
455+ (editor.getBorder() == null ||
456+ editor.getBorder() instanceof UIResource)) {
457+ editor.addPropertyChangeListener(getHandler());
458+ }
459+ }
460+ }
461+
462+ private void removeEditorBorderListener(JComponent editor) {
463+ if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
464+ if (editor instanceof JPanel &&
465+ editor.getComponentCount() > 0) {
466+
467+ editor = (JComponent)editor.getComponent(0);
468+ }
469+ if (editor != null) {
470+ editor.removePropertyChangeListener(getHandler());
471+ }
472+ }
473+ }
474+
475+
476+ /**
477+ * Updates the enabled state of the children Components based on the
478+ * enabled state of the <code>JSpinner</code>.
479+ */
480+ private void updateEnabledState() {
481+ updateEnabledState(spinner, spinner.isEnabled());
482+ }
483+
484+
485+ /**
486+ * Recursively updates the enabled state of the child
487+ * <code>Component</code>s of <code>c</code>.
488+ */
489+ private void updateEnabledState(Container c, boolean enabled) {
490+ for (int counter = c.getComponentCount() - 1; counter >= 0;counter--) {
491+ Component child = c.getComponent(counter);
492+
493+ if (DefaultLookup.getBoolean(spinner, this,
494+ "Spinner.disableOnBoundaryValues", false)) {
495+ SpinnerModel model = spinner.getModel();
496+ if (child.getName() == "Spinner.nextButton" &&
497+ model.getNextValue() == null) {
498+ child.setEnabled(false);
499+ }
500+ else if (child.getName() == "Spinner.previousButton" &&
501+ model.getPreviousValue() == null) {
502+ child.setEnabled(false);
503+ }
504+ else {
505+ child.setEnabled(enabled);
506+ }
507+ }
508+ else {
509+ child.setEnabled(enabled);
510+ }
511+ if (child instanceof Container) {
512+ updateEnabledState((Container)child, enabled);
513+ }
514+ }
515+ }
516+
517+
518+ /**
519+ * Installs the keyboard Actions onto the JSpinner.
520+ *
521+ * @since 1.5
522+ */
523+ protected void installKeyboardActions() {
524+ InputMap iMap = getInputMap(JComponent.
525+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
526+
527+ SwingUtilities.replaceUIInputMap(spinner, JComponent.
528+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
529+ iMap);
530+
531+ LazyActionMap.installLazyActionMap(spinner, BasicSpinnerUI.class,
532+ "Spinner.actionMap");
533+ }
534+
535+ /**
536+ * Returns the InputMap to install for <code>condition</code>.
537+ */
538+ private InputMap getInputMap(int condition) {
539+ if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
540+ return (InputMap)DefaultLookup.get(spinner, this,
541+ "Spinner.ancestorInputMap");
542+ }
543+ return null;
544+ }
545+
546+ static void loadActionMap(LazyActionMap map) {
547+ map.put("increment", nextButtonHandler);
548+ map.put("decrement", previousButtonHandler);
549+ }
550+
551+ /**
552+ * Returns the baseline.
553+ *
554+ * @throws NullPointerException {@inheritDoc}
555+ * @throws IllegalArgumentException {@inheritDoc}
556+ * @see javax.swing.JComponent#getBaseline(int, int)
557+ * @since 1.6
558+ */
559+ public int getBaseline(JComponent c, int width, int height) {
560+ super.getBaseline(c, width, height);
561+ JComponent editor = spinner.getEditor();
562+ Insets insets = spinner.getInsets();
563+ width = width - insets.left - insets.right;
564+ height = height - insets.top - insets.bottom;
565+ if (width >= 0 && height >= 0) {
566+ int baseline = editor.getBaseline(width, height);
567+ if (baseline >= 0) {
568+ return insets.top + baseline;
569+ }
570+ }
571+ return -1;
572+ }
573+
574+ /**
575+ * Returns an enum indicating how the baseline of the component
576+ * changes as the size changes.
577+ *
578+ * @throws NullPointerException {@inheritDoc}
579+ * @see javax.swing.JComponent#getBaseline(int, int)
580+ * @since 1.6
581+ */
582+ public Component.BaselineResizeBehavior getBaselineResizeBehavior(
583+ JComponent c) {
584+ super.getBaselineResizeBehavior(c);
585+ return spinner.getEditor().getBaselineResizeBehavior();
586+ }
587+
588+ /**
589+ * A handler for spinner arrow button mouse and action events. When
590+ * a left mouse pressed event occurs we look up the (enabled) spinner
591+ * that's the source of the event and start the autorepeat timer. The
592+ * timer fires action events until any button is released at which
593+ * point the timer is stopped and the reference to the spinner cleared.
594+ * The timer doesn't start until after a 300ms delay, so often the
595+ * source of the initial (and final) action event is just the button
596+ * logic for mouse released - which means that we're relying on the fact
597+ * that our mouse listener runs after the buttons mouse listener.
598+ * <p>
599+ * Note that one instance of this handler is shared by all slider previous
600+ * arrow buttons and likewise for all of the next buttons,
601+ * so it doesn't have any state that persists beyond the limits
602+ * of a single button pressed/released gesture.
603+ */
604+ private static class ArrowButtonHandler extends AbstractAction
605+ implements FocusListener, MouseListener, UIResource {
606+ final javax.swing.Timer autoRepeatTimer;
607+ final boolean isNext;
608+ JSpinner spinner = null;
609+ JButton arrowButton = null;
610+
611+ ArrowButtonHandler(String name, boolean isNext) {
612+ super(name);
613+ this.isNext = isNext;
614+ autoRepeatTimer = new javax.swing.Timer(60, this);
615+ autoRepeatTimer.setInitialDelay(300);
616+ }
617+
618+ private JSpinner eventToSpinner(AWTEvent e) {
619+ Object src = e.getSource();
620+ while ((src instanceof Component) && !(src instanceof JSpinner)) {
621+ src = ((Component)src).getParent();
622+ }
623+ return (src instanceof JSpinner) ? (JSpinner)src : null;
624+ }
625+
626+ public void actionPerformed(ActionEvent e) {
627+ JSpinner spinner = this.spinner;
628+
629+ if (!(e.getSource() instanceof javax.swing.Timer)) {
630+ // Most likely resulting from being in ActionMap.
631+ spinner = eventToSpinner(e);
632+ if (e.getSource() instanceof JButton) {
633+ arrowButton = (JButton)e.getSource();
634+ }
635+ } else {
636+ if (arrowButton!=null && !arrowButton.getModel().isPressed()
637+ && autoRepeatTimer.isRunning()) {
638+ autoRepeatTimer.stop();
639+ spinner = null;
640+ arrowButton = null;
641+ }
642+ }
643+ if (spinner != null) {
644+ try {
645+ int calendarField = getCalendarField(spinner);
646+ spinner.commitEdit();
647+ if (calendarField != -1) {
648+ ((SpinnerDateModel)spinner.getModel()).
649+ setCalendarField(calendarField);
650+ }
651+ Object value = (isNext) ? spinner.getNextValue() :
652+ spinner.getPreviousValue();
653+ if (value != null) {
654+ spinner.setValue(value);
655+ select(spinner);
656+ }
657+ } catch (IllegalArgumentException iae) {
658+ UIManager.getLookAndFeel().provideErrorFeedback(spinner);
659+ } catch (ParseException pe) {
660+ UIManager.getLookAndFeel().provideErrorFeedback(spinner);
661+ }
662+ }
663+ }
664+
665+ /**
666+ * If the spinner's editor is a DateEditor, this selects the field
667+ * associated with the value that is being incremented.
668+ */
669+ private void select(JSpinner spinner) {
670+ JComponent editor = spinner.getEditor();
671+
672+ if (editor instanceof JSpinner.DateEditor) {
673+ JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
674+ JFormattedTextField ftf = dateEditor.getTextField();
675+ Format format = dateEditor.getFormat();
676+ Object value;
677+
678+ if (format != null && (value = spinner.getValue()) != null) {
679+ SpinnerDateModel model = dateEditor.getModel();
680+ DateFormat.Field field = DateFormat.Field.ofCalendarField(
681+ model.getCalendarField());
682+
683+ if (field != null) {
684+ try {
685+ AttributedCharacterIterator iterator = format.
686+ formatToCharacterIterator(value);
687+ if (!select(ftf, iterator, field) &&
688+ field == DateFormat.Field.HOUR0) {
689+ select(ftf, iterator, DateFormat.Field.HOUR1);
690+ }
691+ }
692+ catch (IllegalArgumentException iae) {}
693+ }
694+ }
695+ }
696+ }
697+
698+ /**
699+ * Selects the passed in field, returning true if it is found,
700+ * false otherwise.
701+ */
702+ private boolean select(JFormattedTextField ftf,
703+ AttributedCharacterIterator iterator,
704+ DateFormat.Field field) {
705+ int max = ftf.getDocument().getLength();
706+
707+ iterator.first();
708+ do {
709+ Map attrs = iterator.getAttributes();
710+
711+ if (attrs != null && attrs.containsKey(field)){
712+ int start = iterator.getRunStart(field);
713+ int end = iterator.getRunLimit(field);
714+
715+ if (start != -1 && end != -1 && start <= max &&
716+ end <= max) {
717+ ftf.select(start, end);
718+ }
719+ return true;
720+ }
721+ } while (iterator.next() != CharacterIterator.DONE);
722+ return false;
723+ }
724+
725+ /**
726+ * Returns the calendarField under the start of the selection, or
727+ * -1 if there is no valid calendar field under the selection (or
728+ * the spinner isn't editing dates.
729+ */
730+ private int getCalendarField(JSpinner spinner) {
731+ JComponent editor = spinner.getEditor();
732+
733+ if (editor instanceof JSpinner.DateEditor) {
734+ JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
735+ JFormattedTextField ftf = dateEditor.getTextField();
736+ int start = ftf.getSelectionStart();
737+ JFormattedTextField.AbstractFormatter formatter =
738+ ftf.getFormatter();
739+
740+ if (formatter instanceof InternationalFormatter) {
741+ Format.Field[] fields = ((InternationalFormatter)
742+ formatter).getFields(start);
743+
744+ for (int counter = 0; counter < fields.length; counter++) {
745+ if (fields[counter] instanceof DateFormat.Field) {
746+ int calendarField;
747+
748+ if (fields[counter] == DateFormat.Field.HOUR1) {
749+ calendarField = Calendar.HOUR;
750+ }
751+ else {
752+ calendarField = ((DateFormat.Field)
753+ fields[counter]).getCalendarField();
754+ }
755+ if (calendarField != -1) {
756+ return calendarField;
757+ }
758+ }
759+ }
760+ }
761+ }
762+ return -1;
763+ }
764+
765+ public void mousePressed(MouseEvent e) {
766+ if (SwingUtilities.isLeftMouseButton(e) && e.getComponent().isEnabled()) {
767+ spinner = eventToSpinner(e);
768+ autoRepeatTimer.start();
769+
770+ focusSpinnerIfNecessary();
771+ }
772+ }
773+
774+ public void mouseReleased(MouseEvent e) {
775+ autoRepeatTimer.stop();
776+ arrowButton = null;
777+ spinner = null;
778+ }
779+
780+ public void mouseClicked(MouseEvent e) {
781+ }
782+
783+ public void mouseEntered(MouseEvent e) {
784+ if (spinner != null && !autoRepeatTimer.isRunning() && spinner == eventToSpinner(e)) {
785+ autoRepeatTimer.start();
786+ }
787+ }
788+
789+ public void mouseExited(MouseEvent e) {
790+ if (autoRepeatTimer.isRunning()) {
791+ autoRepeatTimer.stop();
792+ }
793+ }
794+
795+ /**
796+ * Requests focus on a child of the spinner if the spinner doesn't
797+ * have focus.
798+ */
799+ private void focusSpinnerIfNecessary() {
800+ Component fo = KeyboardFocusManager.
801+ getCurrentKeyboardFocusManager().getFocusOwner();
802+ if (spinner.isRequestFocusEnabled() && (
803+ fo == null ||
804+ !SwingUtilities.isDescendingFrom(fo, spinner))) {
805+ Container root = spinner;
806+
807+ if (!root.isFocusCycleRoot()) {
808+ root = root.getFocusCycleRootAncestor();
809+ }
810+ if (root != null) {
811+ FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
812+ Component child = ftp.getComponentAfter(root, spinner);
813+
814+ if (child != null && SwingUtilities.isDescendingFrom(
815+ child, spinner)) {
816+ child.requestFocus();
817+ }
818+ }
819+ }
820+ }
821+
822+ public void focusGained(FocusEvent e) {
823+ }
824+
825+ public void focusLost(FocusEvent e) {
826+ if (spinner == eventToSpinner(e)) {
827+ if (autoRepeatTimer.isRunning()) {
828+ autoRepeatTimer.stop();
829+ }
830+ spinner = null;
831+ if (arrowButton != null) {
832+ ButtonModel model = arrowButton.getModel();
833+ model.setPressed(false);
834+ model.setArmed(false);
835+ arrowButton = null;
836+ }
837+ }
838+ }
839+ }
840+
841+
842+ private static class Handler implements LayoutManager,
843+ PropertyChangeListener, ChangeListener {
844+ //
845+ // LayoutManager
846+ //
847+ private Component nextButton = null;
848+ private Component previousButton = null;
849+ private Component editor = null;
850+
851+ public void addLayoutComponent(String name, Component c) {
852+ if ("Next".equals(name)) {
853+ nextButton = c;
854+ }
855+ else if ("Previous".equals(name)) {
856+ previousButton = c;
857+ }
858+ else if ("Editor".equals(name)) {
859+ editor = c;
860+ }
861+ }
862+
863+ public void removeLayoutComponent(Component c) {
864+ if (c == nextButton) {
865+ nextButton = null;
866+ }
867+ else if (c == previousButton) {
868+ previousButton = null;
869+ }
870+ else if (c == editor) {
871+ editor = null;
872+ }
873+ }
874+
875+ private Dimension preferredSize(Component c) {
876+ return (c == null) ? zeroSize : c.getPreferredSize();
877+ }
878+
879+ public Dimension preferredLayoutSize(Container parent) {
880+ Dimension nextD = preferredSize(nextButton);
881+ Dimension previousD = preferredSize(previousButton);
882+ Dimension editorD = preferredSize(editor);
883+
884+ /* Force the editors height to be a multiple of 2
885+ */
886+ editorD.height = ((editorD.height + 1) / 2) * 2;
887+
888+ Dimension size = new Dimension(editorD.width, editorD.height);
889+ size.width += Math.max(nextD.width, previousD.width);
890+ Insets insets = parent.getInsets();
891+ size.width += insets.left + insets.right;
892+ size.height += insets.top + insets.bottom;
893+ return size;
894+ }
895+
896+ public Dimension minimumLayoutSize(Container parent) {
897+ return preferredLayoutSize(parent);
898+ }
899+
900+ private void setBounds(Component c, int x, int y, int width, int height) {
901+ if (c != null) {
902+ c.setBounds(x, y, width, height);
903+ }
904+ }
905+
906+ public void layoutContainer(Container parent) {
907+ int width = parent.getWidth();
908+ int height = parent.getHeight();
909+
910+ Insets insets = parent.getInsets();
911+
912+ if (nextButton == null && previousButton == null) {
913+ setBounds(editor, insets.left, insets.top, width - insets.left - insets.right,
914+ height - insets.top - insets.bottom);
915+
916+ return;
917+ }
918+
919+ Dimension nextD = preferredSize(nextButton);
920+ Dimension previousD = preferredSize(previousButton);
921+ int buttonsWidth = Math.max(nextD.width, previousD.width);
922+ int editorHeight = height - (insets.top + insets.bottom);
923+
924+ // The arrowButtonInsets value is used instead of the JSpinner's
925+ // insets if not null. Defining this to be (0, 0, 0, 0) causes the
926+ // buttons to be aligned with the outer edge of the spinner's
927+ // border, and leaving it as "null" places the buttons completely
928+ // inside the spinner's border.
929+ Insets buttonInsets = UIManager.getInsets("Spinner.arrowButtonInsets");
930+ if (buttonInsets == null) {
931+ buttonInsets = insets;
932+ }
933+
934+ /* Deal with the spinner's componentOrientation property.
935+ */
936+ int editorX, editorWidth, buttonsX;
937+ if (parent.getComponentOrientation().isLeftToRight()) {
938+ editorX = insets.left;
939+ editorWidth = width - insets.left - buttonsWidth - buttonInsets.right;
940+ buttonsX = width - buttonsWidth - buttonInsets.right;
941+ } else {
942+ buttonsX = buttonInsets.left;
943+ editorX = buttonsX + buttonsWidth;
944+ editorWidth = width - buttonInsets.left - buttonsWidth - insets.right;
945+ }
946+
947+ int nextY = buttonInsets.top;
948+ int nextHeight = (height / 2) + (height % 2) - nextY;
949+ int previousY = buttonInsets.top + nextHeight;
950+ int previousHeight = height - previousY - buttonInsets.bottom;
951+
952+ setBounds(editor, editorX, insets.top, editorWidth, editorHeight);
953+ setBounds(nextButton, buttonsX, nextY, buttonsWidth, nextHeight);
954+ setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight);
955+ }
956+
957+
958+ //
959+ // PropertyChangeListener
960+ //
961+ public void propertyChange(PropertyChangeEvent e)
962+ {
963+ String propertyName = e.getPropertyName();
964+ if (e.getSource() instanceof JSpinner) {
965+ JSpinner spinner = (JSpinner)(e.getSource());
966+ SpinnerUI spinnerUI = spinner.getUI();
967+
968+ if (spinnerUI instanceof BasicSpinnerUI) {
969+ BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
970+
971+ if ("editor".equals(propertyName)) {
972+ JComponent oldEditor = (JComponent)e.getOldValue();
973+ JComponent newEditor = (JComponent)e.getNewValue();
974+ ui.replaceEditor(oldEditor, newEditor);
975+ ui.updateEnabledState();
976+ if (oldEditor instanceof JSpinner.DefaultEditor) {
977+ JTextField tf =
978+ ((JSpinner.DefaultEditor)oldEditor).getTextField();
979+ if (tf != null) {
980+ tf.removeFocusListener(nextButtonHandler);
981+ tf.removeFocusListener(previousButtonHandler);
982+ }
983+ }
984+ if (newEditor instanceof JSpinner.DefaultEditor) {
985+ JTextField tf =
986+ ((JSpinner.DefaultEditor)newEditor).getTextField();
987+ if (tf != null) {
988+ if (tf.getFont() instanceof UIResource) {
989+ tf.setFont(spinner.getFont());
990+ }
991+ tf.addFocusListener(nextButtonHandler);
992+ tf.addFocusListener(previousButtonHandler);
993+ }
994+ }
995+ }
996+ else if ("enabled".equals(propertyName) ||
997+ "model".equals(propertyName)) {
998+ ui.updateEnabledState();
999+ }
1000+ else if ("font".equals(propertyName)) {
1001+ JComponent editor = spinner.getEditor();
1002+ if (editor!=null && editor instanceof JSpinner.DefaultEditor) {
1003+ JTextField tf =
1004+ ((JSpinner.DefaultEditor)editor).getTextField();
1005+ if (tf != null) {
1006+ if (tf.getFont() instanceof UIResource) {
1007+ tf.setFont(spinner.getFont());
1008+ }
1009+ }
1010+ }
1011+ }
1012+ else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) {
1013+ updateToolTipTextForChildren(spinner);
1014+ }
1015+ }
1016+ } else if (e.getSource() instanceof JComponent) {
1017+ JComponent c = (JComponent)e.getSource();
1018+ if ((c.getParent() instanceof JPanel) &&
1019+ (c.getParent().getParent() instanceof JSpinner) &&
1020+ "border".equals(propertyName)) {
1021+
1022+ JSpinner spinner = (JSpinner)c.getParent().getParent();
1023+ SpinnerUI spinnerUI = spinner.getUI();
1024+ if (spinnerUI instanceof BasicSpinnerUI) {
1025+ BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
1026+ ui.maybeRemoveEditorBorder(c);
1027+ }
1028+ }
1029+ }
1030+ }
1031+
1032+ // Syncronizes the ToolTip text for the components within the spinner
1033+ // to be the same value as the spinner ToolTip text.
1034+ private void updateToolTipTextForChildren(JComponent spinner) {
1035+ String toolTipText = spinner.getToolTipText();
1036+ Component[] children = spinner.getComponents();
1037+ for (int i = 0; i < children.length; i++) {
1038+ if (children[i] instanceof JSpinner.DefaultEditor) {
1039+ JTextField tf = ((JSpinner.DefaultEditor)children[i]).getTextField();
1040+ if (tf != null) {
1041+ tf.setToolTipText(toolTipText);
1042+ }
1043+ } else if (children[i] instanceof JComponent) {
1044+ ((JComponent)children[i]).setToolTipText( spinner.getToolTipText() );
1045+ }
1046+ }
1047+ }
1048+
1049+ public void stateChanged(ChangeEvent e) {
1050+ if (e.getSource() instanceof JSpinner) {
1051+ JSpinner spinner = (JSpinner)e.getSource();
1052+ SpinnerUI spinnerUI = spinner.getUI();
1053+ if (DefaultLookup.getBoolean(spinner, spinnerUI,
1054+ "Spinner.disableOnBoundaryValues", false) &&
1055+ spinnerUI instanceof BasicSpinnerUI) {
1056+ BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
1057+ ui.updateEnabledState();
1058+ }
1059+ }
1060+ }
1061+ }
1062+}