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
26package javax.swing.plaf.basic;
27
28import java.awt.*;
29import java.awt.event.*;
30import java.text.ParseException;
31
32import javax.swing.*;
33import javax.swing.border.*;
34import javax.swing.event.*;
35import javax.swing.plaf.*;
36import javax.swing.text.*;
37
38import java.beans.*;
39import java.text.*;
40import java.util.*;
41import sun.swing.DefaultLookup;
42
43
44/**
45 * The default Spinner UI delegate.
46 *
47 * @author Hans Muller
48 * @since 1.4
49 */
50public 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}