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