1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.apple.laf;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.beans.*;
  31 import java.text.*;
  32 import java.text.AttributedCharacterIterator.Attribute;
  33 import java.text.Format.Field;
  34 import java.util.*;
  35 
  36 import javax.swing.*;
  37 import javax.swing.JSpinner.DefaultEditor;
  38 import javax.swing.plaf.*;
  39 import javax.swing.text.InternationalFormatter;
  40 
  41 import apple.laf.*;
  42 import apple.laf.JRSUIConstants.*;
  43 
  44 import com.apple.laf.AquaUtils.RecyclableSingleton;
  45 import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
  46 
  47 /**
  48  * This is originally derived from BasicSpinnerUI, but they made everything private
  49  * so we can't subclass!
  50  */
  51 public class AquaSpinnerUI extends SpinnerUI {
  52     private static final RecyclableSingleton<? extends PropertyChangeListener> propertyChangeListener = new RecyclableSingletonFromDefaultConstructor<PropertyChangeHandler>(PropertyChangeHandler.class);
  53     static PropertyChangeListener getPropertyChangeListener() {
  54         return propertyChangeListener.get();
  55     }
  56 
  57     private static final RecyclableSingleton<ArrowButtonHandler> nextButtonHandler = new RecyclableSingleton<ArrowButtonHandler>() {
  58         @Override
  59         protected ArrowButtonHandler getInstance() {
  60             return new ArrowButtonHandler("increment", true);
  61         }
  62     };
  63     static ArrowButtonHandler getNextButtonHandler() {
  64         return nextButtonHandler.get();
  65     }
  66     private static final RecyclableSingleton<ArrowButtonHandler> previousButtonHandler = new RecyclableSingleton<ArrowButtonHandler>() {
  67         @Override
  68         protected ArrowButtonHandler getInstance() {
  69             return new ArrowButtonHandler("decrement", false);
  70         }
  71     };
  72     static ArrowButtonHandler getPreviousButtonHandler() {
  73         return previousButtonHandler.get();
  74     }
  75 
  76     JSpinner spinner;
  77     SpinPainter spinPainter;
  78 
  79     public static ComponentUI createUI(final JComponent c) {
  80         return new AquaSpinnerUI();
  81     }
  82 
  83     private void maybeAdd(final Component c, final String s) {
  84         if (c != null) {
  85             spinner.add(c, s);
  86         }
  87     }
  88 
  89     boolean wasOpaque;
  90     public void installUI(final JComponent c) {
  91         this.spinner = (JSpinner)c;
  92         installDefaults();
  93         installListeners();
  94         final TransparentButton next = createNextButton();
  95         final TransparentButton prev = createPreviousButton();
  96         spinPainter = new SpinPainter(next, prev);
  97 
  98         maybeAdd(next, "Next");
  99         maybeAdd(prev, "Previous");
 100         maybeAdd(createEditor(), "Editor");
 101         maybeAdd(spinPainter, "Painter");
 102 
 103         updateEnabledState();
 104         installKeyboardActions();
 105 
 106         // this doesn't work because JSpinner calls setOpaque(true) directly in it's constructor
 107     //    LookAndFeel.installProperty(spinner, "opaque", Boolean.FALSE);
 108 
 109         // ...so we have to handle the is/was opaque ourselves
 110         wasOpaque = spinner.isOpaque();
 111         spinner.setOpaque(false);
 112     }
 113 
 114     public void uninstallUI(final JComponent c) {
 115         uninstallDefaults();
 116         uninstallListeners();
 117         spinner.setOpaque(wasOpaque);
 118         spinner = null;
 119         c.removeAll();
 120     }
 121 
 122     protected void installListeners() {
 123         spinner.addPropertyChangeListener(getPropertyChangeListener());
 124     }
 125 
 126     protected void uninstallListeners() {
 127         spinner.removePropertyChangeListener(getPropertyChangeListener());
 128     }
 129 
 130     protected void installDefaults() {
 131         spinner.setLayout(createLayout());
 132         LookAndFeel.installBorder(spinner, "Spinner.border");
 133         LookAndFeel.installColorsAndFont(spinner, "Spinner.background", "Spinner.foreground", "Spinner.font");
 134     }
 135 
 136     protected void uninstallDefaults() {
 137         spinner.setLayout(null);
 138     }
 139 
 140     protected LayoutManager createLayout() {
 141         return new SpinnerLayout();
 142     }
 143 
 144     protected PropertyChangeListener createPropertyChangeListener() {
 145         return new PropertyChangeHandler();
 146     }
 147 
 148     protected TransparentButton createPreviousButton() {
 149         final TransparentButton b = new TransparentButton();
 150         b.addActionListener(getPreviousButtonHandler());
 151         b.addMouseListener(getPreviousButtonHandler());
 152         b.setInheritsPopupMenu(true);
 153         return b;
 154     }
 155 
 156     protected TransparentButton createNextButton() {
 157         final TransparentButton b = new TransparentButton();
 158         b.addActionListener(getNextButtonHandler());
 159         b.addMouseListener(getNextButtonHandler());
 160         b.setInheritsPopupMenu(true);
 161         return b;
 162     }
 163 
 164     /**
 165      * {@inheritDoc}
 166      */
 167     public int getBaseline(JComponent c, int width, int height) {
 168         super.getBaseline(c, width, height);
 169         JComponent editor = spinner.getEditor();
 170         Insets insets = spinner.getInsets();
 171         width = width - insets.left - insets.right;
 172         height = height - insets.top - insets.bottom;
 173         if (width >= 0 && height >= 0) {
 174             int baseline = editor.getBaseline(width, height);
 175             if (baseline >= 0) {
 176                 return insets.top + baseline;
 177             }
 178         }
 179         return -1;
 180     }
 181 
 182     /**
 183      * {@inheritDoc}
 184      */
 185     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
 186             JComponent c) {
 187         super.getBaselineResizeBehavior(c);
 188         return spinner.getEditor().getBaselineResizeBehavior();
 189     }
 190 
 191     @SuppressWarnings("serial") // Superclass is not serializable across versions
 192     class TransparentButton extends JButton implements SwingConstants {
 193         boolean interceptRepaints = false;
 194 
 195         public TransparentButton() {
 196             super();
 197             setFocusable(false);
 198             // only intercept repaints if we are after this has been initialized
 199             // otherwise we can't talk to our containing class
 200             interceptRepaints = true;
 201         }
 202 
 203         public void paint(final Graphics g) {}
 204 
 205         public void repaint() {
 206             // only intercept repaints if we are after this has been initialized
 207             // otherwise we can't talk to our containing class
 208             if (interceptRepaints) {
 209                 if (spinPainter == null) return;
 210                 spinPainter.repaint();
 211             }
 212             super.repaint();
 213         }
 214     }
 215 
 216     protected JComponent createEditor() {
 217         final JComponent editor = spinner.getEditor();
 218         fixupEditor(editor);
 219         return editor;
 220     }
 221 
 222     protected void replaceEditor(final JComponent oldEditor, final JComponent newEditor) {
 223         spinner.remove(oldEditor);
 224         fixupEditor(newEditor);
 225         spinner.add(newEditor, "Editor");
 226     }
 227 
 228     protected void fixupEditor(final JComponent editor) {
 229         if (!(editor instanceof DefaultEditor)) return;
 230 
 231         editor.setOpaque(false);
 232         editor.setInheritsPopupMenu(true);
 233 
 234         if (editor.getFont() instanceof UIResource) {
 235             editor.setFont(spinner.getFont());
 236         }
 237 
 238         final JFormattedTextField editorTextField = ((DefaultEditor)editor).getTextField();
 239         if (editorTextField.getFont() instanceof UIResource) {
 240             editorTextField.setFont(spinner.getFont());
 241         }
 242         final InputMap spinnerInputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 243         final InputMap editorInputMap = editorTextField.getInputMap();
 244         final KeyStroke[] keys = spinnerInputMap.keys();
 245         for (final KeyStroke k : keys) {
 246             editorInputMap.put(k, spinnerInputMap.get(k));
 247         }
 248     }
 249 
 250     void updateEnabledState() {
 251         updateEnabledState(spinner, spinner.isEnabled());
 252     }
 253 
 254     private void updateEnabledState(final Container c, final boolean enabled) {
 255         for (int counter = c.getComponentCount() - 1; counter >= 0; counter--) {
 256             final Component child = c.getComponent(counter);
 257 
 258             child.setEnabled(enabled);
 259             if (child instanceof Container) {
 260                 updateEnabledState((Container)child, enabled);
 261             }
 262         }
 263     }
 264 
 265     private void installKeyboardActions() {
 266         final InputMap iMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 267         SwingUtilities.replaceUIInputMap(spinner, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, iMap);
 268         SwingUtilities.replaceUIActionMap(spinner, getActionMap());
 269     }
 270 
 271     private InputMap getInputMap(final int condition) {
 272         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
 273             return (InputMap)UIManager.get("Spinner.ancestorInputMap");
 274         }
 275         return null;
 276     }
 277 
 278     private ActionMap getActionMap() {
 279         ActionMap map = (ActionMap)UIManager.get("Spinner.actionMap");
 280 
 281         if (map == null) {
 282             map = createActionMap();
 283             if (map != null) {
 284                 UIManager.getLookAndFeelDefaults().put("Spinner.actionMap", map);
 285             }
 286         }
 287         return map;
 288     }
 289 
 290     private ActionMap createActionMap() {
 291         final ActionMap map = new ActionMapUIResource();
 292         map.put("increment", getNextButtonHandler());
 293         map.put("decrement", getPreviousButtonHandler());
 294         return map;
 295     }
 296 
 297     @SuppressWarnings("serial") // Superclass is not serializable across versions
 298     private static class ArrowButtonHandler extends AbstractAction implements MouseListener {
 299         final javax.swing.Timer autoRepeatTimer;
 300         final boolean isNext;
 301         JSpinner spinner = null;
 302 
 303         ArrowButtonHandler(final String name, final boolean isNext) {
 304             super(name);
 305             this.isNext = isNext;
 306             autoRepeatTimer = new javax.swing.Timer(60, this);
 307             autoRepeatTimer.setInitialDelay(300);
 308         }
 309 
 310         private JSpinner eventToSpinner(final AWTEvent e) {
 311             Object src = e.getSource();
 312             while ((src instanceof Component) && !(src instanceof JSpinner)) {
 313                 src = ((Component)src).getParent();
 314             }
 315             return (src instanceof JSpinner) ? (JSpinner)src : null;
 316         }
 317 
 318         public void actionPerformed(final ActionEvent e) {
 319             if (!(e.getSource() instanceof javax.swing.Timer)) {
 320                 // Most likely resulting from being in ActionMap.
 321                 spinner = eventToSpinner(e);
 322             }
 323 
 324             if (spinner == null) return;
 325 
 326             try {
 327                 final int calendarField = getCalendarField(spinner);
 328                 spinner.commitEdit();
 329                 if (calendarField != -1) {
 330                     ((SpinnerDateModel)spinner.getModel()).setCalendarField(calendarField);
 331                 }
 332                 final Object value = (isNext) ? spinner.getNextValue() : spinner.getPreviousValue();
 333                 if (value != null) {
 334                     spinner.setValue(value);
 335                     select(spinner);
 336                 }
 337             } catch (final IllegalArgumentException iae) {
 338                 UIManager.getLookAndFeel().provideErrorFeedback(spinner);
 339             } catch (final ParseException pe) {
 340                 UIManager.getLookAndFeel().provideErrorFeedback(spinner);
 341             }
 342         }
 343 
 344         /**
 345          * If the spinner's editor is a DateEditor, this selects the field
 346          * associated with the value that is being incremented.
 347          */
 348         private void select(final JSpinner spinnerComponent) {
 349             final JComponent editor = spinnerComponent.getEditor();
 350             if (!(editor instanceof JSpinner.DateEditor)) return;
 351 
 352             final JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
 353             final JFormattedTextField ftf = dateEditor.getTextField();
 354             final Format format = dateEditor.getFormat();
 355             Object value;
 356             if (format == null || (value = spinnerComponent.getValue()) == null) return;
 357 
 358             final SpinnerDateModel model = dateEditor.getModel();
 359             final DateFormat.Field field = DateFormat.Field.ofCalendarField(model.getCalendarField());
 360             if (field == null) return;
 361 
 362             try {
 363                 final AttributedCharacterIterator iterator = format.formatToCharacterIterator(value);
 364                 if (!select(ftf, iterator, field) && field == DateFormat.Field.HOUR0) {
 365                     select(ftf, iterator, DateFormat.Field.HOUR1);
 366                 }
 367             } catch (final IllegalArgumentException iae) {}
 368         }
 369 
 370         /**
 371          * Selects the passed in field, returning true if it is found,
 372          * false otherwise.
 373          */
 374         private boolean select(final JFormattedTextField ftf, final AttributedCharacterIterator iterator, final DateFormat.Field field) {
 375             final int max = ftf.getDocument().getLength();
 376 
 377             iterator.first();
 378             do {
 379                 final Map<Attribute,Object> attrs = iterator.getAttributes();
 380                 if (attrs == null || !attrs.containsKey(field)) continue;
 381 
 382                 final int start = iterator.getRunStart(field);
 383                 final int end = iterator.getRunLimit(field);
 384                 if (start != -1 && end != -1 && start <= max && end <= max) {
 385                     ftf.select(start, end);
 386                 }
 387 
 388                 return true;
 389             } while (iterator.next() != CharacterIterator.DONE);
 390             return false;
 391         }
 392 
 393         /**
 394          * Returns the calendarField under the start of the selection, or
 395          * -1 if there is no valid calendar field under the selection (or
 396          * the spinner isn't editing dates.
 397          */
 398         private int getCalendarField(final JSpinner spinnerComponent) {
 399             final JComponent editor = spinnerComponent.getEditor();
 400             if (!(editor instanceof JSpinner.DateEditor)) return -1;
 401 
 402             final JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
 403             final JFormattedTextField ftf = dateEditor.getTextField();
 404             final int start = ftf.getSelectionStart();
 405             final JFormattedTextField.AbstractFormatter formatter = ftf.getFormatter();
 406             if (!(formatter instanceof InternationalFormatter)) return -1;
 407 
 408             final Format.Field[] fields = ((InternationalFormatter)formatter).getFields(start);
 409             for (final Field element : fields) {
 410                 if (!(element instanceof DateFormat.Field)) continue;
 411                 int calendarField;
 412 
 413                 if (element == DateFormat.Field.HOUR1) {
 414                     calendarField = Calendar.HOUR;
 415                 } else {
 416                     calendarField = ((DateFormat.Field)element).getCalendarField();
 417                 }
 418 
 419                 if (calendarField != -1) {
 420                     return calendarField;
 421                 }
 422             }
 423             return -1;
 424         }
 425 
 426         public void mousePressed(final MouseEvent e) {
 427             if (!SwingUtilities.isLeftMouseButton(e) || !e.getComponent().isEnabled()) return;
 428             spinner = eventToSpinner(e);
 429             autoRepeatTimer.start();
 430 
 431             focusSpinnerIfNecessary();
 432         }
 433 
 434         public void mouseReleased(final MouseEvent e) {
 435             autoRepeatTimer.stop();
 436             spinner = null;
 437         }
 438 
 439         public void mouseClicked(final MouseEvent e) {}
 440         public void mouseEntered(final MouseEvent e) {}
 441         public void mouseExited(final MouseEvent e) {}
 442 
 443         /**
 444          * Requests focus on a child of the spinner if the spinner doesn't
 445          * have focus.
 446          */
 447         private void focusSpinnerIfNecessary() {
 448             final Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
 449             if (!spinner.isRequestFocusEnabled() || (fo != null && (SwingUtilities.isDescendingFrom(fo, spinner)))) return;
 450             Container root = spinner;
 451 
 452             if (!root.isFocusCycleRoot()) {
 453                 root = root.getFocusCycleRootAncestor();
 454             }
 455 
 456             if (root == null) return;
 457             final FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
 458             final Component child = ftp.getComponentAfter(root, spinner);
 459 
 460             if (child != null && SwingUtilities.isDescendingFrom(child, spinner)) {
 461                 child.requestFocus();
 462             }
 463         }
 464     }
 465 
 466     @SuppressWarnings("serial") // Superclass is not serializable across versions
 467     class SpinPainter extends JComponent {
 468         final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getSpinnerArrows());
 469 
 470         ButtonModel fTopModel;
 471         ButtonModel fBottomModel;
 472 
 473         boolean fPressed = false;
 474         boolean fTopPressed = false;
 475 
 476         Dimension kPreferredSize = new Dimension(15, 24); // 19,27 before trimming
 477 
 478         public SpinPainter(final AbstractButton top, final AbstractButton bottom) {
 479             if (top != null) {
 480                 fTopModel = top.getModel();
 481             }
 482 
 483             if (bottom != null) {
 484                 fBottomModel = bottom.getModel();
 485             }
 486         }
 487 
 488         public void paint(final Graphics g) {
 489             if (spinner.isOpaque()) {
 490                 g.setColor(spinner.getBackground());
 491                 g.fillRect(0, 0, getWidth(), getHeight());
 492             }
 493 
 494             AquaUtilControlSize.applySizeForControl(spinner, painter);
 495 
 496             if (isEnabled()) {
 497                 if (fTopModel != null && fTopModel.isPressed()) {
 498                     painter.state.set(State.PRESSED);
 499                     painter.state.set(BooleanValue.NO);
 500                 } else if (fBottomModel != null && fBottomModel.isPressed()) {
 501                     painter.state.set(State.PRESSED);
 502                     painter.state.set(BooleanValue.YES);
 503                 } else {
 504                     painter.state.set(State.ACTIVE);
 505                 }
 506             } else {
 507                 painter.state.set(State.DISABLED);
 508             }
 509 
 510             final Rectangle bounds = getBounds();
 511             painter.paint(g, spinner, 0, 0, bounds.width, bounds.height);
 512         }
 513 
 514         public Dimension getPreferredSize() {
 515             final Size size = AquaUtilControlSize.getUserSizeFrom(this);
 516 
 517             if (size == Size.MINI) {
 518                 return new Dimension(kPreferredSize.width, kPreferredSize.height - 8);
 519             }
 520 
 521             return kPreferredSize;
 522         }
 523     }
 524 
 525     /**
 526      * A simple layout manager for the editor and the next/previous buttons.
 527      * See the AquaSpinnerUI javadoc for more information about exactly
 528      * how the components are arranged.
 529      */
 530     static class SpinnerLayout implements LayoutManager {
 531         private Component nextButton = null;
 532         private Component previousButton = null;
 533         private Component editor = null;
 534         private Component painter = null;
 535 
 536         public void addLayoutComponent(final String name, final Component c) {
 537             if ("Next".equals(name)) {
 538                 nextButton = c;
 539             } else if ("Previous".equals(name)) {
 540                 previousButton = c;
 541             } else if ("Editor".equals(name)) {
 542                 editor = c;
 543             } else if ("Painter".equals(name)) {
 544                 painter = c;
 545             }
 546         }
 547 
 548         public void removeLayoutComponent(Component c) {
 549             if (c == nextButton) {
 550                 c = null;
 551             } else if (c == previousButton) {
 552                 previousButton = null;
 553             } else if (c == editor) {
 554                 editor = null;
 555             } else if (c == painter) {
 556                 painter = null;
 557             }
 558         }
 559 
 560         private Dimension preferredSize(final Component c) {
 561             return (c == null) ? new Dimension(0, 0) : c.getPreferredSize();
 562         }
 563 
 564         public Dimension preferredLayoutSize(final Container parent) {
 565 //            Dimension nextD = preferredSize(nextButton);
 566 //            Dimension previousD = preferredSize(previousButton);
 567             final Dimension editorD = preferredSize(editor);
 568             final Dimension painterD = preferredSize(painter);
 569 
 570             /* Force the editors height to be a multiple of 2
 571              */
 572             editorD.height = ((editorD.height + 1) / 2) * 2;
 573 
 574             final Dimension size = new Dimension(editorD.width, Math.max(painterD.height, editorD.height));
 575             size.width += painterD.width; //Math.max(nextD.width, previousD.width);
 576             final Insets insets = parent.getInsets();
 577             size.width += insets.left + insets.right;
 578             size.height += insets.top + insets.bottom;
 579             return size;
 580         }
 581 
 582         public Dimension minimumLayoutSize(final Container parent) {
 583             return preferredLayoutSize(parent);
 584         }
 585 
 586         private void setBounds(final Component c, final int x, final int y, final int width, final int height) {
 587             if (c != null) {
 588                 c.setBounds(x, y, width, height);
 589             }
 590         }
 591 
 592         public void layoutContainer(final Container parent) {
 593             final Insets insets = parent.getInsets();
 594             final int availWidth = parent.getWidth() - (insets.left + insets.right);
 595             final int availHeight = parent.getHeight() - (insets.top + insets.bottom);
 596 
 597             final Dimension painterD = preferredSize(painter);
 598 //            Dimension nextD = preferredSize(nextButton);
 599 //            Dimension previousD = preferredSize(previousButton);
 600             final int nextHeight = availHeight / 2;
 601             final int previousHeight = availHeight - nextHeight;
 602             final int buttonsWidth = painterD.width; //Math.max(nextD.width, previousD.width);
 603             final int editorWidth = availWidth - buttonsWidth;
 604 
 605             /* Deal with the spinners componentOrientation property.
 606              */
 607             int editorX, buttonsX;
 608             if (parent.getComponentOrientation().isLeftToRight()) {
 609                 editorX = insets.left;
 610                 buttonsX = editorX + editorWidth;
 611             } else {
 612                 buttonsX = insets.left;
 613                 editorX = buttonsX + buttonsWidth;
 614             }
 615 
 616             final int previousY = insets.top + nextHeight;
 617             final int painterTop = previousY - (painterD.height / 2);
 618             setBounds(editor, editorX, insets.top, editorWidth, availHeight);
 619             setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight);
 620             setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight);
 621             setBounds(painter, buttonsX, painterTop, buttonsWidth, painterD.height);
 622         }
 623     }
 624 
 625     /**
 626      * Detect JSpinner property changes we're interested in and delegate.  Subclasses
 627      * shouldn't need to replace the default propertyChangeListener (although they
 628      * can by overriding createPropertyChangeListener) since all of the interesting
 629      * property changes are delegated to protected methods.
 630      */
 631     static class PropertyChangeHandler implements PropertyChangeListener {
 632         public void propertyChange(final PropertyChangeEvent e) {
 633             final String propertyName = e.getPropertyName();
 634             final JSpinner spinner = (JSpinner)(e.getSource());
 635             final SpinnerUI spinnerUI = spinner.getUI();
 636 
 637             if (spinnerUI instanceof AquaSpinnerUI) {
 638                 final AquaSpinnerUI ui = (AquaSpinnerUI)spinnerUI;
 639 
 640                 if ("editor".equals(propertyName)) {
 641                     final JComponent oldEditor = (JComponent)e.getOldValue();
 642                     final JComponent newEditor = (JComponent)e.getNewValue();
 643                     ui.replaceEditor(oldEditor, newEditor);
 644                     ui.updateEnabledState();
 645                 } else if ("enabled".equals(propertyName)) {
 646                     ui.updateEnabledState();
 647                 } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) {
 648                     ui.updateToolTipTextForChildren(spinner);
 649                 } else if ("font".equals(propertyName)) {
 650                     JComponent editor = spinner.getEditor();
 651                     if (editor != null && editor instanceof JSpinner.DefaultEditor) {
 652                         JTextField tf =
 653                                 ((JSpinner.DefaultEditor) editor).getTextField();
 654                         if (tf != null) {
 655                             if (tf.getFont() instanceof UIResource) {
 656                                 tf.setFont(spinner.getFont());
 657                             }
 658                         }
 659                     }
 660                 }
 661             }
 662         }
 663     }
 664 
 665     // Syncronizes the ToolTip text for the components within the spinner
 666     // to be the same value as the spinner ToolTip text.
 667     void updateToolTipTextForChildren(final JComponent spinnerComponent) {
 668         final String toolTipText = spinnerComponent.getToolTipText();
 669         final Component[] children = spinnerComponent.getComponents();
 670         for (final Component element : children) {
 671             if (element instanceof JSpinner.DefaultEditor) {
 672                 final JTextField tf = ((JSpinner.DefaultEditor)element).getTextField();
 673                 if (tf != null) {
 674                     tf.setToolTipText(toolTipText);
 675                 }
 676             } else if (element instanceof JComponent) {
 677                 ((JComponent)element).setToolTipText(toolTipText);
 678             }
 679         }
 680     }
 681 }