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