1 /*
   2  * Copyright (c) 2011, 2012, 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     class TransparentButton extends JButton implements SwingConstants {
 192         boolean interceptRepaints = false;
 193 
 194         public TransparentButton() {
 195             super();
 196             setFocusable(false);
 197             // only intercept repaints if we are after this has been initialized
 198             // otherwise we can't talk to our containing class
 199             interceptRepaints = true;
 200         }
 201 
 202         public void paint(final Graphics g) {}
 203 
 204         public void repaint() {
 205             // only intercept repaints if we are after this has been initialized
 206             // otherwise we can't talk to our containing class
 207             if (interceptRepaints) {
 208                 if (spinPainter == null) return;
 209                 spinPainter.repaint();
 210             }
 211             super.repaint();
 212         }
 213     }
 214 
 215     protected JComponent createEditor() {
 216         final JComponent editor = spinner.getEditor();
 217         fixupEditor(editor);
 218         return editor;
 219     }
 220 
 221     protected void replaceEditor(final JComponent oldEditor, final JComponent newEditor) {
 222         spinner.remove(oldEditor);
 223         fixupEditor(newEditor);
 224         spinner.add(newEditor, "Editor");
 225     }
 226 
 227     protected void fixupEditor(final JComponent editor) {
 228         if (!(editor instanceof DefaultEditor)) return;
 229 
 230         editor.setOpaque(false);
 231         editor.setInheritsPopupMenu(true);
 232 
 233         if (editor.getFont() instanceof UIResource) {
 234             editor.setFont(spinner.getFont());
 235         }
 236 
 237         final JFormattedTextField editorTextField = ((DefaultEditor)editor).getTextField();
 238         if (editorTextField.getFont() instanceof UIResource) {
 239             editorTextField.setFont(spinner.getFont());
 240         }
 241         final InputMap spinnerInputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 242         final InputMap editorInputMap = editorTextField.getInputMap();
 243         final KeyStroke[] keys = spinnerInputMap.keys();
 244         for (final KeyStroke k : keys) {
 245             editorInputMap.put(k, spinnerInputMap.get(k));
 246         }
 247     }
 248 
 249     void updateEnabledState() {
 250         updateEnabledState(spinner, spinner.isEnabled());
 251     }
 252 
 253     private void updateEnabledState(final Container c, final boolean enabled) {
 254         for (int counter = c.getComponentCount() - 1; counter >= 0; counter--) {
 255             final Component child = c.getComponent(counter);
 256 
 257             child.setEnabled(enabled);
 258             if (child instanceof Container) {
 259                 updateEnabledState((Container)child, enabled);
 260             }
 261         }
 262     }
 263 
 264     private void installKeyboardActions() {
 265         final InputMap iMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 266         SwingUtilities.replaceUIInputMap(spinner, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, iMap);
 267         SwingUtilities.replaceUIActionMap(spinner, getActionMap());
 268     }
 269 
 270     private InputMap getInputMap(final int condition) {
 271         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
 272             return (InputMap)UIManager.get("Spinner.ancestorInputMap");
 273         }
 274         return null;
 275     }
 276 
 277     private ActionMap getActionMap() {
 278         ActionMap map = (ActionMap)UIManager.get("Spinner.actionMap");
 279 
 280         if (map == null) {
 281             map = createActionMap();
 282             if (map != null) {
 283                 UIManager.getLookAndFeelDefaults().put("Spinner.actionMap", map);
 284             }
 285         }
 286         return map;
 287     }
 288 
 289     private ActionMap createActionMap() {
 290         final ActionMap map = new ActionMapUIResource();
 291         map.put("increment", getNextButtonHandler());
 292         map.put("decrement", getPreviousButtonHandler());
 293         return map;
 294     }
 295 
 296     private static class ArrowButtonHandler extends AbstractAction implements MouseListener {
 297         final javax.swing.Timer autoRepeatTimer;
 298         final boolean isNext;
 299         JSpinner spinner = null;
 300 
 301         ArrowButtonHandler(final String name, final boolean isNext) {
 302             super(name);
 303             this.isNext = isNext;
 304             autoRepeatTimer = new javax.swing.Timer(60, this);
 305             autoRepeatTimer.setInitialDelay(300);
 306         }
 307 
 308         private JSpinner eventToSpinner(final AWTEvent e) {
 309             Object src = e.getSource();
 310             while ((src instanceof Component) && !(src instanceof JSpinner)) {
 311                 src = ((Component)src).getParent();
 312             }
 313             return (src instanceof JSpinner) ? (JSpinner)src : null;
 314         }
 315 
 316         public void actionPerformed(final ActionEvent e) {
 317             if (!(e.getSource() instanceof javax.swing.Timer)) {
 318                 // Most likely resulting from being in ActionMap.
 319                 spinner = eventToSpinner(e);
 320             }
 321 
 322             if (spinner == null) return;
 323 
 324             try {
 325                 final int calendarField = getCalendarField(spinner);
 326                 spinner.commitEdit();
 327                 if (calendarField != -1) {
 328                     ((SpinnerDateModel)spinner.getModel()).setCalendarField(calendarField);
 329                 }
 330                 final Object value = (isNext) ? spinner.getNextValue() : spinner.getPreviousValue();
 331                 if (value != null) {
 332                     spinner.setValue(value);
 333                     select(spinner);
 334                 }
 335             } catch (final IllegalArgumentException iae) {
 336                 UIManager.getLookAndFeel().provideErrorFeedback(spinner);
 337             } catch (final ParseException pe) {
 338                 UIManager.getLookAndFeel().provideErrorFeedback(spinner);
 339             }
 340         }
 341 
 342         /**
 343          * If the spinner's editor is a DateEditor, this selects the field
 344          * associated with the value that is being incremented.
 345          */
 346         private void select(final JSpinner spinnerComponent) {
 347             final JComponent editor = spinnerComponent.getEditor();
 348             if (!(editor instanceof JSpinner.DateEditor)) return;
 349 
 350             final JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
 351             final JFormattedTextField ftf = dateEditor.getTextField();
 352             final Format format = dateEditor.getFormat();
 353             Object value;
 354             if (format == null || (value = spinnerComponent.getValue()) == null) return;
 355 
 356             final SpinnerDateModel model = dateEditor.getModel();
 357             final DateFormat.Field field = DateFormat.Field.ofCalendarField(model.getCalendarField());
 358             if (field == null) return;
 359 
 360             try {
 361                 final AttributedCharacterIterator iterator = format.formatToCharacterIterator(value);
 362                 if (!select(ftf, iterator, field) && field == DateFormat.Field.HOUR0) {
 363                     select(ftf, iterator, DateFormat.Field.HOUR1);
 364                 }
 365             } catch (final IllegalArgumentException iae) {}
 366         }
 367 
 368         /**
 369          * Selects the passed in field, returning true if it is found,
 370          * false otherwise.
 371          */
 372         private boolean select(final JFormattedTextField ftf, final AttributedCharacterIterator iterator, final DateFormat.Field field) {
 373             final int max = ftf.getDocument().getLength();
 374 
 375             iterator.first();
 376             do {
 377                 final Map<Attribute,Object> attrs = iterator.getAttributes();
 378                 if (attrs == null || !attrs.containsKey(field)) continue;
 379 
 380                 final int start = iterator.getRunStart(field);
 381                 final int end = iterator.getRunLimit(field);
 382                 if (start != -1 && end != -1 && start <= max && end <= max) {
 383                     ftf.select(start, end);
 384                 }
 385 
 386                 return true;
 387             } while (iterator.next() != CharacterIterator.DONE);
 388             return false;
 389         }
 390 
 391         /**
 392          * Returns the calendarField under the start of the selection, or
 393          * -1 if there is no valid calendar field under the selection (or
 394          * the spinner isn't editing dates.
 395          */
 396         private int getCalendarField(final JSpinner spinnerComponent) {
 397             final JComponent editor = spinnerComponent.getEditor();
 398             if (!(editor instanceof JSpinner.DateEditor)) return -1;
 399 
 400             final JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
 401             final JFormattedTextField ftf = dateEditor.getTextField();
 402             final int start = ftf.getSelectionStart();
 403             final JFormattedTextField.AbstractFormatter formatter = ftf.getFormatter();
 404             if (!(formatter instanceof InternationalFormatter)) return -1;
 405 
 406             final Format.Field[] fields = ((InternationalFormatter)formatter).getFields(start);
 407             for (final Field element : fields) {
 408                 if (!(element instanceof DateFormat.Field)) continue;
 409                 int calendarField;
 410 
 411                 if (element == DateFormat.Field.HOUR1) {
 412                     calendarField = Calendar.HOUR;
 413                 } else {
 414                     calendarField = ((DateFormat.Field)element).getCalendarField();
 415                 }
 416 
 417                 if (calendarField != -1) {
 418                     return calendarField;
 419                 }
 420             }
 421             return -1;
 422         }
 423 
 424         public void mousePressed(final MouseEvent e) {
 425             if (!SwingUtilities.isLeftMouseButton(e) || !e.getComponent().isEnabled()) return;
 426             spinner = eventToSpinner(e);
 427             autoRepeatTimer.start();
 428 
 429             focusSpinnerIfNecessary();
 430         }
 431 
 432         public void mouseReleased(final MouseEvent e) {
 433             autoRepeatTimer.stop();
 434             spinner = null;
 435         }
 436 
 437         public void mouseClicked(final MouseEvent e) {}
 438         public void mouseEntered(final MouseEvent e) {}
 439         public void mouseExited(final MouseEvent e) {}
 440 
 441         /**
 442          * Requests focus on a child of the spinner if the spinner doesn't
 443          * have focus.
 444          */
 445         private void focusSpinnerIfNecessary() {
 446             final Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
 447             if (!spinner.isRequestFocusEnabled() || (fo != null && (SwingUtilities.isDescendingFrom(fo, spinner)))) return;
 448             Container root = spinner;
 449 
 450             if (!root.isFocusCycleRoot()) {
 451                 root = root.getFocusCycleRootAncestor();
 452             }
 453 
 454             if (root == null) return;
 455             final FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
 456             final Component child = ftp.getComponentAfter(root, spinner);
 457 
 458             if (child != null && SwingUtilities.isDescendingFrom(child, spinner)) {
 459                 child.requestFocus();
 460             }
 461         }
 462     }
 463 
 464     class SpinPainter extends JComponent {
 465         final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getSpinnerArrows());
 466 
 467         ButtonModel fTopModel;
 468         ButtonModel fBottomModel;
 469 
 470         boolean fPressed = false;
 471         boolean fTopPressed = false;
 472 
 473         Dimension kPreferredSize = new Dimension(15, 24); // 19,27 before trimming
 474 
 475         public SpinPainter(final AbstractButton top, final AbstractButton bottom) {
 476             if (top != null) {
 477                 fTopModel = top.getModel();
 478             }
 479 
 480             if (bottom != null) {
 481                 fBottomModel = bottom.getModel();
 482             }
 483         }
 484 
 485         public void paint(final Graphics g) {
 486             if (spinner.isOpaque()) {
 487                 g.setColor(spinner.getBackground());
 488                 g.fillRect(0, 0, getWidth(), getHeight());
 489             }
 490 
 491             AquaUtilControlSize.applySizeForControl(spinner, painter);
 492 
 493             if (isEnabled()) {
 494                 if (fTopModel != null && fTopModel.isPressed()) {
 495                     painter.state.set(State.PRESSED);
 496                     painter.state.set(BooleanValue.NO);
 497                 } else if (fBottomModel != null && fBottomModel.isPressed()) {
 498                     painter.state.set(State.PRESSED);
 499                     painter.state.set(BooleanValue.YES);
 500                 } else {
 501                     painter.state.set(State.ACTIVE);
 502                 }
 503             } else {
 504                 painter.state.set(State.DISABLED);
 505             }
 506 
 507             final Rectangle bounds = getBounds();
 508             painter.paint(g, spinner, 0, 0, bounds.width, bounds.height);
 509         }
 510 
 511         public Dimension getPreferredSize() {
 512             final Size size = AquaUtilControlSize.getUserSizeFrom(this);
 513 
 514             if (size == Size.MINI) {
 515                 return new Dimension(kPreferredSize.width, kPreferredSize.height - 8);
 516             }
 517 
 518             return kPreferredSize;
 519         }
 520     }
 521 
 522     /**
 523      * A simple layout manager for the editor and the next/previous buttons.
 524      * See the AquaSpinnerUI javadoc for more information about exactly
 525      * how the components are arranged.
 526      */
 527     static class SpinnerLayout implements LayoutManager {
 528         private Component nextButton = null;
 529         private Component previousButton = null;
 530         private Component editor = null;
 531         private Component painter = null;
 532 
 533         public void addLayoutComponent(final String name, final Component c) {
 534             if ("Next".equals(name)) {
 535                 nextButton = c;
 536             } else if ("Previous".equals(name)) {
 537                 previousButton = c;
 538             } else if ("Editor".equals(name)) {
 539                 editor = c;
 540             } else if ("Painter".equals(name)) {
 541                 painter = c;
 542             }
 543         }
 544 
 545         public void removeLayoutComponent(Component c) {
 546             if (c == nextButton) {
 547                 c = null;
 548             } else if (c == previousButton) {
 549                 previousButton = null;
 550             } else if (c == editor) {
 551                 editor = null;
 552             } else if (c == painter) {
 553                 painter = null;
 554             }
 555         }
 556 
 557         private Dimension preferredSize(final Component c) {
 558             return (c == null) ? new Dimension(0, 0) : c.getPreferredSize();
 559         }
 560 
 561         public Dimension preferredLayoutSize(final Container parent) {
 562 //            Dimension nextD = preferredSize(nextButton);
 563 //            Dimension previousD = preferredSize(previousButton);
 564             final Dimension editorD = preferredSize(editor);
 565             final Dimension painterD = preferredSize(painter);
 566 
 567             /* Force the editors height to be a multiple of 2
 568              */
 569             editorD.height = ((editorD.height + 1) / 2) * 2;
 570 
 571             final Dimension size = new Dimension(editorD.width, Math.max(painterD.height, editorD.height));
 572             size.width += painterD.width; //Math.max(nextD.width, previousD.width);
 573             final Insets insets = parent.getInsets();
 574             size.width += insets.left + insets.right;
 575             size.height += insets.top + insets.bottom;
 576             return size;
 577         }
 578 
 579         public Dimension minimumLayoutSize(final Container parent) {
 580             return preferredLayoutSize(parent);
 581         }
 582 
 583         private void setBounds(final Component c, final int x, final int y, final int width, final int height) {
 584             if (c != null) {
 585                 c.setBounds(x, y, width, height);
 586             }
 587         }
 588 
 589         public void layoutContainer(final Container parent) {
 590             final Insets insets = parent.getInsets();
 591             final int availWidth = parent.getWidth() - (insets.left + insets.right);
 592             final int availHeight = parent.getHeight() - (insets.top + insets.bottom);
 593 
 594             final Dimension painterD = preferredSize(painter);
 595 //            Dimension nextD = preferredSize(nextButton);
 596 //            Dimension previousD = preferredSize(previousButton);
 597             final int nextHeight = availHeight / 2;
 598             final int previousHeight = availHeight - nextHeight;
 599             final int buttonsWidth = painterD.width; //Math.max(nextD.width, previousD.width);
 600             final int editorWidth = availWidth - buttonsWidth;
 601 
 602             /* Deal with the spinners componentOrientation property.
 603              */
 604             int editorX, buttonsX;
 605             if (parent.getComponentOrientation().isLeftToRight()) {
 606                 editorX = insets.left;
 607                 buttonsX = editorX + editorWidth;
 608             } else {
 609                 buttonsX = insets.left;
 610                 editorX = buttonsX + buttonsWidth;
 611             }
 612 
 613             final int previousY = insets.top + nextHeight;
 614             final int painterTop = previousY - (painterD.height / 2);
 615             setBounds(editor, editorX, insets.top, editorWidth, availHeight);
 616             setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight);
 617             setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight);
 618             setBounds(painter, buttonsX, painterTop, buttonsWidth, painterD.height);
 619         }
 620     }
 621 
 622     /**
 623      * Detect JSpinner property changes we're interested in and delegate.  Subclasses
 624      * shouldn't need to replace the default propertyChangeListener (although they
 625      * can by overriding createPropertyChangeListener) since all of the interesting
 626      * property changes are delegated to protected methods.
 627      */
 628     static class PropertyChangeHandler implements PropertyChangeListener {
 629         public void propertyChange(final PropertyChangeEvent e) {
 630             final String propertyName = e.getPropertyName();
 631             final JSpinner spinner = (JSpinner)(e.getSource());
 632             final SpinnerUI spinnerUI = spinner.getUI();
 633 
 634             if (spinnerUI instanceof AquaSpinnerUI) {
 635                 final AquaSpinnerUI ui = (AquaSpinnerUI)spinnerUI;
 636 
 637                 if ("editor".equals(propertyName)) {
 638                     final JComponent oldEditor = (JComponent)e.getOldValue();
 639                     final JComponent newEditor = (JComponent)e.getNewValue();
 640                     ui.replaceEditor(oldEditor, newEditor);
 641                     ui.updateEnabledState();
 642                 } else if ("enabled".equals(propertyName)) {
 643                     ui.updateEnabledState();
 644                 } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) {
 645                     ui.updateToolTipTextForChildren(spinner);
 646                 } else if ("font".equals(propertyName)) {
 647                     JComponent editor = spinner.getEditor();
 648                     if (editor != null && editor instanceof JSpinner.DefaultEditor) {
 649                         JTextField tf =
 650                                 ((JSpinner.DefaultEditor) editor).getTextField();
 651                         if (tf != null) {
 652                             if (tf.getFont() instanceof UIResource) {
 653                                 tf.setFont(spinner.getFont());
 654                             }
 655                         }
 656                     }
 657                 }
 658             }
 659         }
 660     }
 661 
 662     // Syncronizes the ToolTip text for the components within the spinner
 663     // to be the same value as the spinner ToolTip text.
 664     void updateToolTipTextForChildren(final JComponent spinnerComponent) {
 665         final String toolTipText = spinnerComponent.getToolTipText();
 666         final Component[] children = spinnerComponent.getComponents();
 667         for (final Component element : children) {
 668             if (element instanceof JSpinner.DefaultEditor) {
 669                 final JTextField tf = ((JSpinner.DefaultEditor)element).getTextField();
 670                 if (tf != null) {
 671                     tf.setToolTipText(toolTipText);
 672                 }
 673             } else if (element instanceof JComponent) {
 674                 ((JComponent)element).setToolTipText(toolTipText);
 675             }
 676         }
 677     }
 678 }