1 /* 2 * Copyright (c) 2011, 2018, 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 } 500 501 @Override 502 public void mouseExited(final MouseEvent e) { 503 } 504 505 /** 506 * Requests focus on a child of the spinner if the spinner doesn't have 507 * focus. 508 */ 509 private void focusSpinnerIfNecessary() { 510 final Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 511 if (!spinner.isRequestFocusEnabled() || (fo != null && (SwingUtilities.isDescendingFrom(fo, spinner)))) { 512 return; 513 } 514 Container root = spinner; 515 516 if (!root.isFocusCycleRoot()) { 517 root = root.getFocusCycleRootAncestor(); 518 } 519 520 if (root == null) { 521 return; 522 } 523 final FocusTraversalPolicy ftp = root.getFocusTraversalPolicy(); 524 final Component child = ftp.getComponentAfter(root, spinner); 525 526 if (child != null && SwingUtilities.isDescendingFrom(child, spinner)) { 527 child.requestFocus(); 528 } 529 } 530 } 531 532 @SuppressWarnings("serial") // Superclass is not serializable across versions 533 class SpinPainter extends JComponent { 534 535 final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getSpinnerArrows()); 536 537 ButtonModel fTopModel; 538 ButtonModel fBottomModel; 539 540 boolean fPressed = false; 541 boolean fTopPressed = false; 542 543 Dimension kPreferredSize = new Dimension(15, 24); // 19,27 before trimming 544 545 public SpinPainter(final AbstractButton top, final AbstractButton bottom) { 546 if (top != null) { 547 fTopModel = top.getModel(); 548 } 549 550 if (bottom != null) { 551 fBottomModel = bottom.getModel(); 552 } 553 } 554 555 @Override 556 public void paint(final Graphics g) { 557 if (spinner.isOpaque()) { 558 g.setColor(spinner.getBackground()); 559 g.fillRect(0, 0, getWidth(), getHeight()); 560 } 561 562 AquaUtilControlSize.applySizeForControl(spinner, painter); 563 564 if (isEnabled()) { 565 if (fTopModel != null && fTopModel.isPressed()) { 566 painter.state.set(State.PRESSED); 567 painter.state.set(BooleanValue.NO); 568 } else if (fBottomModel != null && fBottomModel.isPressed()) { 569 painter.state.set(State.PRESSED); 570 painter.state.set(BooleanValue.YES); 571 } else { 572 painter.state.set(State.ACTIVE); 573 } 574 } else { 575 painter.state.set(State.DISABLED); 576 } 577 578 final Rectangle bounds = getBounds(); 579 painter.paint(g, spinner, 0, 0, bounds.width, bounds.height); 580 } 581 582 @Override 583 public Dimension getPreferredSize() { 584 final Size size = AquaUtilControlSize.getUserSizeFrom(this); 585 586 if (size == Size.MINI) { 587 return new Dimension(kPreferredSize.width, kPreferredSize.height - 8); 588 } 589 590 return kPreferredSize; 591 } 592 } 593 594 /** 595 * A simple layout manager for the editor and the next/previous buttons. See 596 * the AquaSpinnerUI javadoc for more information about exactly how the 597 * components are arranged. 598 */ 599 static class SpinnerLayout implements LayoutManager { 600 601 private Component nextButton = null; 602 private Component previousButton = null; 603 private Component editor = null; 604 private Component painter = null; 605 606 @Override 607 public void addLayoutComponent(final String name, final Component c) { 608 if ("Next".equals(name)) { 609 nextButton = c; 610 } else if ("Previous".equals(name)) { 611 previousButton = c; 612 } else if ("Editor".equals(name)) { 613 editor = c; 614 } else if ("Painter".equals(name)) { 615 painter = c; 616 } 617 } 618 619 @Override 620 public void removeLayoutComponent(Component c) { 621 if (c == nextButton) { 622 c = null; 623 } else if (c == previousButton) { 624 previousButton = null; 625 } else if (c == editor) { 626 editor = null; 627 } else if (c == painter) { 628 painter = null; 629 } 630 } 631 632 private Dimension preferredSize(final Component c) { 633 return (c == null) ? new Dimension(0, 0) : c.getPreferredSize(); 634 } 635 636 @Override 637 public Dimension preferredLayoutSize(final Container parent) { 638 // Dimension nextD = preferredSize(nextButton); 639 // Dimension previousD = preferredSize(previousButton); 640 final Dimension editorD = preferredSize(editor); 641 final Dimension painterD = preferredSize(painter); 642 643 /* Force the editors height to be a multiple of 2 644 */ 645 editorD.height = ((editorD.height + 1) / 2) * 2; 646 647 final Dimension size = new Dimension(editorD.width, Math.max(painterD.height, editorD.height)); 648 size.width += painterD.width; //Math.max(nextD.width, previousD.width); 649 final Insets insets = parent.getInsets(); 650 size.width += insets.left + insets.right; 651 size.height += insets.top + insets.bottom; 652 return size; 653 } 654 655 @Override 656 public Dimension minimumLayoutSize(final Container parent) { 657 return preferredLayoutSize(parent); 658 } 659 660 private void setBounds(final Component c, final int x, final int y, final int width, final int height) { 661 if (c != null) { 662 c.setBounds(x, y, width, height); 663 } 664 } 665 666 @Override 667 public void layoutContainer(final Container parent) { 668 final Insets insets = parent.getInsets(); 669 final int availWidth = parent.getWidth() - (insets.left + insets.right); 670 final int availHeight = parent.getHeight() - (insets.top + insets.bottom); 671 672 final Dimension painterD = preferredSize(painter); 673 // Dimension nextD = preferredSize(nextButton); 674 // Dimension previousD = preferredSize(previousButton); 675 final int nextHeight = availHeight / 2; 676 final int previousHeight = availHeight - nextHeight; 677 final int buttonsWidth = painterD.width; //Math.max(nextD.width, previousD.width); 678 final int editorWidth = availWidth - buttonsWidth; 679 680 /* Deal with the spinners componentOrientation property. 681 */ 682 int editorX, buttonsX; 683 if (parent.getComponentOrientation().isLeftToRight()) { 684 editorX = insets.left; 685 buttonsX = editorX + editorWidth; 686 } else { 687 buttonsX = insets.left; 688 editorX = buttonsX + buttonsWidth; 689 } 690 691 final int previousY = insets.top + nextHeight; 692 final int painterTop = previousY - (painterD.height / 2); 693 setBounds(editor, editorX, insets.top, editorWidth, availHeight); 694 setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight); 695 setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight); 696 setBounds(painter, buttonsX, painterTop, buttonsWidth, painterD.height); 697 } 698 } 699 700 /** 701 * Detect JSpinner property changes we're interested in and delegate. 702 * Subclasses shouldn't need to replace the default propertyChangeListener 703 * (although they can by overriding createPropertyChangeListener) since all 704 * of the interesting property changes are delegated to protected methods. 705 */ 706 static class PropertyChangeHandler implements PropertyChangeListener { 707 708 @Override 709 public void propertyChange(final PropertyChangeEvent e) { 710 final String propertyName = e.getPropertyName(); 711 final JSpinner spinner = (JSpinner) (e.getSource()); 712 final SpinnerUI spinnerUI = spinner.getUI(); 713 714 if (spinnerUI instanceof AquaSpinnerUI) { 715 final AquaSpinnerUI ui = (AquaSpinnerUI) spinnerUI; 716 717 if ("editor".equals(propertyName)) { 718 final JComponent oldEditor = (JComponent) e.getOldValue(); 719 final JComponent newEditor = (JComponent) e.getNewValue(); 720 ui.replaceEditor(oldEditor, newEditor); 721 ui.updateEnabledState(); 722 } else if ("componentOrientation".equals(propertyName)) { 723 ComponentOrientation o 724 = (ComponentOrientation) e.getNewValue(); 725 if (o != e.getOldValue()) { 726 JComponent editor = spinner.getEditor(); 727 if (editor != null) { 728 editor.applyComponentOrientation(o); 729 } 730 spinner.revalidate(); 731 spinner.repaint(); 732 } 733 } else if ("enabled".equals(propertyName)) { 734 ui.updateEnabledState(); 735 } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) { 736 ui.updateToolTipTextForChildren(spinner); 737 } else if ("font".equals(propertyName)) { 738 JComponent editor = spinner.getEditor(); 739 if (editor instanceof JSpinner.DefaultEditor) { 740 JTextField tf 741 = ((JSpinner.DefaultEditor) editor).getTextField(); 742 if (tf != null) { 743 if (tf.getFont() instanceof UIResource) { 744 Font font = spinner.getFont(); 745 tf.setFont(font == null ? null : new FontUIResource(font)); 746 } 747 } 748 } 749 } 750 } 751 } 752 } 753 754 // Syncronizes the ToolTip text for the components within the spinner 755 // to be the same value as the spinner ToolTip text. 756 void updateToolTipTextForChildren(final JComponent spinnerComponent) { 757 final String toolTipText = spinnerComponent.getToolTipText(); 758 final Component[] children = spinnerComponent.getComponents(); 759 for (final Component element : children) { 760 if (element instanceof JSpinner.DefaultEditor) { 761 final JTextField tf = ((JSpinner.DefaultEditor) element).getTextField(); 762 if (tf != null) { 763 tf.setToolTipText(toolTipText); 764 } 765 } else if (element instanceof JComponent) { 766 ((JComponent) element).setToolTipText(toolTipText); 767 } 768 } 769 } 770 }