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
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 * @param oldEditor an old instance of editor
404 * @param newEditor a new instance of editor
405 * @see #createEditor
406 * @see #createPropertyChangeListener
407 */
408 protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
409 spinner.remove(oldEditor);
410 maybeRemoveEditorBorder(newEditor);
411 installEditorBorderListener(newEditor);
412 newEditor.setInheritsPopupMenu(true);
413 spinner.add(newEditor, "Editor");
414 }
415
416 private void updateEditorAlignment(JComponent editor) {
417 if (editor instanceof JSpinner.DefaultEditor) {
418 // if editor alignment isn't set in LAF, we get 0 (CENTER) here
419 int alignment = UIManager.getInt("Spinner.editorAlignment");
420 JTextField text = ((JSpinner.DefaultEditor)editor).getTextField();
421 text.setHorizontalAlignment(alignment);
460 }
461 }
462 }
463
464 private void removeEditorBorderListener(JComponent editor) {
465 if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
466 if (editor instanceof JPanel &&
467 editor.getComponentCount() > 0) {
468
469 editor = (JComponent)editor.getComponent(0);
470 }
471 if (editor != null) {
472 editor.removePropertyChangeListener(getHandler());
473 }
474 }
475 }
476
477
478 /**
479 * Updates the enabled state of the children Components based on the
480 * enabled state of the <code>JSpinner</code>.
481 */
482 private void updateEnabledState() {
483 updateEnabledState(spinner, spinner.isEnabled());
484 }
485
486
487 /**
488 * Recursively updates the enabled state of the child
489 * <code>Component</code>s of <code>c</code>.
490 */
491 private void updateEnabledState(Container c, boolean enabled) {
492 for (int counter = c.getComponentCount() - 1; counter >= 0;counter--) {
493 Component child = c.getComponent(counter);
494
495 if (DefaultLookup.getBoolean(spinner, this,
496 "Spinner.disableOnBoundaryValues", false)) {
497 SpinnerModel model = spinner.getModel();
498 if (child.getName() == "Spinner.nextButton" &&
499 model.getNextValue() == null) {
500 child.setEnabled(false);
501 }
502 else if (child.getName() == "Spinner.previousButton" &&
503 model.getPreviousValue() == null) {
504 child.setEnabled(false);
505 }
506 else {
507 child.setEnabled(enabled);
508 }
509 }
518
519
520 /**
521 * Installs the keyboard Actions onto the JSpinner.
522 *
523 * @since 1.5
524 */
525 protected void installKeyboardActions() {
526 InputMap iMap = getInputMap(JComponent.
527 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
528
529 SwingUtilities.replaceUIInputMap(spinner, JComponent.
530 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
531 iMap);
532
533 LazyActionMap.installLazyActionMap(spinner, BasicSpinnerUI.class,
534 "Spinner.actionMap");
535 }
536
537 /**
538 * Returns the InputMap to install for <code>condition</code>.
539 */
540 private InputMap getInputMap(int condition) {
541 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
542 return (InputMap)DefaultLookup.get(spinner, this,
543 "Spinner.ancestorInputMap");
544 }
545 return null;
546 }
547
548 static void loadActionMap(LazyActionMap map) {
549 map.put("increment", nextButtonHandler);
550 map.put("decrement", previousButtonHandler);
551 }
552
553 /**
554 * Returns the baseline.
555 *
556 * @throws NullPointerException {@inheritDoc}
557 * @throws IllegalArgumentException {@inheritDoc}
558 * @see javax.swing.JComponent#getBaseline(int, int)
|
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} method, and reset to null
55 * by {@code uninstallUI}.
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
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 installListeners},
106 * and then adds the components returned by {@code createNextButton},
107 * {@code createPreviousButton}, and {@code createEditor}.
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 uninstallListeners},
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} with
144 * a shared object that delegates interesting PropertyChangeEvents
145 * to protected methods.
146 * <p>
147 * This method is called by {@code installUI}.
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} added
172 * by installListeners.
173 * <p>
174 * This method is called by {@code uninstallUI}.
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 border},
197 * {@code foreground}, and {@code background}, properties
198 * based on the corresponding "Spinner.*" properties from defaults table.
199 * The {@code JSpinners} layout is set to the value returned by
200 * {@code createLayout}. This method is called by {@code installUI}.
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} layout manager to null. This
218 * method is called by {@code uninstallUI}.
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},
238 * to update the {@code JSpinner} in response to a user gesture.
239 *
240 * @param c Component to install the listeners on
241 * @throws NullPointerException if {@code c} 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},
251 * to update the {@code JSpinner} in response to a user gesture.
252 *
253 * @param c Component to install the listeners on.
254 * @throws NullPointerException if {@code c} 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} that manages the {@code editor},
272 * {@code nextButton}, and {@code previousButton}
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} 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} responsibility to
293 * add the editor to the JSpinner (and remove the old one).
294 * This method is called by {@code installListeners}.
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}.
307 * By default the {@code previousButton} 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}.
327 * By default the {@code nextButton} 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}. By default it just returns
361 * {@code JSpinner.getEditor()}. Subclasses can override
362 * {@code createEditor} 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} in an
365 * {@code installUI} 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} method is called when the spinners
372 * editor is changed with {@code JSpinner.setEditor}. If you've
373 * overriden this method, then you'll probably want to override
374 * {@code replaceEditor} 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} when the
393 * {@code JSpinner} 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} should be coordinated
401 * with the {@code createEditor} method.
402 *
403 * @param oldEditor an old instance of editor
404 * @param newEditor a new instance of editor
405 * @see #createEditor
406 * @see #createPropertyChangeListener
407 */
408 protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
409 spinner.remove(oldEditor);
410 maybeRemoveEditorBorder(newEditor);
411 installEditorBorderListener(newEditor);
412 newEditor.setInheritsPopupMenu(true);
413 spinner.add(newEditor, "Editor");
414 }
415
416 private void updateEditorAlignment(JComponent editor) {
417 if (editor instanceof JSpinner.DefaultEditor) {
418 // if editor alignment isn't set in LAF, we get 0 (CENTER) here
419 int alignment = UIManager.getInt("Spinner.editorAlignment");
420 JTextField text = ((JSpinner.DefaultEditor)editor).getTextField();
421 text.setHorizontalAlignment(alignment);
460 }
461 }
462 }
463
464 private void removeEditorBorderListener(JComponent editor) {
465 if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
466 if (editor instanceof JPanel &&
467 editor.getComponentCount() > 0) {
468
469 editor = (JComponent)editor.getComponent(0);
470 }
471 if (editor != null) {
472 editor.removePropertyChangeListener(getHandler());
473 }
474 }
475 }
476
477
478 /**
479 * Updates the enabled state of the children Components based on the
480 * enabled state of the {@code JSpinner}.
481 */
482 private void updateEnabledState() {
483 updateEnabledState(spinner, spinner.isEnabled());
484 }
485
486
487 /**
488 * Recursively updates the enabled state of the child
489 * {@code Component}s of {@code c}.
490 */
491 private void updateEnabledState(Container c, boolean enabled) {
492 for (int counter = c.getComponentCount() - 1; counter >= 0;counter--) {
493 Component child = c.getComponent(counter);
494
495 if (DefaultLookup.getBoolean(spinner, this,
496 "Spinner.disableOnBoundaryValues", false)) {
497 SpinnerModel model = spinner.getModel();
498 if (child.getName() == "Spinner.nextButton" &&
499 model.getNextValue() == null) {
500 child.setEnabled(false);
501 }
502 else if (child.getName() == "Spinner.previousButton" &&
503 model.getPreviousValue() == null) {
504 child.setEnabled(false);
505 }
506 else {
507 child.setEnabled(enabled);
508 }
509 }
518
519
520 /**
521 * Installs the keyboard Actions onto the JSpinner.
522 *
523 * @since 1.5
524 */
525 protected void installKeyboardActions() {
526 InputMap iMap = getInputMap(JComponent.
527 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
528
529 SwingUtilities.replaceUIInputMap(spinner, JComponent.
530 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
531 iMap);
532
533 LazyActionMap.installLazyActionMap(spinner, BasicSpinnerUI.class,
534 "Spinner.actionMap");
535 }
536
537 /**
538 * Returns the InputMap to install for {@code condition}.
539 */
540 private InputMap getInputMap(int condition) {
541 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
542 return (InputMap)DefaultLookup.get(spinner, this,
543 "Spinner.ancestorInputMap");
544 }
545 return null;
546 }
547
548 static void loadActionMap(LazyActionMap map) {
549 map.put("increment", nextButtonHandler);
550 map.put("decrement", previousButtonHandler);
551 }
552
553 /**
554 * Returns the baseline.
555 *
556 * @throws NullPointerException {@inheritDoc}
557 * @throws IllegalArgumentException {@inheritDoc}
558 * @see javax.swing.JComponent#getBaseline(int, int)
|