/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.plaf.basic;
import java.awt.*;
import java.awt.event.*;
import java.text.ParseException;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
import java.beans.*;
import java.text.*;
import java.util.*;
import sun.swing.DefaultLookup;
The default Spinner UI delegate.
Author: Hans Muller Since: 1.4
/**
* The default Spinner UI delegate.
*
* @author Hans Muller
* @since 1.4
*/
public class BasicSpinnerUI extends SpinnerUI
{
The spinner that we're a UI delegate for. Initialized by
the installUI
method, and reset to null
by uninstallUI
.
See Also: - installUI
- uninstallUI
/**
* The spinner that we're a UI delegate for. Initialized by
* the <code>installUI</code> method, and reset to null
* by <code>uninstallUI</code>.
*
* @see #installUI
* @see #uninstallUI
*/
protected JSpinner spinner;
private Handler handler;
The mouse/action listeners that are added to the spinner's
arrow buttons. These listeners are shared by all
spinner arrow buttons.
See Also: - createNextButton
- createPreviousButton
/**
* The mouse/action listeners that are added to the spinner's
* arrow buttons. These listeners are shared by all
* spinner arrow buttons.
*
* @see #createNextButton
* @see #createPreviousButton
*/
private static final ArrowButtonHandler nextButtonHandler = new ArrowButtonHandler("increment", true);
private static final ArrowButtonHandler previousButtonHandler = new ArrowButtonHandler("decrement", false);
private PropertyChangeListener propertyChangeListener;
Used by the default LayoutManager class - SpinnerLayout for
missing (null) editor/nextButton/previousButton children.
/**
* Used by the default LayoutManager class - SpinnerLayout for
* missing (null) editor/nextButton/previousButton children.
*/
private static final Dimension zeroSize = new Dimension(0, 0);
Returns a new instance of BasicSpinnerUI. SpinnerListUI
delegates are allocated one per JSpinner.
Params: - c – the JSpinner (not used)
See Also: Returns: a new BasicSpinnerUI object
/**
* Returns a new instance of BasicSpinnerUI. SpinnerListUI
* delegates are allocated one per JSpinner.
*
* @param c the JSpinner (not used)
* @see ComponentUI#createUI
* @return a new BasicSpinnerUI object
*/
public static ComponentUI createUI(JComponent c) {
return new BasicSpinnerUI();
}
private void maybeAdd(Component c, String s) {
if (c != null) {
spinner.add(c, s);
}
}
Calls installDefaults
, installListeners
,
and then adds the components returned by createNextButton
,
createPreviousButton
, and createEditor
.
Params: - c – the JSpinner
See Also:
/**
* Calls <code>installDefaults</code>, <code>installListeners</code>,
* and then adds the components returned by <code>createNextButton</code>,
* <code>createPreviousButton</code>, and <code>createEditor</code>.
*
* @param c the JSpinner
* @see #installDefaults
* @see #installListeners
* @see #createNextButton
* @see #createPreviousButton
* @see #createEditor
*/
public void installUI(JComponent c) {
this.spinner = (JSpinner)c;
installDefaults();
installListeners();
maybeAdd(createNextButton(), "Next");
maybeAdd(createPreviousButton(), "Previous");
maybeAdd(createEditor(), "Editor");
updateEnabledState();
installKeyboardActions();
}
Calls uninstallDefaults
, uninstallListeners
,
and then removes all of the spinners children.
Params: - c – the JSpinner (not used)
/**
* Calls <code>uninstallDefaults</code>, <code>uninstallListeners</code>,
* and then removes all of the spinners children.
*
* @param c the JSpinner (not used)
*/
public void uninstallUI(JComponent c) {
uninstallDefaults();
uninstallListeners();
this.spinner = null;
c.removeAll();
}
Initializes PropertyChangeListener
with
a shared object that delegates interesting PropertyChangeEvents
to protected methods.
This method is called by installUI
.
See Also: - replaceEditor
- uninstallListeners
/**
* Initializes <code>PropertyChangeListener</code> with
* a shared object that delegates interesting PropertyChangeEvents
* to protected methods.
* <p>
* This method is called by <code>installUI</code>.
*
* @see #replaceEditor
* @see #uninstallListeners
*/
protected void installListeners() {
propertyChangeListener = createPropertyChangeListener();
spinner.addPropertyChangeListener(propertyChangeListener);
if (DefaultLookup.getBoolean(spinner, this,
"Spinner.disableOnBoundaryValues", false)) {
spinner.addChangeListener(getHandler());
}
JComponent editor = spinner.getEditor();
if (editor != null && editor instanceof JSpinner.DefaultEditor) {
JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField();
if (tf != null) {
tf.addFocusListener(nextButtonHandler);
tf.addFocusListener(previousButtonHandler);
}
}
}
Removes the PropertyChangeListener
added
by installListeners.
This method is called by uninstallUI
.
See Also: - installListeners
/**
* Removes the <code>PropertyChangeListener</code> added
* by installListeners.
* <p>
* This method is called by <code>uninstallUI</code>.
*
* @see #installListeners
*/
protected void uninstallListeners() {
spinner.removePropertyChangeListener(propertyChangeListener);
spinner.removeChangeListener(handler);
JComponent editor = spinner.getEditor();
removeEditorBorderListener(editor);
if (editor instanceof JSpinner.DefaultEditor) {
JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField();
if (tf != null) {
tf.removeFocusListener(nextButtonHandler);
tf.removeFocusListener(previousButtonHandler);
}
}
propertyChangeListener = null;
handler = null;
}
Initialize the JSpinner
border
,
foreground
, and background
, properties
based on the corresponding "Spinner.*" properties from defaults table.
The JSpinners
layout is set to the value returned by
createLayout
. This method is called by installUI
.
See Also:
/**
* Initialize the <code>JSpinner</code> <code>border</code>,
* <code>foreground</code>, and <code>background</code>, properties
* based on the corresponding "Spinner.*" properties from defaults table.
* The <code>JSpinners</code> layout is set to the value returned by
* <code>createLayout</code>. This method is called by <code>installUI</code>.
*
* @see #uninstallDefaults
* @see #installUI
* @see #createLayout
* @see LookAndFeel#installBorder
* @see LookAndFeel#installColors
*/
protected void installDefaults() {
spinner.setLayout(createLayout());
LookAndFeel.installBorder(spinner, "Spinner.border");
LookAndFeel.installColorsAndFont(spinner, "Spinner.background", "Spinner.foreground", "Spinner.font");
LookAndFeel.installProperty(spinner, "opaque", Boolean.TRUE);
JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.DefaultEditor) {
JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
if (tf != null) {
if (tf.getFont() instanceof UIResource) {
Font font = spinner.getFont();
tf.setFont(font == null ? null : new FontUIResource(font));
}
}
}
}
Sets the JSpinner's
layout manager to null. This
method is called by uninstallUI
.
See Also: - installDefaults
- uninstallUI
/**
* Sets the <code>JSpinner's</code> layout manager to null. This
* method is called by <code>uninstallUI</code>.
*
* @see #installDefaults
* @see #uninstallUI
*/
protected void uninstallDefaults() {
spinner.setLayout(null);
}
private Handler getHandler() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
Installs the necessary listeners on the next button, c
,
to update the JSpinner
in response to a user gesture.
Params: - c – Component to install the listeners on
Throws: - NullPointerException – if
c
is null.
See Also: Since: 1.5
/**
* Installs the necessary listeners on the next button, <code>c</code>,
* to update the <code>JSpinner</code> in response to a user gesture.
*
* @param c Component to install the listeners on
* @throws NullPointerException if <code>c</code> is null.
* @see #createNextButton
* @since 1.5
*/
protected void installNextButtonListeners(Component c) {
installButtonListeners(c, nextButtonHandler);
}
Installs the necessary listeners on the previous button, c
,
to update the JSpinner
in response to a user gesture.
Params: - c – Component to install the listeners on.
Throws: - NullPointerException – if
c
is null.
See Also: Since: 1.5
/**
* Installs the necessary listeners on the previous button, <code>c</code>,
* to update the <code>JSpinner</code> in response to a user gesture.
*
* @param c Component to install the listeners on.
* @throws NullPointerException if <code>c</code> is null.
* @see #createPreviousButton
* @since 1.5
*/
protected void installPreviousButtonListeners(Component c) {
installButtonListeners(c, previousButtonHandler);
}
private void installButtonListeners(Component c,
ArrowButtonHandler handler) {
if (c instanceof JButton) {
((JButton)c).addActionListener(handler);
}
c.addMouseListener(handler);
}
Creates a LayoutManager
that manages the editor
,
nextButton
, and previousButton
children of the JSpinner. These three children must be
added with a constraint that identifies their role:
"Editor", "Next", and "Previous". The default layout manager
can handle the absence of any of these children.
See Also: Returns: a LayoutManager for the editor, next button, and previous button.
/**
* Creates a <code>LayoutManager</code> that manages the <code>editor</code>,
* <code>nextButton</code>, and <code>previousButton</code>
* children of the JSpinner. These three children must be
* added with a constraint that identifies their role:
* "Editor", "Next", and "Previous". The default layout manager
* can handle the absence of any of these children.
*
* @return a LayoutManager for the editor, next button, and previous button.
* @see #createNextButton
* @see #createPreviousButton
* @see #createEditor
*/
protected LayoutManager createLayout() {
return getHandler();
}
Creates a PropertyChangeListener
that can be
added to the JSpinner itself. Typically, this listener
will call replaceEditor when the "editor" property changes,
since it's the SpinnerUI's
responsibility to
add the editor to the JSpinner (and remove the old one).
This method is called by installListeners
.
See Also: Returns: A PropertyChangeListener for the JSpinner itself
/**
* Creates a <code>PropertyChangeListener</code> that can be
* added to the JSpinner itself. Typically, this listener
* will call replaceEditor when the "editor" property changes,
* since it's the <code>SpinnerUI's</code> responsibility to
* add the editor to the JSpinner (and remove the old one).
* This method is called by <code>installListeners</code>.
*
* @return A PropertyChangeListener for the JSpinner itself
* @see #installListeners
*/
protected PropertyChangeListener createPropertyChangeListener() {
return getHandler();
}
Creates a decrement button, i.e. component that replaces the spinner
value with the object returned by spinner.getPreviousValue
.
By default the previousButton
is a JButton
. If the decrement button is not needed this method should return null
. See Also: Returns: a component that will replace the spinner's value with the previous value in the sequence, or null
/**
* Creates a decrement button, i.e. component that replaces the spinner
* value with the object returned by <code>spinner.getPreviousValue</code>.
* By default the <code>previousButton</code> is a {@code JButton}. If the
* decrement button is not needed this method should return {@code null}.
*
* @return a component that will replace the spinner's value with the
* previous value in the sequence, or {@code null}
* @see #installUI
* @see #createNextButton
* @see #installPreviousButtonListeners
*/
protected Component createPreviousButton() {
Component c = createArrowButton(SwingConstants.SOUTH);
c.setName("Spinner.previousButton");
installPreviousButtonListeners(c);
return c;
}
Creates an increment button, i.e. component that replaces the spinner
value with the object returned by spinner.getNextValue
.
By default the nextButton
is a JButton
. If the increment button is not needed this method should return null
. See Also: Returns: a component that will replace the spinner's value with the next value in the sequence, or null
/**
* Creates an increment button, i.e. component that replaces the spinner
* value with the object returned by <code>spinner.getNextValue</code>.
* By default the <code>nextButton</code> is a {@code JButton}. If the
* increment button is not needed this method should return {@code null}.
*
* @return a component that will replace the spinner's value with the
* next value in the sequence, or {@code null}
* @see #installUI
* @see #createPreviousButton
* @see #installNextButtonListeners
*/
protected Component createNextButton() {
Component c = createArrowButton(SwingConstants.NORTH);
c.setName("Spinner.nextButton");
installNextButtonListeners(c);
return c;
}
private Component createArrowButton(int direction) {
JButton b = new BasicArrowButton(direction);
Border buttonBorder = UIManager.getBorder("Spinner.arrowButtonBorder");
if (buttonBorder instanceof UIResource) {
// Wrap the border to avoid having the UIResource be replaced by
// the ButtonUI. This is the opposite of using BorderUIResource.
b.setBorder(new CompoundBorder(buttonBorder, null));
} else {
b.setBorder(buttonBorder);
}
b.setInheritsPopupMenu(true);
return b;
}
This method is called by installUI to get the editor component
of the JSpinner
. By default it just returns
JSpinner.getEditor()
. Subclasses can override
createEditor
to return a component that contains
the spinner's editor or null, if they're going to handle adding
the editor to the JSpinner
in an
installUI
override.
Typically this method would be overridden to wrap the editor
with a container with a custom border, since one can't assume
that the editors border can be set directly.
The replaceEditor
method is called when the spinners
editor is changed with JSpinner.setEditor
. If you've
overriden this method, then you'll probably want to override
replaceEditor
as well.
See Also: Returns: the JSpinners editor JComponent, spinner.getEditor() by default
/**
* This method is called by installUI to get the editor component
* of the <code>JSpinner</code>. By default it just returns
* <code>JSpinner.getEditor()</code>. Subclasses can override
* <code>createEditor</code> to return a component that contains
* the spinner's editor or null, if they're going to handle adding
* the editor to the <code>JSpinner</code> in an
* <code>installUI</code> override.
* <p>
* Typically this method would be overridden to wrap the editor
* with a container with a custom border, since one can't assume
* that the editors border can be set directly.
* <p>
* The <code>replaceEditor</code> method is called when the spinners
* editor is changed with <code>JSpinner.setEditor</code>. If you've
* overriden this method, then you'll probably want to override
* <code>replaceEditor</code> as well.
*
* @return the JSpinners editor JComponent, spinner.getEditor() by default
* @see #installUI
* @see #replaceEditor
* @see JSpinner#getEditor
*/
protected JComponent createEditor() {
JComponent editor = spinner.getEditor();
maybeRemoveEditorBorder(editor);
installEditorBorderListener(editor);
editor.setInheritsPopupMenu(true);
updateEditorAlignment(editor);
return editor;
}
Called by the PropertyChangeListener
when the
JSpinner
editor property changes. It's the responsibility
of this method to remove the old editor and add the new one. By
default this operation is just:
spinner.remove(oldEditor);
spinner.add(newEditor, "Editor");
The implementation of replaceEditor
should be coordinated
with the createEditor
method.
Params: - oldEditor – an old instance of editor
- newEditor – a new instance of editor
See Also:
/**
* Called by the <code>PropertyChangeListener</code> when the
* <code>JSpinner</code> editor property changes. It's the responsibility
* of this method to remove the old editor and add the new one. By
* default this operation is just:
* <pre>
* spinner.remove(oldEditor);
* spinner.add(newEditor, "Editor");
* </pre>
* The implementation of <code>replaceEditor</code> should be coordinated
* with the <code>createEditor</code> method.
*
* @param oldEditor an old instance of editor
* @param newEditor a new instance of editor
* @see #createEditor
* @see #createPropertyChangeListener
*/
protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
spinner.remove(oldEditor);
maybeRemoveEditorBorder(newEditor);
installEditorBorderListener(newEditor);
newEditor.setInheritsPopupMenu(true);
spinner.add(newEditor, "Editor");
}
private void updateEditorAlignment(JComponent editor) {
if (editor instanceof JSpinner.DefaultEditor) {
// if editor alignment isn't set in LAF, we get 0 (CENTER) here
int alignment = UIManager.getInt("Spinner.editorAlignment");
JTextField text = ((JSpinner.DefaultEditor)editor).getTextField();
text.setHorizontalAlignment(alignment);
}
}
Remove the border around the inner editor component for LaFs
that install an outside border around the spinner,
/**
* Remove the border around the inner editor component for LaFs
* that install an outside border around the spinner,
*/
private void maybeRemoveEditorBorder(JComponent editor) {
if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
if (editor instanceof JPanel &&
editor.getBorder() == null &&
editor.getComponentCount() > 0) {
editor = (JComponent)editor.getComponent(0);
}
if (editor != null && editor.getBorder() instanceof UIResource) {
editor.setBorder(null);
}
}
}
Remove the border around the inner editor component for LaFs
that install an outside border around the spinner,
/**
* Remove the border around the inner editor component for LaFs
* that install an outside border around the spinner,
*/
private void installEditorBorderListener(JComponent editor) {
if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
if (editor instanceof JPanel &&
editor.getBorder() == null &&
editor.getComponentCount() > 0) {
editor = (JComponent)editor.getComponent(0);
}
if (editor != null &&
(editor.getBorder() == null ||
editor.getBorder() instanceof UIResource)) {
editor.addPropertyChangeListener(getHandler());
}
}
}
private void removeEditorBorderListener(JComponent editor) {
if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
if (editor instanceof JPanel &&
editor.getComponentCount() > 0) {
editor = (JComponent)editor.getComponent(0);
}
if (editor != null) {
editor.removePropertyChangeListener(getHandler());
}
}
}
Updates the enabled state of the children Components based on the
enabled state of the JSpinner
.
/**
* Updates the enabled state of the children Components based on the
* enabled state of the <code>JSpinner</code>.
*/
private void updateEnabledState() {
updateEnabledState(spinner, spinner.isEnabled());
}
Recursively updates the enabled state of the child
Component
s of c
.
/**
* Recursively updates the enabled state of the child
* <code>Component</code>s of <code>c</code>.
*/
private void updateEnabledState(Container c, boolean enabled) {
for (int counter = c.getComponentCount() - 1; counter >= 0;counter--) {
Component child = c.getComponent(counter);
if (DefaultLookup.getBoolean(spinner, this,
"Spinner.disableOnBoundaryValues", false)) {
SpinnerModel model = spinner.getModel();
if (child.getName() == "Spinner.nextButton" &&
model.getNextValue() == null) {
child.setEnabled(false);
}
else if (child.getName() == "Spinner.previousButton" &&
model.getPreviousValue() == null) {
child.setEnabled(false);
}
else {
child.setEnabled(enabled);
}
}
else {
child.setEnabled(enabled);
}
if (child instanceof Container) {
updateEnabledState((Container)child, enabled);
}
}
}
Installs the keyboard Actions onto the JSpinner.
Since: 1.5
/**
* Installs the keyboard Actions onto the JSpinner.
*
* @since 1.5
*/
protected void installKeyboardActions() {
InputMap iMap = getInputMap(JComponent.
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
SwingUtilities.replaceUIInputMap(spinner, JComponent.
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
iMap);
LazyActionMap.installLazyActionMap(spinner, BasicSpinnerUI.class,
"Spinner.actionMap");
}
Returns the InputMap to install for condition
.
/**
* Returns the InputMap to install for <code>condition</code>.
*/
private InputMap getInputMap(int condition) {
if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
return (InputMap)DefaultLookup.get(spinner, this,
"Spinner.ancestorInputMap");
}
return null;
}
static void loadActionMap(LazyActionMap map) {
map.put("increment", nextButtonHandler);
map.put("decrement", previousButtonHandler);
}
Returns the baseline.
Throws: - NullPointerException – {@inheritDoc}
- IllegalArgumentException – {@inheritDoc}
See Also: Since: 1.6
/**
* Returns the baseline.
*
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @see javax.swing.JComponent#getBaseline(int, int)
* @since 1.6
*/
public int getBaseline(JComponent c, int width, int height) {
super.getBaseline(c, width, height);
JComponent editor = spinner.getEditor();
Insets insets = spinner.getInsets();
width = width - insets.left - insets.right;
height = height - insets.top - insets.bottom;
if (width >= 0 && height >= 0) {
int baseline = editor.getBaseline(width, height);
if (baseline >= 0) {
return insets.top + baseline;
}
}
return -1;
}
Returns an enum indicating how the baseline of the component
changes as the size changes.
Throws: - NullPointerException – {@inheritDoc}
See Also: Since: 1.6
/**
* Returns an enum indicating how the baseline of the component
* changes as the size changes.
*
* @throws NullPointerException {@inheritDoc}
* @see javax.swing.JComponent#getBaseline(int, int)
* @since 1.6
*/
public Component.BaselineResizeBehavior getBaselineResizeBehavior(
JComponent c) {
super.getBaselineResizeBehavior(c);
return spinner.getEditor().getBaselineResizeBehavior();
}
A handler for spinner arrow button mouse and action events. When
a left mouse pressed event occurs we look up the (enabled) spinner
that's the source of the event and start the autorepeat timer. The
timer fires action events until any button is released at which
point the timer is stopped and the reference to the spinner cleared.
The timer doesn't start until after a 300ms delay, so often the
source of the initial (and final) action event is just the button
logic for mouse released - which means that we're relying on the fact
that our mouse listener runs after the buttons mouse listener.
Note that one instance of this handler is shared by all slider previous
arrow buttons and likewise for all of the next buttons,
so it doesn't have any state that persists beyond the limits
of a single button pressed/released gesture.
/**
* A handler for spinner arrow button mouse and action events. When
* a left mouse pressed event occurs we look up the (enabled) spinner
* that's the source of the event and start the autorepeat timer. The
* timer fires action events until any button is released at which
* point the timer is stopped and the reference to the spinner cleared.
* The timer doesn't start until after a 300ms delay, so often the
* source of the initial (and final) action event is just the button
* logic for mouse released - which means that we're relying on the fact
* that our mouse listener runs after the buttons mouse listener.
* <p>
* Note that one instance of this handler is shared by all slider previous
* arrow buttons and likewise for all of the next buttons,
* so it doesn't have any state that persists beyond the limits
* of a single button pressed/released gesture.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
private static class ArrowButtonHandler extends AbstractAction
implements FocusListener, MouseListener, UIResource {
final javax.swing.Timer autoRepeatTimer;
final boolean isNext;
JSpinner spinner = null;
JButton arrowButton = null;
ArrowButtonHandler(String name, boolean isNext) {
super(name);
this.isNext = isNext;
autoRepeatTimer = new javax.swing.Timer(60, this);
autoRepeatTimer.setInitialDelay(300);
}
private JSpinner eventToSpinner(AWTEvent e) {
Object src = e.getSource();
while ((src instanceof Component) && !(src instanceof JSpinner)) {
src = ((Component)src).getParent();
}
return (src instanceof JSpinner) ? (JSpinner)src : null;
}
public void actionPerformed(ActionEvent e) {
JSpinner spinner = this.spinner;
if (!(e.getSource() instanceof javax.swing.Timer)) {
// Most likely resulting from being in ActionMap.
spinner = eventToSpinner(e);
if (e.getSource() instanceof JButton) {
arrowButton = (JButton)e.getSource();
}
} else {
if (arrowButton!=null && !arrowButton.getModel().isPressed()
&& autoRepeatTimer.isRunning()) {
autoRepeatTimer.stop();
spinner = null;
arrowButton = null;
}
}
if (spinner != null) {
try {
int calendarField = getCalendarField(spinner);
spinner.commitEdit();
if (calendarField != -1) {
((SpinnerDateModel)spinner.getModel()).
setCalendarField(calendarField);
}
Object value = (isNext) ? spinner.getNextValue() :
spinner.getPreviousValue();
if (value != null) {
spinner.setValue(value);
select(spinner);
}
} catch (IllegalArgumentException iae) {
UIManager.getLookAndFeel().provideErrorFeedback(spinner);
} catch (ParseException pe) {
UIManager.getLookAndFeel().provideErrorFeedback(spinner);
}
}
}
If the spinner's editor is a DateEditor, this selects the field
associated with the value that is being incremented.
/**
* If the spinner's editor is a DateEditor, this selects the field
* associated with the value that is being incremented.
*/
private void select(JSpinner spinner) {
JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.DateEditor) {
JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
JFormattedTextField ftf = dateEditor.getTextField();
Format format = dateEditor.getFormat();
Object value;
if (format != null && (value = spinner.getValue()) != null) {
SpinnerDateModel model = dateEditor.getModel();
DateFormat.Field field = DateFormat.Field.ofCalendarField(
model.getCalendarField());
if (field != null) {
try {
AttributedCharacterIterator iterator = format.
formatToCharacterIterator(value);
if (!select(ftf, iterator, field) &&
field == DateFormat.Field.HOUR0) {
select(ftf, iterator, DateFormat.Field.HOUR1);
}
}
catch (IllegalArgumentException iae) {}
}
}
}
}
Selects the passed in field, returning true if it is found,
false otherwise.
/**
* Selects the passed in field, returning true if it is found,
* false otherwise.
*/
private boolean select(JFormattedTextField ftf,
AttributedCharacterIterator iterator,
DateFormat.Field field) {
int max = ftf.getDocument().getLength();
iterator.first();
do {
Map<?, ?> attrs = iterator.getAttributes();
if (attrs != null && attrs.containsKey(field)){
int start = iterator.getRunStart(field);
int end = iterator.getRunLimit(field);
if (start != -1 && end != -1 && start <= max &&
end <= max) {
ftf.select(start, end);
}
return true;
}
} while (iterator.next() != CharacterIterator.DONE);
return false;
}
Returns the calendarField under the start of the selection, or
-1 if there is no valid calendar field under the selection (or
the spinner isn't editing dates.
/**
* Returns the calendarField under the start of the selection, or
* -1 if there is no valid calendar field under the selection (or
* the spinner isn't editing dates.
*/
private int getCalendarField(JSpinner spinner) {
JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.DateEditor) {
JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
JFormattedTextField ftf = dateEditor.getTextField();
int start = ftf.getSelectionStart();
JFormattedTextField.AbstractFormatter formatter =
ftf.getFormatter();
if (formatter instanceof InternationalFormatter) {
Format.Field[] fields = ((InternationalFormatter)
formatter).getFields(start);
for (int counter = 0; counter < fields.length; counter++) {
if (fields[counter] instanceof DateFormat.Field) {
int calendarField;
if (fields[counter] == DateFormat.Field.HOUR1) {
calendarField = Calendar.HOUR;
}
else {
calendarField = ((DateFormat.Field)
fields[counter]).getCalendarField();
}
if (calendarField != -1) {
return calendarField;
}
}
}
}
}
return -1;
}
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e) && e.getComponent().isEnabled()) {
spinner = eventToSpinner(e);
autoRepeatTimer.start();
focusSpinnerIfNecessary();
}
}
public void mouseReleased(MouseEvent e) {
autoRepeatTimer.stop();
arrowButton = null;
spinner = null;
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
if (spinner != null && !autoRepeatTimer.isRunning() && spinner == eventToSpinner(e)) {
autoRepeatTimer.start();
}
}
public void mouseExited(MouseEvent e) {
if (autoRepeatTimer.isRunning()) {
autoRepeatTimer.stop();
}
}
Requests focus on a child of the spinner if the spinner doesn't
have focus.
/**
* Requests focus on a child of the spinner if the spinner doesn't
* have focus.
*/
private void focusSpinnerIfNecessary() {
Component fo = KeyboardFocusManager.
getCurrentKeyboardFocusManager().getFocusOwner();
if (spinner.isRequestFocusEnabled() && (
fo == null ||
!SwingUtilities.isDescendingFrom(fo, spinner))) {
Container root = spinner;
if (!root.isFocusCycleRoot()) {
root = root.getFocusCycleRootAncestor();
}
if (root != null) {
FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
Component child = ftp.getComponentAfter(root, spinner);
if (child != null && SwingUtilities.isDescendingFrom(
child, spinner)) {
child.requestFocus();
}
}
}
}
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
if (spinner == eventToSpinner(e)) {
if (autoRepeatTimer.isRunning()) {
autoRepeatTimer.stop();
}
spinner = null;
if (arrowButton != null) {
ButtonModel model = arrowButton.getModel();
model.setPressed(false);
model.setArmed(false);
arrowButton = null;
}
}
}
}
private static class Handler implements LayoutManager,
PropertyChangeListener, ChangeListener {
//
// LayoutManager
//
private Component nextButton = null;
private Component previousButton = null;
private Component editor = null;
public void addLayoutComponent(String name, Component c) {
if ("Next".equals(name)) {
nextButton = c;
}
else if ("Previous".equals(name)) {
previousButton = c;
}
else if ("Editor".equals(name)) {
editor = c;
}
}
public void removeLayoutComponent(Component c) {
if (c == nextButton) {
nextButton = null;
}
else if (c == previousButton) {
previousButton = null;
}
else if (c == editor) {
editor = null;
}
}
private Dimension preferredSize(Component c) {
return (c == null) ? zeroSize : c.getPreferredSize();
}
public Dimension preferredLayoutSize(Container parent) {
Dimension nextD = preferredSize(nextButton);
Dimension previousD = preferredSize(previousButton);
Dimension editorD = preferredSize(editor);
/* Force the editors height to be a multiple of 2
*/
editorD.height = ((editorD.height + 1) / 2) * 2;
Dimension size = new Dimension(editorD.width, editorD.height);
size.width += Math.max(nextD.width, previousD.width);
Insets insets = parent.getInsets();
size.width += insets.left + insets.right;
size.height += insets.top + insets.bottom;
return size;
}
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
private void setBounds(Component c, int x, int y, int width, int height) {
if (c != null) {
c.setBounds(x, y, width, height);
}
}
public void layoutContainer(Container parent) {
int width = parent.getWidth();
int height = parent.getHeight();
Insets insets = parent.getInsets();
if (nextButton == null && previousButton == null) {
setBounds(editor, insets.left, insets.top, width - insets.left - insets.right,
height - insets.top - insets.bottom);
return;
}
Dimension nextD = preferredSize(nextButton);
Dimension previousD = preferredSize(previousButton);
int buttonsWidth = Math.max(nextD.width, previousD.width);
int editorHeight = height - (insets.top + insets.bottom);
// The arrowButtonInsets value is used instead of the JSpinner's
// insets if not null. Defining this to be (0, 0, 0, 0) causes the
// buttons to be aligned with the outer edge of the spinner's
// border, and leaving it as "null" places the buttons completely
// inside the spinner's border.
Insets buttonInsets = UIManager.getInsets("Spinner.arrowButtonInsets");
if (buttonInsets == null) {
buttonInsets = insets;
}
/* Deal with the spinner's componentOrientation property.
*/
int editorX, editorWidth, buttonsX;
if (parent.getComponentOrientation().isLeftToRight()) {
editorX = insets.left;
editorWidth = width - insets.left - buttonsWidth - buttonInsets.right;
buttonsX = width - buttonsWidth - buttonInsets.right;
} else {
buttonsX = buttonInsets.left;
editorX = buttonsX + buttonsWidth;
editorWidth = width - buttonInsets.left - buttonsWidth - insets.right;
}
int nextY = buttonInsets.top;
int nextHeight = (height / 2) + (height % 2) - nextY;
int previousY = buttonInsets.top + nextHeight;
int previousHeight = height - previousY - buttonInsets.bottom;
setBounds(editor, editorX, insets.top, editorWidth, editorHeight);
setBounds(nextButton, buttonsX, nextY, buttonsWidth, nextHeight);
setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight);
}
//
// PropertyChangeListener
//
public void propertyChange(PropertyChangeEvent e)
{
String propertyName = e.getPropertyName();
if (e.getSource() instanceof JSpinner) {
JSpinner spinner = (JSpinner)(e.getSource());
SpinnerUI spinnerUI = spinner.getUI();
if (spinnerUI instanceof BasicSpinnerUI) {
BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
if ("editor".equals(propertyName)) {
JComponent oldEditor = (JComponent)e.getOldValue();
JComponent newEditor = (JComponent)e.getNewValue();
ui.replaceEditor(oldEditor, newEditor);
ui.updateEnabledState();
if (oldEditor instanceof JSpinner.DefaultEditor) {
JTextField tf =
((JSpinner.DefaultEditor)oldEditor).getTextField();
if (tf != null) {
tf.removeFocusListener(nextButtonHandler);
tf.removeFocusListener(previousButtonHandler);
}
}
if (newEditor instanceof JSpinner.DefaultEditor) {
JTextField tf =
((JSpinner.DefaultEditor)newEditor).getTextField();
if (tf != null) {
if (tf.getFont() instanceof UIResource) {
Font font = spinner.getFont();
tf.setFont(font == null ? null : new FontUIResource(font));
}
tf.addFocusListener(nextButtonHandler);
tf.addFocusListener(previousButtonHandler);
}
}
}
else if ("enabled".equals(propertyName) ||
"model".equals(propertyName)) {
ui.updateEnabledState();
}
else if ("font".equals(propertyName)) {
JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.DefaultEditor) {
JTextField tf =
((JSpinner.DefaultEditor)editor).getTextField();
if (tf != null) {
if (tf.getFont() instanceof UIResource) {
Font font = spinner.getFont();
tf.setFont(font == null ? null : new FontUIResource(font));
}
}
}
}
else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) {
updateToolTipTextForChildren(spinner);
} else if ("componentOrientation".equals(propertyName)) {
ComponentOrientation o
= (ComponentOrientation) e.getNewValue();
if (o != (ComponentOrientation) e.getOldValue()) {
JComponent editor = spinner.getEditor();
if (editor != null) {
editor.applyComponentOrientation(o);
}
spinner.revalidate();
spinner.repaint();
}
}
}
} else if (e.getSource() instanceof JComponent) {
JComponent c = (JComponent)e.getSource();
if ((c.getParent() instanceof JPanel) &&
(c.getParent().getParent() instanceof JSpinner) &&
"border".equals(propertyName)) {
JSpinner spinner = (JSpinner)c.getParent().getParent();
SpinnerUI spinnerUI = spinner.getUI();
if (spinnerUI instanceof BasicSpinnerUI) {
BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
ui.maybeRemoveEditorBorder(c);
}
}
}
}
// Syncronizes the ToolTip text for the components within the spinner
// to be the same value as the spinner ToolTip text.
private void updateToolTipTextForChildren(JComponent spinner) {
String toolTipText = spinner.getToolTipText();
Component[] children = spinner.getComponents();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof JSpinner.DefaultEditor) {
JTextField tf = ((JSpinner.DefaultEditor)children[i]).getTextField();
if (tf != null) {
tf.setToolTipText(toolTipText);
}
} else if (children[i] instanceof JComponent) {
((JComponent)children[i]).setToolTipText( spinner.getToolTipText() );
}
}
}
public void stateChanged(ChangeEvent e) {
if (e.getSource() instanceof JSpinner) {
JSpinner spinner = (JSpinner)e.getSource();
SpinnerUI spinnerUI = spinner.getUI();
if (DefaultLookup.getBoolean(spinner, spinnerUI,
"Spinner.disableOnBoundaryValues", false) &&
spinnerUI instanceof BasicSpinnerUI) {
BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
ui.updateEnabledState();
}
}
}
}
}