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