1. /*
  2. * @(#)BasicSpinnerUI.java 1.15 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.text.ParseException;
  11. import javax.swing.*;
  12. import javax.swing.border.*;
  13. import javax.swing.event.*;
  14. import javax.swing.plaf.*;
  15. import javax.swing.text.*;
  16. import java.beans.*;
  17. import java.text.*;
  18. import java.util.*;
  19. /**
  20. * The default Spinner UI delegate.
  21. *
  22. * @version 1.15 01/23/03
  23. * @author Hans Muller
  24. * @since 1.4
  25. */
  26. public class BasicSpinnerUI extends SpinnerUI
  27. {
  28. /**
  29. * The spinner that we're a UI delegate for. Initialized by
  30. * the <code>installUI</code> method, and reset to null
  31. * by <code>uninstallUI</code>.
  32. *
  33. * @see #installUI
  34. * @see #uninstallUI
  35. */
  36. protected JSpinner spinner;
  37. /**
  38. * The <code>PropertyChangeListener</code> that's added to the
  39. * <code>JSpinner</code> itself. This listener is created by the
  40. * <code>createPropertyChangeListener</code> method, added by the
  41. * <code>installListeners</code> method, and removed by the
  42. * <code>uninstallListeners</code> method.
  43. * <p>
  44. * One instance of this listener is shared by all JSpinners.
  45. *
  46. * @see #createPropertyChangeListener
  47. * @see #installListeners
  48. * @see #uninstallListeners
  49. */
  50. private static final PropertyChangeListener propertyChangeListener = new PropertyChangeHandler();
  51. /**
  52. * The mouse/action listeners that are added to the spinner's
  53. * arrow buttons. These listeners are shared by all
  54. * spinner arrow buttons.
  55. *
  56. * @see #createNextButton
  57. * @see #createPreviousButton
  58. */
  59. private static final ArrowButtonHandler nextButtonHandler = new ArrowButtonHandler("increment", true);
  60. private static final ArrowButtonHandler previousButtonHandler = new ArrowButtonHandler("decrement", false);
  61. /**
  62. * Used by the default LayoutManager class - SpinnerLayout for
  63. * missing (null) editor/nextButton/previousButton children.
  64. */
  65. private static final Dimension zeroSize = new Dimension(0, 0);
  66. /**
  67. * Returns a new instance of BasicSpinnerUI. SpinnerListUI
  68. * delegates are allocated one per JSpinner.
  69. *
  70. * @param c the JSpinner (not used)
  71. * @see ComponentUI#createUI
  72. * @return a new BasicSpinnerUI object
  73. */
  74. public static ComponentUI createUI(JComponent c) {
  75. return new BasicSpinnerUI();
  76. }
  77. private void maybeAdd(Component c, String s) {
  78. if (c != null) {
  79. spinner.add(c, s);
  80. }
  81. }
  82. /**
  83. * Calls <code>installDefaults</code>, <code>installListeners</code>,
  84. * and then adds the components returned by <code>createNextButton</code>,
  85. * <code>createPreviousButton</code>, and <code>createEditor</code>.
  86. *
  87. * @param c the JSpinner
  88. * @see #installDefaults
  89. * @see #installListeners
  90. * @see #createNextButton
  91. * @see #createPreviousButton
  92. * @see #createEditor
  93. */
  94. public void installUI(JComponent c) {
  95. this.spinner = (JSpinner)c;
  96. installDefaults();
  97. installListeners();
  98. maybeAdd(createNextButton(), "Next");
  99. maybeAdd(createPreviousButton(), "Previous");
  100. maybeAdd(createEditor(), "Editor");
  101. updateEnabledState();
  102. installKeyboardActions();
  103. }
  104. /**
  105. * Calls <code>uninstallDefaults</code>, <code>uninstallListeners</code>,
  106. * and then removes all of the spinners children.
  107. *
  108. * @param c the JSpinner (not used)
  109. */
  110. public void uninstallUI(JComponent c) {
  111. uninstallDefaults();
  112. uninstallListeners();
  113. this.spinner = null;
  114. c.removeAll();
  115. }
  116. /**
  117. * Initializes <code>propertyChangeListener</code> with
  118. * a shared object that delegates interesting PropertyChangeEvents
  119. * to protected methods.
  120. * <p>
  121. * This method is called by <code>installUI</code>.
  122. *
  123. * @see #replaceEditor
  124. * @see #uninstallListeners
  125. */
  126. protected void installListeners() {
  127. spinner.addPropertyChangeListener(propertyChangeListener);
  128. }
  129. /**
  130. * Removes the <code>propertyChangeListener</code> added
  131. * by installListeners.
  132. * <p>
  133. * This method is called by <code>uninstallUI</code>.
  134. *
  135. * @see #installListeners
  136. */
  137. protected void uninstallListeners() {
  138. spinner.removePropertyChangeListener(propertyChangeListener);
  139. removeEditorBorderListener(spinner.getEditor());
  140. }
  141. /**
  142. * Initialize the <code>JSpinner</code> <code>border</code>,
  143. * <code>foreground</code>, and <code>background</code>, properties
  144. * based on the corresponding "Spinner.*" properties from defaults table.
  145. * The <code>JSpinners</code> layout is set to the value returned by
  146. * <code>createLayout</code>. This method is called by <code>installUI</code>.
  147. *
  148. * @see #uninstallDefaults
  149. * @see #installUI
  150. * @see #createLayout
  151. * @see LookAndFeel#installBorder
  152. * @see LookAndFeel#installColors
  153. */
  154. protected void installDefaults() {
  155. spinner.setLayout(createLayout());
  156. LookAndFeel.installBorder(spinner, "Spinner.border");
  157. LookAndFeel.installColorsAndFont(spinner, "Spinner.background", "Spinner.foreground", "Spinner.font");
  158. }
  159. /**
  160. * Sets the <code>JSpinner's</code> layout manager to null. This
  161. * method is called by <code>uninstallUI</code>.
  162. *
  163. * @see #installDefaults
  164. * @see #uninstallUI
  165. */
  166. protected void uninstallDefaults() {
  167. spinner.setLayout(null);
  168. }
  169. /**
  170. * Create a <code>LayoutManager</code> that manages the <code>editor</code>,
  171. * <code>nextButton</code>, and <code>previousButton</code>
  172. * children of the JSpinner. These three children must be
  173. * added with a constraint that identifies their role:
  174. * "Editor", "Next", and "Previous". The default layout manager
  175. * can handle the absence of any of these children.
  176. *
  177. * @return a LayoutManager for the editor, next button, and previous button.
  178. * @see #createNextButton
  179. * @see #createPreviousButton
  180. * @see #createEditor
  181. */
  182. protected LayoutManager createLayout() {
  183. return new SpinnerLayout();
  184. }
  185. /**
  186. * Create a <code>PropertyChangeListener</code> that can be
  187. * added to the JSpinner itself. Typically, this listener
  188. * will call replaceEditor when the "editor" property changes,
  189. * since it's the <code>SpinnerUI's</code> responsibility to
  190. * add the editor to the JSpinner (and remove the old one).
  191. * This method is called by <code>installListeners</code>.
  192. *
  193. * @return A PropertyChangeListener for the JSpinner itself
  194. * @see #installListeners
  195. */
  196. protected PropertyChangeListener createPropertyChangeListener() {
  197. return new PropertyChangeHandler();
  198. }
  199. /**
  200. * Create a component that will replace the spinner models value
  201. * with the object returned by <code>spinner.getPreviousValue</code>.
  202. * By default the <code>previousButton</code> is a JButton
  203. * who's <code>ActionListener</code> updates it's <code>JSpinner</code>
  204. * ancestors model. If a previousButton isn't needed (in a subclass)
  205. * then override this method to return null.
  206. *
  207. * @return a component that will replace the spinners model with the
  208. * next value in the sequence, or null
  209. * @see #installUI
  210. * @see #createNextButton
  211. */
  212. protected Component createPreviousButton() {
  213. return createArrowButton(SwingConstants.SOUTH, previousButtonHandler);
  214. }
  215. /**
  216. * Create a component that will replace the spinner models value
  217. * with the object returned by <code>spinner.getNextValue</code>.
  218. * By default the <code>nextButton</code> is a JButton
  219. * who's <code>ActionListener</code> updates it's <code>JSpinner</code>
  220. * ancestors model. If a nextButton isn't needed (in a subclass)
  221. * then override this method to return null.
  222. *
  223. * @return a component that will replace the spinners model with the
  224. * next value in the sequence, or null
  225. * @see #installUI
  226. * @see #createPreviousButton
  227. */
  228. protected Component createNextButton() {
  229. return createArrowButton(SwingConstants.NORTH, nextButtonHandler);
  230. }
  231. private Component createArrowButton(int direction, ArrowButtonHandler handler) {
  232. JButton b = new BasicArrowButton(direction);
  233. b.addActionListener(handler);
  234. b.addMouseListener(handler);
  235. Border buttonBorder = UIManager.getBorder("Spinner.arrowButtonBorder");
  236. if (buttonBorder instanceof UIResource) {
  237. // Wrap the border to avoid having the UIResource be replaced by
  238. // the ButtonUI. This is the opposite of using BorderUIResource.
  239. b.setBorder(new CompoundBorder(buttonBorder, null));
  240. } else {
  241. b.setBorder(buttonBorder);
  242. }
  243. return b;
  244. }
  245. /**
  246. * This method is called by installUI to get the editor component
  247. * of the <code>JSpinner</code>. By default it just returns
  248. * <code>JSpinner.getEditor()</code>. Subclasses can override
  249. * <code>createEditor</code> to return a component that contains
  250. * the spinner's editor or null, if they're going to handle adding
  251. * the editor to the <code>JSpinner</code> in an
  252. * <code>installUI</code> override.
  253. * <p>
  254. * Typically this method would be overridden to wrap the editor
  255. * with a container with a custom border, since one can't assume
  256. * that the editors border can be set directly.
  257. * <p>
  258. * The <code>replaceEditor</code> method is called when the spinners
  259. * editor is changed with <code>JSpinner.setEditor</code>. If you've
  260. * overriden this method, then you'll probably want to override
  261. * <code>replaceEditor</code> as well.
  262. *
  263. * @return the JSpinners editor JComponent, spinner.getEditor() by default
  264. * @see #installUI
  265. * @see #replaceEditor
  266. * @see JSpinner#getEditor
  267. */
  268. protected JComponent createEditor() {
  269. JComponent editor = spinner.getEditor();
  270. maybeRemoveEditorBorder(editor);
  271. installEditorBorderListener(editor);
  272. return editor;
  273. }
  274. /**
  275. * Called by the <code>PropertyChangeListener</code> when the
  276. * <code>JSpinner</code> editor property changes. It's the responsibility
  277. * of this method to remove the old editor and add the new one. By
  278. * default this operation is just:
  279. * <pre>
  280. * spinner.remove(oldEditor);
  281. * spinner.add(newEditor, "Editor");
  282. * </pre>
  283. * The implementation of <code>replaceEditor</code> should be coordinated
  284. * with the <code>createEditor</code> method.
  285. *
  286. * @see #createEditor
  287. * @see #createPropertyChangeListener
  288. */
  289. protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
  290. spinner.remove(oldEditor);
  291. maybeRemoveEditorBorder(newEditor);
  292. installEditorBorderListener(newEditor);
  293. spinner.add(newEditor, "Editor");
  294. }
  295. /**
  296. * Remove the border around the inner editor component for LaFs
  297. * that install an outside border around the spinner,
  298. */
  299. private void maybeRemoveEditorBorder(JComponent editor) {
  300. if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
  301. if (editor instanceof JPanel &&
  302. editor.getBorder() == null &&
  303. editor.getComponentCount() > 0) {
  304. editor = (JComponent)editor.getComponent(0);
  305. }
  306. if (editor != null && editor.getBorder() instanceof UIResource) {
  307. editor.setBorder(null);
  308. }
  309. }
  310. }
  311. /**
  312. * Remove the border around the inner editor component for LaFs
  313. * that install an outside border around the spinner,
  314. */
  315. private void installEditorBorderListener(JComponent editor) {
  316. if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
  317. if (editor instanceof JPanel &&
  318. editor.getBorder() == null &&
  319. editor.getComponentCount() > 0) {
  320. editor = (JComponent)editor.getComponent(0);
  321. }
  322. if (editor != null &&
  323. (editor.getBorder() == null ||
  324. editor.getBorder() instanceof UIResource)) {
  325. editor.addPropertyChangeListener(propertyChangeListener);
  326. }
  327. }
  328. }
  329. private void removeEditorBorderListener(JComponent editor) {
  330. if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
  331. if (editor instanceof JPanel &&
  332. editor.getComponentCount() > 0) {
  333. editor = (JComponent)editor.getComponent(0);
  334. }
  335. if (editor != null) {
  336. editor.removePropertyChangeListener(propertyChangeListener);
  337. }
  338. }
  339. }
  340. /**
  341. * Updates the enabled state of the children Components based on the
  342. * enabled state of the <code>JSpinner</code>.
  343. */
  344. private void updateEnabledState() {
  345. updateEnabledState(spinner, spinner.isEnabled());
  346. }
  347. /**
  348. * Recursively updates the enabled state of the child
  349. * <code>Component</code>s of <code>c</code>.
  350. */
  351. private void updateEnabledState(Container c, boolean enabled) {
  352. for (int counter = c.getComponentCount() - 1; counter >= 0;counter--) {
  353. Component child = c.getComponent(counter);
  354. child.setEnabled(enabled);
  355. if (child instanceof Container) {
  356. updateEnabledState((Container)child, enabled);
  357. }
  358. }
  359. }
  360. /**
  361. * Installs the KeyboardActions onto the JSpinner.
  362. */
  363. private void installKeyboardActions() {
  364. InputMap iMap = getInputMap(JComponent.
  365. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  366. SwingUtilities.replaceUIInputMap(spinner, JComponent.
  367. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  368. iMap);
  369. SwingUtilities.replaceUIActionMap(spinner, getActionMap());
  370. }
  371. /**
  372. * Returns the InputMap to install for <code>condition</code>.
  373. */
  374. private InputMap getInputMap(int condition) {
  375. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  376. return (InputMap)UIManager.get("Spinner.ancestorInputMap");
  377. }
  378. return null;
  379. }
  380. private ActionMap getActionMap() {
  381. ActionMap map = (ActionMap)UIManager.get("Spinner.actionMap");
  382. if (map == null) {
  383. map = createActionMap();
  384. if (map != null) {
  385. UIManager.getLookAndFeelDefaults().put("Spinner.actionMap",
  386. map);
  387. }
  388. }
  389. return map;
  390. }
  391. private ActionMap createActionMap() {
  392. ActionMap map = new ActionMapUIResource();
  393. map.put("increment", nextButtonHandler);
  394. map.put("decrement", previousButtonHandler);
  395. return map;
  396. }
  397. /**
  398. * A handler for spinner arrow button mouse and action events. When
  399. * a left mouse pressed event occurs we look up the (enabled) spinner
  400. * that's the source of the event and start the autorepeat timer. The
  401. * timer fires action events until any button is released at which
  402. * point the timer is stopped and the reference to the spinner cleared.
  403. * The timer doesn't start until after a 300ms delay, so often the
  404. * source of the initial (and final) action event is just the button
  405. * logic for mouse released - which means that we're relying on the fact
  406. * that our mouse listener runs after the buttons mouse listener.
  407. * <p>
  408. * Note that one instance of this handler is shared by all slider previous
  409. * arrow buttons and likewise for all of the next buttons,
  410. * so it doesn't have any state that persists beyond the limits
  411. * of a single button pressed/released gesture.
  412. */
  413. private static class ArrowButtonHandler extends AbstractAction
  414. implements MouseListener, UIResource {
  415. final javax.swing.Timer autoRepeatTimer;
  416. final boolean isNext;
  417. JSpinner spinner = null;
  418. ArrowButtonHandler(String name, boolean isNext) {
  419. super(name);
  420. this.isNext = isNext;
  421. autoRepeatTimer = new javax.swing.Timer(60, this);
  422. autoRepeatTimer.setInitialDelay(300);
  423. }
  424. private JSpinner eventToSpinner(AWTEvent e) {
  425. Object src = e.getSource();
  426. while ((src instanceof Component) && !(src instanceof JSpinner)) {
  427. src = ((Component)src).getParent();
  428. }
  429. return (src instanceof JSpinner) ? (JSpinner)src : null;
  430. }
  431. public void actionPerformed(ActionEvent e) {
  432. JSpinner spinner = this.spinner;
  433. if (!(e.getSource() instanceof javax.swing.Timer)) {
  434. // Most likely resulting from being in ActionMap.
  435. spinner = eventToSpinner(e);
  436. }
  437. if (spinner != null) {
  438. try {
  439. int calendarField = getCalendarField(spinner);
  440. spinner.commitEdit();
  441. if (calendarField != -1) {
  442. ((SpinnerDateModel)spinner.getModel()).
  443. setCalendarField(calendarField);
  444. }
  445. Object value = (isNext) ? spinner.getNextValue() :
  446. spinner.getPreviousValue();
  447. if (value != null) {
  448. spinner.setValue(value);
  449. select(spinner);
  450. }
  451. } catch (IllegalArgumentException iae) {
  452. UIManager.getLookAndFeel().provideErrorFeedback(spinner);
  453. } catch (ParseException pe) {
  454. UIManager.getLookAndFeel().provideErrorFeedback(spinner);
  455. }
  456. }
  457. }
  458. /**
  459. * If the spinner's editor is a DateEditor, this selects the field
  460. * associated with the value that is being incremented.
  461. */
  462. private void select(JSpinner spinner) {
  463. JComponent editor = spinner.getEditor();
  464. if (editor instanceof JSpinner.DateEditor) {
  465. JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
  466. JFormattedTextField ftf = dateEditor.getTextField();
  467. Format format = dateEditor.getFormat();
  468. Object value;
  469. if (format != null && (value = spinner.getValue()) != null) {
  470. SpinnerDateModel model = dateEditor.getModel();
  471. DateFormat.Field field = DateFormat.Field.ofCalendarField(
  472. model.getCalendarField());
  473. if (field != null) {
  474. try {
  475. AttributedCharacterIterator iterator = format.
  476. formatToCharacterIterator(value);
  477. if (!select(ftf, iterator, field) &&
  478. field == DateFormat.Field.HOUR0) {
  479. select(ftf, iterator, DateFormat.Field.HOUR1);
  480. }
  481. }
  482. catch (IllegalArgumentException iae) {}
  483. }
  484. }
  485. }
  486. }
  487. /**
  488. * Selects the passed in field, returning true if it is found,
  489. * false otherwise.
  490. */
  491. private boolean select(JFormattedTextField ftf,
  492. AttributedCharacterIterator iterator,
  493. DateFormat.Field field) {
  494. int max = ftf.getDocument().getLength();
  495. iterator.first();
  496. do {
  497. Map attrs = iterator.getAttributes();
  498. if (attrs != null && attrs.containsKey(field)){
  499. int start = iterator.getRunStart(field);
  500. int end = iterator.getRunLimit(field);
  501. if (start != -1 && end != -1 && start <= max &&
  502. end <= max) {
  503. ftf.select(start, end);
  504. }
  505. return true;
  506. }
  507. } while (iterator.next() != CharacterIterator.DONE);
  508. return false;
  509. }
  510. /**
  511. * Returns the calendarField under the start of the selection, or
  512. * -1 if there is no valid calendar field under the selection (or
  513. * the spinner isn't editing dates.
  514. */
  515. private int getCalendarField(JSpinner spinner) {
  516. JComponent editor = spinner.getEditor();
  517. if (editor instanceof JSpinner.DateEditor) {
  518. JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
  519. JFormattedTextField ftf = dateEditor.getTextField();
  520. int start = ftf.getSelectionStart();
  521. JFormattedTextField.AbstractFormatter formatter =
  522. ftf.getFormatter();
  523. if (formatter instanceof InternationalFormatter) {
  524. Format.Field[] fields = ((InternationalFormatter)
  525. formatter).getFields(start);
  526. for (int counter = 0; counter < fields.length; counter++) {
  527. if (fields[counter] instanceof DateFormat.Field) {
  528. int calendarField;
  529. if (fields[counter] == DateFormat.Field.HOUR1) {
  530. calendarField = Calendar.HOUR;
  531. }
  532. else {
  533. calendarField = ((DateFormat.Field)
  534. fields[counter]).getCalendarField();
  535. }
  536. if (calendarField != -1) {
  537. return calendarField;
  538. }
  539. }
  540. }
  541. }
  542. }
  543. return -1;
  544. }
  545. public void mousePressed(MouseEvent e) {
  546. if (SwingUtilities.isLeftMouseButton(e) && e.getComponent().isEnabled()) {
  547. spinner = eventToSpinner(e);
  548. autoRepeatTimer.start();
  549. focusSpinnerIfNecessary();
  550. }
  551. }
  552. public void mouseReleased(MouseEvent e) {
  553. autoRepeatTimer.stop();
  554. spinner = null;
  555. }
  556. public void mouseClicked(MouseEvent e) {
  557. }
  558. public void mouseEntered(MouseEvent e) {
  559. }
  560. public void mouseExited(MouseEvent e) {
  561. }
  562. /**
  563. * Requests focus on a child of the spinner if the spinner doesn't
  564. * have focus.
  565. */
  566. private void focusSpinnerIfNecessary() {
  567. Component fo = KeyboardFocusManager.
  568. getCurrentKeyboardFocusManager().getFocusOwner();
  569. if (spinner.isRequestFocusEnabled() && (
  570. fo == null ||
  571. !SwingUtilities.isDescendingFrom(fo, spinner))) {
  572. Container root = spinner;
  573. if (!root.isFocusCycleRoot()) {
  574. root = root.getFocusCycleRootAncestor();
  575. }
  576. if (root != null) {
  577. FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
  578. Component child = ftp.getComponentAfter(root, spinner);
  579. if (child != null && SwingUtilities.isDescendingFrom(
  580. child, spinner)) {
  581. child.requestFocus();
  582. }
  583. }
  584. }
  585. }
  586. }
  587. /**
  588. * A simple layout manager for the editor and the next/previous buttons.
  589. * See the BasicSpinnerUI javadoc for more information about exactly
  590. * how the components are arranged.
  591. */
  592. private static class SpinnerLayout implements LayoutManager
  593. {
  594. private Component nextButton = null;
  595. private Component previousButton = null;
  596. private Component editor = null;
  597. public void addLayoutComponent(String name, Component c) {
  598. if ("Next".equals(name)) {
  599. nextButton = c;
  600. }
  601. else if ("Previous".equals(name)) {
  602. previousButton = c;
  603. }
  604. else if ("Editor".equals(name)) {
  605. editor = c;
  606. }
  607. }
  608. public void removeLayoutComponent(Component c) {
  609. if (c == nextButton) {
  610. c = null;
  611. }
  612. else if (c == previousButton) {
  613. previousButton = null;
  614. }
  615. else if (c == editor) {
  616. editor = null;
  617. }
  618. }
  619. private Dimension preferredSize(Component c) {
  620. return (c == null) ? zeroSize : c.getPreferredSize();
  621. }
  622. public Dimension preferredLayoutSize(Container parent) {
  623. Dimension nextD = preferredSize(nextButton);
  624. Dimension previousD = preferredSize(previousButton);
  625. Dimension editorD = preferredSize(editor);
  626. /* Force the editors height to be a multiple of 2
  627. */
  628. editorD.height = ((editorD.height + 1) / 2) * 2;
  629. Dimension size = new Dimension(editorD.width, editorD.height);
  630. size.width += Math.max(nextD.width, previousD.width);
  631. Insets insets = parent.getInsets();
  632. size.width += insets.left + insets.right;
  633. size.height += insets.top + insets.bottom;
  634. return size;
  635. }
  636. public Dimension minimumLayoutSize(Container parent) {
  637. return preferredLayoutSize(parent);
  638. }
  639. private void setBounds(Component c, int x, int y, int width, int height) {
  640. if (c != null) {
  641. c.setBounds(x, y, width, height);
  642. }
  643. }
  644. public void layoutContainer(Container parent) {
  645. int width = parent.getWidth();
  646. int height = parent.getHeight();
  647. Insets insets = parent.getInsets();
  648. Dimension nextD = preferredSize(nextButton);
  649. Dimension previousD = preferredSize(previousButton);
  650. int buttonsWidth = Math.max(nextD.width, previousD.width);
  651. int editorHeight = height - (insets.top + insets.bottom);
  652. // The arrowButtonInsets value is used instead of the JSpinner's
  653. // insets if not null. Defining this to be (0, 0, 0, 0) causes the
  654. // buttons to be aligned with the outer edge of the spinner's
  655. // border, and leaving it as "null" places the buttons completely
  656. // inside the spinner's border.
  657. Insets buttonInsets = UIManager.getInsets("Spinner.arrowButtonInsets");
  658. if (buttonInsets == null) {
  659. buttonInsets = insets;
  660. }
  661. /* Deal with the spinner's componentOrientation property.
  662. */
  663. int editorX, editorWidth, buttonsX;
  664. if (parent.getComponentOrientation().isLeftToRight()) {
  665. editorX = insets.left;
  666. editorWidth = width - insets.left - buttonsWidth - buttonInsets.right;
  667. buttonsX = width - buttonsWidth - buttonInsets.right;
  668. } else {
  669. buttonsX = buttonInsets.left;
  670. editorX = buttonsX + buttonsWidth;
  671. editorWidth = width - buttonInsets.left - buttonsWidth - insets.right;
  672. }
  673. int nextY = buttonInsets.top;
  674. int nextHeight = (height / 2) + (height % 2) - nextY;
  675. int previousY = buttonInsets.top + nextHeight;
  676. int previousHeight = height - previousY - buttonInsets.bottom;
  677. setBounds(editor, editorX, insets.top, editorWidth, editorHeight);
  678. setBounds(nextButton, buttonsX, nextY, buttonsWidth, nextHeight);
  679. setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight);
  680. }
  681. }
  682. /**
  683. * Detect JSpinner property changes we're interested in and delegate. Subclasses
  684. * shouldn't need to replace the default propertyChangeListener (although they
  685. * can by overriding createPropertyChangeListener) since all of the interesting
  686. * property changes are delegated to protected methods.
  687. */
  688. private static class PropertyChangeHandler implements PropertyChangeListener
  689. {
  690. public void propertyChange(PropertyChangeEvent e)
  691. {
  692. String propertyName = e.getPropertyName();
  693. if (e.getSource() instanceof JSpinner) {
  694. JSpinner spinner = (JSpinner)(e.getSource());
  695. SpinnerUI spinnerUI = spinner.getUI();
  696. if (spinnerUI instanceof BasicSpinnerUI) {
  697. BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
  698. if ("editor".equals(propertyName)) {
  699. JComponent oldEditor = (JComponent)e.getOldValue();
  700. JComponent newEditor = (JComponent)e.getNewValue();
  701. ui.replaceEditor(oldEditor, newEditor);
  702. ui.updateEnabledState();
  703. }
  704. else if ("enabled".equals(propertyName)) {
  705. ui.updateEnabledState();
  706. }
  707. }
  708. } else if (e.getSource() instanceof JComponent) {
  709. JComponent c = (JComponent)e.getSource();
  710. if ((c.getParent() instanceof JPanel) &&
  711. (c.getParent().getParent() instanceof JSpinner) &&
  712. "border".equals(propertyName)) {
  713. JSpinner spinner = (JSpinner)c.getParent().getParent();
  714. SpinnerUI spinnerUI = spinner.getUI();
  715. if (spinnerUI instanceof BasicSpinnerUI) {
  716. BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
  717. ui.maybeRemoveEditorBorder(c);
  718. }
  719. }
  720. }
  721. }
  722. }
  723. }