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