1/* 2 * Copyright (c) 2011, 2015, 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 */ 25package com.apple.laf; 26 27import java.awt.*; 28import java.awt.event.*; 29import java.beans.*; 30import java.text.*; 31import java.text.AttributedCharacterIterator.Attribute; 32import java.text.Format.Field; 33import java.util.*; 34 35import javax.swing.*; 36import javax.swing.JSpinner.DefaultEditor; 37import javax.swing.plaf.*; 38import javax.swing.text.InternationalFormatter; 39 40import apple.laf.*; 41import apple.laf.JRSUIConstants.*; 42 43import com.apple.laf.AquaUtils.RecyclableSingleton; 44import 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 */ 50public 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 editor.setFont(new FontUIResource(spinner.getFont())); 265 } 266 267 final JFormattedTextField editorTextField = ((DefaultEditor) editor).getTextField(); 268 if (editorTextField.getFont() instanceof UIResource) { 269 editorTextField.setFont(new FontUIResource(spinner.getFont())); 270 } 271 final InputMap spinnerInputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 272 final InputMap editorInputMap = editorTextField.getInputMap(); 273 final KeyStroke[] keys = spinnerInputMap.keys(); 274 for (final KeyStroke k : keys) { 275 editorInputMap.put(k, spinnerInputMap.get(k)); 276 } 277 } 278 279 void updateEnabledState() { 280 updateEnabledState(spinner, spinner.isEnabled()); 281 } 282 283 private void updateEnabledState(final Container c, final boolean enabled) { 284 for (int counter = c.getComponentCount() - 1; counter >= 0; counter--) { 285 final Component child = c.getComponent(counter); 286 287 child.setEnabled(enabled); 288 if (child instanceof Container) { 289 updateEnabledState((Container) child, enabled); 290 } 291 } 292 } 293 294 private void installKeyboardActions() { 295 final InputMap iMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 296 SwingUtilities.replaceUIInputMap(spinner, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, iMap); 297 SwingUtilities.replaceUIActionMap(spinner, getActionMap()); 298 } 299 300 private InputMap getInputMap(final int condition) { 301 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 302 return (InputMap) UIManager.get("Spinner.ancestorInputMap"); 303 } 304 return null; 305 } 306 307 private ActionMap getActionMap() { 308 ActionMap map = (ActionMap) UIManager.get("Spinner.actionMap"); 309 310 if (map == null) { 311 map = createActionMap(); 312 if (map != null) { 313 UIManager.getLookAndFeelDefaults().put("Spinner.actionMap", map); 314 } 315 } 316 return map; 317 } 318 319 private ActionMap createActionMap() { 320 final ActionMap map = new ActionMapUIResource(); 321 map.put("increment", getNextButtonHandler()); 322 map.put("decrement", getPreviousButtonHandler()); 323 return map; 324 } 325 326 @SuppressWarnings("serial") // Superclass is not serializable across versions 327 private static class ArrowButtonHandler extends AbstractAction implements MouseListener { 328 329 final javax.swing.Timer autoRepeatTimer; 330 final boolean isNext; 331 JSpinner spinner = null; 332 333 ArrowButtonHandler(final String name, final boolean isNext) { 334 super(name); 335 this.isNext = isNext; 336 autoRepeatTimer = new javax.swing.Timer(60, this); 337 autoRepeatTimer.setInitialDelay(300); 338 } 339 340 private JSpinner eventToSpinner(final AWTEvent e) { 341 Object src = e.getSource(); 342 while ((src instanceof Component) && !(src instanceof JSpinner)) { 343 src = ((Component) src).getParent(); 344 } 345 return (src instanceof JSpinner) ? (JSpinner) src : null; 346 } 347 348 @Override 349 public void actionPerformed(final ActionEvent e) { 350 if (!(e.getSource() instanceof javax.swing.Timer)) { 351 // Most likely resulting from being in ActionMap. 352 spinner = eventToSpinner(e); 353 } 354 355 if (spinner == null) { 356 return; 357 } 358 359 try { 360 final int calendarField = getCalendarField(spinner); 361 spinner.commitEdit(); 362 if (calendarField != -1) { 363 ((SpinnerDateModel) spinner.getModel()).setCalendarField(calendarField); 364 } 365 final Object value = (isNext) ? spinner.getNextValue() : spinner.getPreviousValue(); 366 if (value != null) { 367 spinner.setValue(value); 368 select(spinner); 369 } 370 } catch (final IllegalArgumentException iae) { 371 UIManager.getLookAndFeel().provideErrorFeedback(spinner); 372 } catch (final ParseException pe) { 373 UIManager.getLookAndFeel().provideErrorFeedback(spinner); 374 } 375 } 376 377 /** 378 * If the spinner's editor is a DateEditor, this selects the field 379 * associated with the value that is being incremented. 380 */ 381 private void select(final JSpinner spinnerComponent) { 382 final JComponent editor = spinnerComponent.getEditor(); 383 if (!(editor instanceof JSpinner.DateEditor)) { 384 return; 385 } 386 387 final JSpinner.DateEditor dateEditor = (JSpinner.DateEditor) editor; 388 final JFormattedTextField ftf = dateEditor.getTextField(); 389 final Format format = dateEditor.getFormat(); 390 Object value; 391 if (format == null || (value = spinnerComponent.getValue()) == null) { 392 return; 393 } 394 395 final SpinnerDateModel model = dateEditor.getModel(); 396 final DateFormat.Field field = DateFormat.Field.ofCalendarField(model.getCalendarField()); 397 if (field == null) { 398 return; 399 } 400 401 try { 402 final AttributedCharacterIterator iterator = format.formatToCharacterIterator(value); 403 if (!select(ftf, iterator, field) && field == DateFormat.Field.HOUR0) { 404 select(ftf, iterator, DateFormat.Field.HOUR1); 405 } 406 } catch (final IllegalArgumentException iae) { 407 } 408 } 409 410 /** 411 * Selects the passed in field, returning true if it is found, false 412 * otherwise. 413 */ 414 private boolean select(final JFormattedTextField ftf, final AttributedCharacterIterator iterator, final DateFormat.Field field) { 415 final int max = ftf.getDocument().getLength(); 416 417 iterator.first(); 418 do { 419 final Map<Attribute, Object> attrs = iterator.getAttributes(); 420 if (attrs == null || !attrs.containsKey(field)) { 421 continue; 422 } 423 424 final int start = iterator.getRunStart(field); 425 final int end = iterator.getRunLimit(field); 426 if (start != -1 && end != -1 && start <= max && end <= max) { 427 ftf.select(start, end); 428 } 429 430 return true; 431 } while (iterator.next() != CharacterIterator.DONE); 432 return false; 433 } 434 435 /** 436 * Returns the calendarField under the start of the selection, or -1 if 437 * there is no valid calendar field under the selection (or the spinner 438 * isn't editing dates. 439 */ 440 private int getCalendarField(final JSpinner spinnerComponent) { 441 final JComponent editor = spinnerComponent.getEditor(); 442 if (!(editor instanceof JSpinner.DateEditor)) { 443 return -1; 444 } 445 446 final JSpinner.DateEditor dateEditor = (JSpinner.DateEditor) editor; 447 final JFormattedTextField ftf = dateEditor.getTextField(); 448 final int start = ftf.getSelectionStart(); 449 final JFormattedTextField.AbstractFormatter formatter = ftf.getFormatter(); 450 if (!(formatter instanceof InternationalFormatter)) { 451 return -1; 452 } 453 454 final Format.Field[] fields = ((InternationalFormatter) formatter).getFields(start); 455 for (final Field element : fields) { 456 if (!(element instanceof DateFormat.Field)) { 457 continue; 458 } 459 int calendarField; 460 461 if (element == DateFormat.Field.HOUR1) { 462 calendarField = Calendar.HOUR; 463 } else { 464 calendarField = ((DateFormat.Field) element).getCalendarField(); 465 } 466 467 if (calendarField != -1) { 468 return calendarField; 469 } 470 } 471 return -1; 472 } 473 474 @Override 475 public void mousePressed(final MouseEvent e) { 476 if (!SwingUtilities.isLeftMouseButton(e) || !e.getComponent().isEnabled()) { 477 return; 478 } 479 spinner = eventToSpinner(e); 480 autoRepeatTimer.start(); 481 482 focusSpinnerIfNecessary(); 483 } 484 485 @Override 486 public void mouseReleased(final MouseEvent e) { 487 autoRepeatTimer.stop(); 488 spinner = null; 489 } 490 491 @Override 492 public void mouseClicked(final MouseEvent e) { 493 } 494 495 @Override 496 public void mouseEntered(final MouseEvent e) { 497 } 498 499 @Override 500 public void mouseExited(final MouseEvent e) { 501 } 502 503 /** 504 * Requests focus on a child of the spinner if the spinner doesn't have 505 * focus. 506 */ 507 private void focusSpinnerIfNecessary() { 508 final Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 509 if (!spinner.isRequestFocusEnabled() || (fo != null && (SwingUtilities.isDescendingFrom(fo, spinner)))) { 510 return; 511 } 512 Container root = spinner; 513 514 if (!root.isFocusCycleRoot()) { 515 root = root.getFocusCycleRootAncestor(); 516 } 517 518 if (root == null) { 519 return; 520 } 521 final FocusTraversalPolicy ftp = root.getFocusTraversalPolicy(); 522 final Component child = ftp.getComponentAfter(root, spinner); 523 524 if (child != null && SwingUtilities.isDescendingFrom(child, spinner)) { 525 child.requestFocus(); 526 } 527 } 528 } 529 530 @SuppressWarnings("serial") // Superclass is not serializable across versions 531 class SpinPainter extends JComponent { 532 533 final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getSpinnerArrows()); 534 535 ButtonModel fTopModel; 536 ButtonModel fBottomModel; 537 538 boolean fPressed = false; 539 boolean fTopPressed = false; 540 541 Dimension kPreferredSize = new Dimension(15, 24); // 19,27 before trimming 542 543 public SpinPainter(final AbstractButton top, final AbstractButton bottom) { 544 if (top != null) { 545 fTopModel = top.getModel(); 546 } 547 548 if (bottom != null) { 549 fBottomModel = bottom.getModel(); 550 } 551 } 552 553 @Override 554 public void paint(final Graphics g) { 555 if (spinner.isOpaque()) { 556 g.setColor(spinner.getBackground()); 557 g.fillRect(0, 0, getWidth(), getHeight()); 558 } 559 560 AquaUtilControlSize.applySizeForControl(spinner, painter); 561 562 if (isEnabled()) { 563 if (fTopModel != null && fTopModel.isPressed()) { 564 painter.state.set(State.PRESSED); 565 painter.state.set(BooleanValue.NO); 566 } else if (fBottomModel != null && fBottomModel.isPressed()) { 567 painter.state.set(State.PRESSED); 568 painter.state.set(BooleanValue.YES); 569 } else { 570 painter.state.set(State.ACTIVE); 571 } 572 } else { 573 painter.state.set(State.DISABLED); 574 } 575 576 final Rectangle bounds = getBounds(); 577 painter.paint(g, spinner, 0, 0, bounds.width, bounds.height); 578 } 579 580 @Override 581 public Dimension getPreferredSize() { 582 final Size size = AquaUtilControlSize.getUserSizeFrom(this); 583 584 if (size == Size.MINI) { 585 return new Dimension(kPreferredSize.width, kPreferredSize.height - 8); 586 } 587 588 return kPreferredSize; 589 } 590 } 591 592 /** 593 * A simple layout manager for the editor and the next/previous buttons. See 594 * the AquaSpinnerUI javadoc for more information about exactly how the 595 * components are arranged. 596 */ 597 static class SpinnerLayout implements LayoutManager { 598 599 private Component nextButton = null; 600 private Component previousButton = null; 601 private Component editor = null; 602 private Component painter = null; 603 604 @Override 605 public void addLayoutComponent(final String name, final Component c) { 606 if ("Next".equals(name)) { 607 nextButton = c; 608 } else if ("Previous".equals(name)) { 609 previousButton = c; 610 } else if ("Editor".equals(name)) { 611 editor = c; 612 } else if ("Painter".equals(name)) { 613 painter = c; 614 } 615 } 616 617 @Override 618 public void removeLayoutComponent(Component c) { 619 if (c == nextButton) { 620 c = null; 621 } else if (c == previousButton) { 622 previousButton = null; 623 } else if (c == editor) { 624 editor = null; 625 } else if (c == painter) { 626 painter = null; 627 } 628 } 629 630 private Dimension preferredSize(final Component c) { 631 return (c == null) ? new Dimension(0, 0) : c.getPreferredSize(); 632 } 633 634 @Override 635 public Dimension preferredLayoutSize(final Container parent) { 636// Dimension nextD = preferredSize(nextButton); 637// Dimension previousD = preferredSize(previousButton); 638 final Dimension editorD = preferredSize(editor); 639 final Dimension painterD = preferredSize(painter); 640 641 /* Force the editors height to be a multiple of 2 642 */ 643 editorD.height = ((editorD.height + 1) / 2) * 2; 644 645 final Dimension size = new Dimension(editorD.width, Math.max(painterD.height, editorD.height)); 646 size.width += painterD.width; //Math.max(nextD.width, previousD.width); 647 final Insets insets = parent.getInsets(); 648 size.width += insets.left + insets.right; 649 size.height += insets.top + insets.bottom; 650 return size; 651 } 652 653 @Override 654 public Dimension minimumLayoutSize(final Container parent) { 655 return preferredLayoutSize(parent); 656 } 657 658 private void setBounds(final Component c, final int x, final int y, final int width, final int height) { 659 if (c != null) { 660 c.setBounds(x, y, width, height); 661 } 662 } 663 664 @Override 665 public void layoutContainer(final Container parent) { 666 final Insets insets = parent.getInsets(); 667 final int availWidth = parent.getWidth() - (insets.left + insets.right); 668 final int availHeight = parent.getHeight() - (insets.top + insets.bottom); 669 670 final Dimension painterD = preferredSize(painter); 671// Dimension nextD = preferredSize(nextButton); 672// Dimension previousD = preferredSize(previousButton); 673 final int nextHeight = availHeight / 2; 674 final int previousHeight = availHeight - nextHeight; 675 final int buttonsWidth = painterD.width; //Math.max(nextD.width, previousD.width); 676 final int editorWidth = availWidth - buttonsWidth; 677 678 /* Deal with the spinners componentOrientation property. 679 */ 680 int editorX, buttonsX; 681 if (parent.getComponentOrientation().isLeftToRight()) { 682 editorX = insets.left; 683 buttonsX = editorX + editorWidth; 684 } else { 685 buttonsX = insets.left; 686 editorX = buttonsX + buttonsWidth; 687 } 688 689 final int previousY = insets.top + nextHeight; 690 final int painterTop = previousY - (painterD.height / 2); 691 setBounds(editor, editorX, insets.top, editorWidth, availHeight); 692 setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight); 693 setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight); 694 setBounds(painter, buttonsX, painterTop, buttonsWidth, painterD.height); 695 } 696 } 697 698 /** 699 * Detect JSpinner property changes we're interested in and delegate. 700 * Subclasses shouldn't need to replace the default propertyChangeListener 701 * (although they can by overriding createPropertyChangeListener) since all 702 * of the interesting property changes are delegated to protected methods. 703 */ 704 static class PropertyChangeHandler implements PropertyChangeListener { 705 706 @Override 707 public void propertyChange(final PropertyChangeEvent e) { 708 final String propertyName = e.getPropertyName(); 709 final JSpinner spinner = (JSpinner) (e.getSource()); 710 final SpinnerUI spinnerUI = spinner.getUI(); 711 712 if (spinnerUI instanceof AquaSpinnerUI) { 713 final AquaSpinnerUI ui = (AquaSpinnerUI) spinnerUI; 714 715 if ("editor".equals(propertyName)) { 716 final JComponent oldEditor = (JComponent) e.getOldValue(); 717 final JComponent newEditor = (JComponent) e.getNewValue(); 718 ui.replaceEditor(oldEditor, newEditor); 719 ui.updateEnabledState(); 720 } else if ("componentOrientation".equals(propertyName)) { 721 ComponentOrientation o 722 = (ComponentOrientation) e.getNewValue(); 723 if (o != e.getOldValue()) { 724 JComponent editor = spinner.getEditor(); 725 if (editor != null) { 726 editor.applyComponentOrientation(o); 727 } 728 spinner.revalidate(); 729 spinner.repaint(); 730 } 731 } else if ("enabled".equals(propertyName)) { 732 ui.updateEnabledState(); 733 } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) { 734 ui.updateToolTipTextForChildren(spinner); 735 } else if ("font".equals(propertyName)) { 736 JComponent editor = spinner.getEditor(); 737 if (editor instanceof JSpinner.DefaultEditor) { 738 JTextField tf 739 = ((JSpinner.DefaultEditor) editor).getTextField(); 740 if (tf != null) { 741 if (tf.getFont() instanceof UIResource) { 742 tf.setFont(new FontUIResource(spinner.getFont())); 743 } 744 } 745 } 746 } 747 } 748 } 749 } 750 751 // Syncronizes the ToolTip text for the components within the spinner 752 // to be the same value as the spinner ToolTip text. 753 void updateToolTipTextForChildren(final JComponent spinnerComponent) { 754 final String toolTipText = spinnerComponent.getToolTipText(); 755 final Component[] children = spinnerComponent.getComponents(); 756 for (final Component element : children) { 757 if (element instanceof JSpinner.DefaultEditor) { 758 final JTextField tf = ((JSpinner.DefaultEditor) element).getTextField(); 759 if (tf != null) { 760 tf.setToolTipText(toolTipText); 761 } 762 } else if (element instanceof JComponent) { 763 ((JComponent) element).setToolTipText(toolTipText); 764 } 765 } 766 } 767} 768