1 /* 2 * Copyright (c) 1997, 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 javax.swing; 26 27 import java.awt.Component; 28 import java.awt.Color; 29 import java.awt.Point; 30 import java.awt.Dimension; 31 import java.awt.Rectangle; 32 import java.awt.Font; 33 import java.awt.FontMetrics; 34 import java.awt.Cursor; 35 36 import java.awt.event.MouseEvent; 37 import java.awt.event.FocusListener; 38 39 import java.beans.JavaBean; 40 import java.beans.BeanProperty; 41 import java.beans.Transient; 42 43 import java.util.Locale; 44 import java.util.ArrayList; 45 46 import javax.swing.event.ChangeListener; 47 import javax.swing.event.ChangeEvent; 48 49 import javax.swing.plaf.TabbedPaneUI; 50 import javax.swing.plaf.UIResource; 51 52 import javax.accessibility.AccessibleContext; 53 import javax.accessibility.Accessible; 54 import javax.accessibility.AccessibleRole; 55 import javax.accessibility.AccessibleComponent; 56 import javax.accessibility.AccessibleStateSet; 57 import javax.accessibility.AccessibleIcon; 58 import javax.accessibility.AccessibleSelection; 59 import javax.accessibility.AccessibleState; 60 61 import sun.swing.SwingUtilities2; 62 63 import java.io.Serializable; 64 import java.io.ObjectOutputStream; 65 import java.io.ObjectInputStream; 66 import java.io.IOException; 67 68 /** 69 * A component that lets the user switch between a group of components by 70 * clicking on a tab with a given title and/or icon. 71 * For examples and information on using tabbed panes see 72 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/tabbedpane.html">How to Use Tabbed Panes</a>, 73 * a section in <em>The Java Tutorial</em>. 74 * <p> 75 * Tabs/components are added to a <code>TabbedPane</code> object by using the 76 * <code>addTab</code> and <code>insertTab</code> methods. 77 * A tab is represented by an index corresponding 78 * to the position it was added in, where the first tab has an index equal to 0 79 * and the last tab has an index equal to the tab count minus 1. 80 * <p> 81 * The <code>TabbedPane</code> uses a <code>SingleSelectionModel</code> 82 * to represent the set 83 * of tab indices and the currently selected index. If the tab count 84 * is greater than 0, then there will always be a selected index, which 85 * by default will be initialized to the first tab. If the tab count is 86 * 0, then the selected index will be -1. 87 * <p> 88 * The tab title can be rendered by a <code>Component</code>. 89 * For example, the following produce similar results: 90 * <pre> 91 * // In this case the look and feel renders the title for the tab. 92 * tabbedPane.addTab("Tab", myComponent); 93 * // In this case the custom component is responsible for rendering the 94 * // title of the tab. 95 * tabbedPane.addTab(null, myComponent); 96 * tabbedPane.setTabComponentAt(0, new JLabel("Tab")); 97 * </pre> 98 * The latter is typically used when you want a more complex user interaction 99 * that requires custom components on the tab. For example, you could 100 * provide a custom component that animates or one that has widgets for 101 * closing the tab. 102 * <p> 103 * If you specify a component for a tab, the <code>JTabbedPane</code> 104 * will not render any text or icon you have specified for the tab. 105 * <p> 106 * <strong>Note:</strong> 107 * Do not use <code>setVisible</code> directly on a tab component to make it visible, 108 * use <code>setSelectedComponent</code> or <code>setSelectedIndex</code> methods instead. 109 * <p> 110 * <strong>Warning:</strong> Swing is not thread safe. For more 111 * information see <a 112 * href="package-summary.html#threading">Swing's Threading 113 * Policy</a>. 114 * <p> 115 * <strong>Warning:</strong> 116 * Serialized objects of this class will not be compatible with 117 * future Swing releases. The current serialization support is 118 * appropriate for short term storage or RMI between applications running 119 * the same version of Swing. As of 1.4, support for long term storage 120 * of all JavaBeans 121 * has been added to the <code>java.beans</code> package. 122 * Please see {@link java.beans.XMLEncoder}. 123 * 124 * @author Dave Moore 125 * @author Philip Milne 126 * @author Amy Fowler 127 * 128 * @see SingleSelectionModel 129 * @since 1.2 130 */ 131 @JavaBean(defaultProperty = "UI", description = "A component which provides a tab folder metaphor for displaying one component from a set of components.") 132 @SwingContainer 133 @SuppressWarnings("serial") // Same-version serialization only 134 public class JTabbedPane extends JComponent 135 implements Serializable, Accessible, SwingConstants { 136 137 /** 138 * The tab layout policy for wrapping tabs in multiple runs when all 139 * tabs will not fit within a single run. 140 */ 141 public static final int WRAP_TAB_LAYOUT = 0; 142 143 /** 144 * Tab layout policy for providing a subset of available tabs when all 145 * the tabs will not fit within a single run. If all the tabs do 146 * not fit within a single run the look and feel will provide a way 147 * to navigate to hidden tabs. 148 */ 149 public static final int SCROLL_TAB_LAYOUT = 1; 150 151 152 /** 153 * @see #getUIClassID 154 * @see #readObject 155 */ 156 private static final String uiClassID = "TabbedPaneUI"; 157 158 /** 159 * Where the tabs are placed. 160 * @see #setTabPlacement 161 */ 162 protected int tabPlacement = TOP; 163 164 private int tabLayoutPolicy; 165 166 /** The default selection model */ 167 protected SingleSelectionModel model; 168 169 private boolean haveRegistered; 170 171 /** 172 * The <code>changeListener</code> is the listener we add to the 173 * model. 174 */ 175 protected ChangeListener changeListener = null; 176 177 private final java.util.List<Page> pages; 178 179 /* The component that is currently visible */ 180 private Component visComp = null; 181 182 /** 183 * Only one <code>ChangeEvent</code> is needed per <code>TabPane</code> 184 * instance since the 185 * event's only (read-only) state is the source property. The source 186 * of events generated here is always "this". 187 */ 188 protected transient ChangeEvent changeEvent = null; 189 190 /** 191 * Creates an empty <code>TabbedPane</code> with a default 192 * tab placement of <code>JTabbedPane.TOP</code>. 193 * @see #addTab 194 */ 195 public JTabbedPane() { 196 this(TOP, WRAP_TAB_LAYOUT); 197 } 198 199 /** 200 * Creates an empty <code>TabbedPane</code> with the specified tab placement 201 * of either: <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>, 202 * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>. 203 * 204 * @param tabPlacement the placement for the tabs relative to the content 205 * @see #addTab 206 */ 207 public JTabbedPane(int tabPlacement) { 208 this(tabPlacement, WRAP_TAB_LAYOUT); 209 } 210 211 /** 212 * Creates an empty <code>TabbedPane</code> with the specified tab placement 213 * and tab layout policy. Tab placement may be either: 214 * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>, 215 * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>. 216 * Tab layout policy may be either: <code>JTabbedPane.WRAP_TAB_LAYOUT</code> 217 * or <code>JTabbedPane.SCROLL_TAB_LAYOUT</code>. 218 * 219 * @param tabPlacement the placement for the tabs relative to the content 220 * @param tabLayoutPolicy the policy for laying out tabs when all tabs will not fit on one run 221 * @exception IllegalArgumentException if tab placement or tab layout policy are not 222 * one of the above supported values 223 * @see #addTab 224 * @since 1.4 225 */ 226 public JTabbedPane(int tabPlacement, int tabLayoutPolicy) { 227 setTabPlacement(tabPlacement); 228 setTabLayoutPolicy(tabLayoutPolicy); 229 pages = new ArrayList<Page>(1); 230 setModel(new DefaultSingleSelectionModel()); 231 updateUI(); 232 } 233 234 /** 235 * Returns the UI object which implements the L&F for this component. 236 * 237 * @return a <code>TabbedPaneUI</code> object 238 * @see #setUI 239 */ 240 public TabbedPaneUI getUI() { 241 return (TabbedPaneUI)ui; 242 } 243 244 /** 245 * Sets the UI object which implements the L&F for this component. 246 * 247 * @param ui the new UI object 248 * @see UIDefaults#getUI 249 */ 250 @BeanProperty(hidden = true, visualUpdate = true, description 251 = "The UI object that implements the tabbedpane's LookAndFeel") 252 public void setUI(TabbedPaneUI ui) { 253 super.setUI(ui); 254 // disabled icons are generated by LF so they should be unset here 255 for (int i = 0; i < getTabCount(); i++) { 256 Icon icon = pages.get(i).disabledIcon; 257 if (icon instanceof UIResource) { 258 setDisabledIconAt(i, null); 259 } 260 } 261 } 262 263 /** 264 * Resets the UI property to a value from the current look and feel. 265 * 266 * @see JComponent#updateUI 267 */ 268 public void updateUI() { 269 setUI((TabbedPaneUI)UIManager.getUI(this)); 270 } 271 272 273 /** 274 * Returns the name of the UI class that implements the 275 * L&F for this component. 276 * 277 * @return the string "TabbedPaneUI" 278 * @see JComponent#getUIClassID 279 * @see UIDefaults#getUI 280 */ 281 @BeanProperty(bound = false) 282 public String getUIClassID() { 283 return uiClassID; 284 } 285 286 287 /** 288 * We pass <code>ModelChanged</code> events along to the listeners with 289 * the tabbedpane (instead of the model itself) as the event source. 290 */ 291 protected class ModelListener implements ChangeListener, Serializable { 292 public void stateChanged(ChangeEvent e) { 293 fireStateChanged(); 294 } 295 } 296 297 /** 298 * Subclasses that want to handle <code>ChangeEvents</code> differently 299 * can override this to return a subclass of <code>ModelListener</code> or 300 * another <code>ChangeListener</code> implementation. 301 * 302 * @return a {@code ChangeListener} 303 * @see #fireStateChanged 304 */ 305 protected ChangeListener createChangeListener() { 306 return new ModelListener(); 307 } 308 309 /** 310 * Adds a <code>ChangeListener</code> to this tabbedpane. 311 * 312 * @param l the <code>ChangeListener</code> to add 313 * @see #fireStateChanged 314 * @see #removeChangeListener 315 */ 316 public void addChangeListener(ChangeListener l) { 317 listenerList.add(ChangeListener.class, l); 318 } 319 320 /** 321 * Removes a <code>ChangeListener</code> from this tabbedpane. 322 * 323 * @param l the <code>ChangeListener</code> to remove 324 * @see #fireStateChanged 325 * @see #addChangeListener 326 */ 327 public void removeChangeListener(ChangeListener l) { 328 listenerList.remove(ChangeListener.class, l); 329 } 330 331 /** 332 * Returns an array of all the <code>ChangeListener</code>s added 333 * to this <code>JTabbedPane</code> with <code>addChangeListener</code>. 334 * 335 * @return all of the <code>ChangeListener</code>s added or an empty 336 * array if no listeners have been added 337 * @since 1.4 338 */ 339 @BeanProperty(bound = false) 340 public ChangeListener[] getChangeListeners() { 341 return listenerList.getListeners(ChangeListener.class); 342 } 343 344 /** 345 * Sends a {@code ChangeEvent}, with this {@code JTabbedPane} as the source, 346 * to each registered listener. This method is called each time there is 347 * a change to either the selected index or the selected tab in the 348 * {@code JTabbedPane}. Usually, the selected index and selected tab change 349 * together. However, there are some cases, such as tab addition, where the 350 * selected index changes and the same tab remains selected. There are other 351 * cases, such as deleting the selected tab, where the index remains the 352 * same, but a new tab moves to that index. Events are fired for all of 353 * these cases. 354 * 355 * @see #addChangeListener 356 * @see EventListenerList 357 */ 358 @SuppressWarnings("deprecation") 359 protected void fireStateChanged() { 360 /* --- Begin code to deal with visibility --- */ 361 362 /* This code deals with changing the visibility of components to 363 * hide and show the contents for the selected tab. It duplicates 364 * logic already present in BasicTabbedPaneUI, logic that is 365 * processed during the layout pass. This code exists to allow 366 * developers to do things that are quite difficult to accomplish 367 * with the previous model of waiting for the layout pass to process 368 * visibility changes; such as requesting focus on the new visible 369 * component. 370 * 371 * For the average code, using the typical JTabbedPane methods, 372 * all visibility changes will now be processed here. However, 373 * the code in BasicTabbedPaneUI still exists, for the purposes 374 * of backward compatibility. Therefore, when making changes to 375 * this code, ensure that the BasicTabbedPaneUI code is kept in 376 * synch. 377 */ 378 379 int selIndex = getSelectedIndex(); 380 381 /* if the selection is now nothing */ 382 if (selIndex < 0) { 383 /* if there was a previous visible component */ 384 if (visComp != null && visComp.isVisible()) { 385 /* make it invisible */ 386 visComp.setVisible(false); 387 } 388 389 /* now there's no visible component */ 390 visComp = null; 391 392 /* else - the selection is now something */ 393 } else { 394 /* Fetch the component for the new selection */ 395 Component newComp = getComponentAt(selIndex); 396 397 /* if the new component is non-null and different */ 398 if (newComp != null && newComp != visComp) { 399 boolean shouldChangeFocus = false; 400 401 /* Note: the following (clearing of the old visible component) 402 * is inside this if-statement for good reason: Tabbed pane 403 * should continue to show the previously visible component 404 * if there is no component for the chosen tab. 405 */ 406 407 /* if there was a previous visible component */ 408 if (visComp != null) { 409 shouldChangeFocus = 410 (SwingUtilities.findFocusOwner(visComp) != null); 411 412 /* if it's still visible */ 413 if (visComp.isVisible()) { 414 /* make it invisible */ 415 visComp.setVisible(false); 416 } 417 } 418 419 if (!newComp.isVisible()) { 420 newComp.setVisible(true); 421 } 422 423 if (shouldChangeFocus) { 424 SwingUtilities2.tabbedPaneChangeFocusTo(newComp); 425 } 426 427 visComp = newComp; 428 } /* else - the visible component shouldn't changed */ 429 } 430 431 /* --- End code to deal with visibility --- */ 432 433 // Guaranteed to return a non-null array 434 Object[] listeners = listenerList.getListenerList(); 435 // Process the listeners last to first, notifying 436 // those that are interested in this event 437 for (int i = listeners.length-2; i>=0; i-=2) { 438 if (listeners[i]==ChangeListener.class) { 439 // Lazily create the event: 440 if (changeEvent == null) 441 changeEvent = new ChangeEvent(this); 442 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); 443 } 444 } 445 } 446 447 /** 448 * Returns the model associated with this tabbedpane. 449 * 450 * @return the {@code SingleSelectionModel} associated with this tabbedpane 451 * @see #setModel 452 */ 453 public SingleSelectionModel getModel() { 454 return model; 455 } 456 457 /** 458 * Sets the model to be used with this tabbedpane. 459 * 460 * @param model the model to be used 461 * @see #getModel 462 */ 463 @BeanProperty(description 464 = "The tabbedpane's SingleSelectionModel.") 465 public void setModel(SingleSelectionModel model) { 466 SingleSelectionModel oldModel = getModel(); 467 468 if (oldModel != null) { 469 oldModel.removeChangeListener(changeListener); 470 changeListener = null; 471 } 472 473 this.model = model; 474 475 if (model != null) { 476 changeListener = createChangeListener(); 477 model.addChangeListener(changeListener); 478 } 479 480 firePropertyChange("model", oldModel, model); 481 repaint(); 482 } 483 484 /** 485 * Returns the placement of the tabs for this tabbedpane. 486 * 487 * @return an {@code int} specifying the placement for the tabs 488 * @see #setTabPlacement 489 */ 490 public int getTabPlacement() { 491 return tabPlacement; 492 } 493 494 /** 495 * Sets the tab placement for this tabbedpane. 496 * Possible values are:<ul> 497 * <li><code>JTabbedPane.TOP</code> 498 * <li><code>JTabbedPane.BOTTOM</code> 499 * <li><code>JTabbedPane.LEFT</code> 500 * <li><code>JTabbedPane.RIGHT</code> 501 * </ul> 502 * The default value, if not set, is <code>SwingConstants.TOP</code>. 503 * 504 * @param tabPlacement the placement for the tabs relative to the content 505 * @exception IllegalArgumentException if tab placement value isn't one 506 * of the above valid values 507 */ 508 @BeanProperty(preferred = true, visualUpdate = true, enumerationValues = { 509 "JTabbedPane.TOP", 510 "JTabbedPane.LEFT", 511 "JTabbedPane.BOTTOM", 512 "JTabbedPane.RIGHT"}, description 513 = "The tabbedpane's tab placement.") 514 public void setTabPlacement(int tabPlacement) { 515 checkTabPlacement(tabPlacement); 516 if (this.tabPlacement != tabPlacement) { 517 int oldValue = this.tabPlacement; 518 this.tabPlacement = tabPlacement; 519 firePropertyChange("tabPlacement", oldValue, tabPlacement); 520 revalidate(); 521 repaint(); 522 } 523 } 524 525 private static void checkTabPlacement(int tabPlacement) { 526 if (tabPlacement != TOP && tabPlacement != LEFT && 527 tabPlacement != BOTTOM && tabPlacement != RIGHT) { 528 throw new IllegalArgumentException("illegal tab placement:" 529 + " must be TOP, BOTTOM, LEFT, or RIGHT"); 530 } 531 } 532 533 /** 534 * Returns the policy used by the tabbedpane to layout the tabs when all the 535 * tabs will not fit within a single run. 536 * 537 * @return an {@code int} specifying the policy used to layout the tabs 538 * @see #setTabLayoutPolicy 539 * @since 1.4 540 */ 541 public int getTabLayoutPolicy() { 542 return tabLayoutPolicy; 543 } 544 545 /** 546 * Sets the policy which the tabbedpane will use in laying out the tabs 547 * when all the tabs will not fit within a single run. 548 * Possible values are: 549 * <ul> 550 * <li><code>JTabbedPane.WRAP_TAB_LAYOUT</code> 551 * <li><code>JTabbedPane.SCROLL_TAB_LAYOUT</code> 552 * </ul> 553 * 554 * The default value, if not set by the UI, is <code>JTabbedPane.WRAP_TAB_LAYOUT</code>. 555 * <p> 556 * Some look and feels might only support a subset of the possible 557 * layout policies, in which case the value of this property may be 558 * ignored. 559 * 560 * @param tabLayoutPolicy the policy used to layout the tabs 561 * @exception IllegalArgumentException if layoutPolicy value isn't one 562 * of the above valid values 563 * @see #getTabLayoutPolicy 564 * @since 1.4 565 */ 566 @BeanProperty(preferred = true, visualUpdate = true, enumerationValues = { 567 "JTabbedPane.WRAP_TAB_LAYOUT", 568 "JTabbedPane.SCROLL_TAB_LAYOUT"}, description 569 = "The tabbedpane's policy for laying out the tabs") 570 public void setTabLayoutPolicy(int tabLayoutPolicy) { 571 checkTabLayoutPolicy(tabLayoutPolicy); 572 if (this.tabLayoutPolicy != tabLayoutPolicy) { 573 int oldValue = this.tabLayoutPolicy; 574 this.tabLayoutPolicy = tabLayoutPolicy; 575 firePropertyChange("tabLayoutPolicy", oldValue, tabLayoutPolicy); 576 revalidate(); 577 repaint(); 578 } 579 } 580 581 private static void checkTabLayoutPolicy(int tabLayoutPolicy) { 582 if (tabLayoutPolicy != WRAP_TAB_LAYOUT 583 && tabLayoutPolicy != SCROLL_TAB_LAYOUT) { 584 throw new IllegalArgumentException("illegal tab layout policy:" 585 + " must be WRAP_TAB_LAYOUT or SCROLL_TAB_LAYOUT"); 586 } 587 } 588 589 /** 590 * Returns the currently selected index for this tabbedpane. 591 * Returns -1 if there is no currently selected tab. 592 * 593 * @return the index of the selected tab 594 * @see #setSelectedIndex 595 */ 596 @Transient 597 public int getSelectedIndex() { 598 return model.getSelectedIndex(); 599 } 600 601 /** 602 * Sets the selected index for this tabbedpane. The index must be 603 * a valid tab index or -1, which indicates that no tab should be selected 604 * (can also be used when there are no tabs in the tabbedpane). If a -1 605 * value is specified when the tabbedpane contains one or more tabs, then 606 * the results will be implementation defined. 607 * 608 * @param index the index to be selected 609 * @exception IndexOutOfBoundsException if index is out of range 610 * {@code (index < -1 || index >= tab count)} 611 * 612 * @see #getSelectedIndex 613 * @see SingleSelectionModel#setSelectedIndex 614 */ 615 @BeanProperty(bound = false, preferred = true, description 616 = "The tabbedpane's selected tab index.") 617 public void setSelectedIndex(int index) { 618 if (index != -1) { 619 checkIndex(index); 620 } 621 setSelectedIndexImpl(index, true); 622 } 623 624 625 private void setSelectedIndexImpl(int index, boolean doAccessibleChanges) { 626 int oldIndex = model.getSelectedIndex(); 627 Page oldPage = null, newPage = null; 628 String oldName = null; 629 630 doAccessibleChanges = doAccessibleChanges && (oldIndex != index); 631 632 if (doAccessibleChanges) { 633 if (accessibleContext != null) { 634 oldName = accessibleContext.getAccessibleName(); 635 } 636 637 if (oldIndex >= 0) { 638 oldPage = pages.get(oldIndex); 639 } 640 641 if (index >= 0) { 642 newPage = pages.get(index); 643 } 644 } 645 646 model.setSelectedIndex(index); 647 648 if (doAccessibleChanges) { 649 changeAccessibleSelection(oldPage, oldName, newPage); 650 } 651 } 652 653 private void changeAccessibleSelection(Page oldPage, String oldName, Page newPage) { 654 if (accessibleContext == null) { 655 return; 656 } 657 658 if (oldPage != null) { 659 oldPage.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 660 AccessibleState.SELECTED, null); 661 } 662 663 if (newPage != null) { 664 newPage.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 665 null, AccessibleState.SELECTED); 666 } 667 668 accessibleContext.firePropertyChange( 669 AccessibleContext.ACCESSIBLE_NAME_PROPERTY, 670 oldName, 671 accessibleContext.getAccessibleName()); 672 } 673 674 /** 675 * Returns the currently selected component for this tabbedpane. 676 * Returns <code>null</code> if there is no currently selected tab. 677 * 678 * @return the component corresponding to the selected tab 679 * @see #setSelectedComponent 680 */ 681 @Transient 682 public Component getSelectedComponent() { 683 int index = getSelectedIndex(); 684 if (index == -1) { 685 return null; 686 } 687 return getComponentAt(index); 688 } 689 690 /** 691 * Sets the selected component for this tabbedpane. This 692 * will automatically set the <code>selectedIndex</code> to the index 693 * corresponding to the specified component. 694 * 695 * @param c the selected {@code Component} for this {@code TabbedPane} 696 * @exception IllegalArgumentException if component not found in tabbed 697 * pane 698 * @see #getSelectedComponent 699 */ 700 @BeanProperty(bound = false, preferred = true, description 701 = "The tabbedpane's selected component.") 702 public void setSelectedComponent(Component c) { 703 int index = indexOfComponent(c); 704 if (index != -1) { 705 setSelectedIndex(index); 706 } else { 707 throw new IllegalArgumentException("component not found in tabbed pane"); 708 } 709 } 710 711 /** 712 * Inserts a new tab for the given component, at the given index, 713 * represented by the given title and/or icon, either of which may 714 * be {@code null}. 715 * 716 * @param title the title to be displayed on the tab 717 * @param icon the icon to be displayed on the tab 718 * @param component the component to be displayed when this tab is clicked. 719 * @param tip the tooltip to be displayed for this tab 720 * @param index the position to insert this new tab 721 * ({@code > 0 and <= getTabCount()}) 722 * 723 * @throws IndexOutOfBoundsException if the index is out of range 724 * ({@code < 0 or > getTabCount()}) 725 * 726 * @see #addTab 727 * @see #removeTabAt 728 */ 729 public void insertTab(String title, Icon icon, Component component, String tip, int index) { 730 int newIndex = index; 731 732 // If component already exists, remove corresponding 733 // tab so that new tab gets added correctly 734 // Note: we are allowing component=null because of compatibility, 735 // but we really should throw an exception because much of the 736 // rest of the JTabbedPane implementation isn't designed to deal 737 // with null components for tabs. 738 int removeIndex = indexOfComponent(component); 739 if (component != null && removeIndex != -1) { 740 removeTabAt(removeIndex); 741 if (newIndex > removeIndex) { 742 newIndex--; 743 } 744 } 745 746 int selectedIndex = getSelectedIndex(); 747 748 pages.add( 749 newIndex, 750 new Page(this, title != null? title : "", icon, null, component, tip)); 751 752 753 if (component != null) { 754 addImpl(component, null, -1); 755 component.setVisible(false); 756 } else { 757 firePropertyChange("indexForNullComponent", -1, index); 758 } 759 760 if (pages.size() == 1) { 761 setSelectedIndex(0); 762 } 763 764 if (selectedIndex >= newIndex) { 765 setSelectedIndexImpl(selectedIndex + 1, false); 766 } 767 768 if (!haveRegistered && tip != null) { 769 ToolTipManager.sharedInstance().registerComponent(this); 770 haveRegistered = true; 771 } 772 773 if (accessibleContext != null) { 774 accessibleContext.firePropertyChange( 775 AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, 776 null, component); 777 } 778 revalidate(); 779 repaint(); 780 } 781 782 /** 783 * Adds a <code>component</code> and <code>tip</code> 784 * represented by a <code>title</code> and/or <code>icon</code>, 785 * either of which can be <code>null</code>. 786 * Cover method for <code>insertTab</code>. 787 * 788 * @param title the title to be displayed in this tab 789 * @param icon the icon to be displayed in this tab 790 * @param component the component to be displayed when this tab is clicked 791 * @param tip the tooltip to be displayed for this tab 792 * 793 * @see #insertTab 794 * @see #removeTabAt 795 */ 796 public void addTab(String title, Icon icon, Component component, String tip) { 797 insertTab(title, icon, component, tip, pages.size()); 798 } 799 800 /** 801 * Adds a <code>component</code> represented by a <code>title</code> 802 * and/or <code>icon</code>, either of which can be <code>null</code>. 803 * Cover method for <code>insertTab</code>. 804 * 805 * @param title the title to be displayed in this tab 806 * @param icon the icon to be displayed in this tab 807 * @param component the component to be displayed when this tab is clicked 808 * 809 * @see #insertTab 810 * @see #removeTabAt 811 */ 812 public void addTab(String title, Icon icon, Component component) { 813 insertTab(title, icon, component, null, pages.size()); 814 } 815 816 /** 817 * Adds a <code>component</code> represented by a <code>title</code> 818 * and no icon. 819 * Cover method for <code>insertTab</code>. 820 * 821 * @param title the title to be displayed in this tab 822 * @param component the component to be displayed when this tab is clicked 823 * 824 * @see #insertTab 825 * @see #removeTabAt 826 */ 827 public void addTab(String title, Component component) { 828 insertTab(title, null, component, null, pages.size()); 829 } 830 831 /** 832 * Adds a <code>component</code> with a tab title defaulting to 833 * the name of the component which is the result of calling 834 * <code>component.getName</code>. 835 * Cover method for <code>insertTab</code>. 836 * 837 * @param component the component to be displayed when this tab is clicked 838 * @return the component 839 * 840 * @see #insertTab 841 * @see #removeTabAt 842 */ 843 public Component add(Component component) { 844 if (!(component instanceof UIResource)) { 845 addTab(component.getName(), component); 846 } else { 847 super.add(component); 848 } 849 return component; 850 } 851 852 /** 853 * Adds a <code>component</code> with the specified tab title. 854 * Cover method for <code>insertTab</code>. 855 * 856 * @param title the title to be displayed in this tab 857 * @param component the component to be displayed when this tab is clicked 858 * @return the component 859 * 860 * @see #insertTab 861 * @see #removeTabAt 862 */ 863 public Component add(String title, Component component) { 864 if (!(component instanceof UIResource)) { 865 addTab(title, component); 866 } else { 867 super.add(title, component); 868 } 869 return component; 870 } 871 872 /** 873 * Adds a <code>component</code> at the specified tab index with a tab 874 * title defaulting to the name of the component. 875 * Cover method for <code>insertTab</code>. 876 * 877 * @param component the component to be displayed when this tab is clicked 878 * @param index the position to insert this new tab 879 * @return the component 880 * 881 * @see #insertTab 882 * @see #removeTabAt 883 */ 884 public Component add(Component component, int index) { 885 if (!(component instanceof UIResource)) { 886 // Container.add() interprets -1 as "append", so convert 887 // the index appropriately to be handled by the vector 888 insertTab(component.getName(), null, component, null, 889 index == -1? getTabCount() : index); 890 } else { 891 super.add(component, index); 892 } 893 return component; 894 } 895 896 /** 897 * Adds a <code>component</code> to the tabbed pane. 898 * If <code>constraints</code> is a <code>String</code> or an 899 * <code>Icon</code>, it will be used for the tab title, 900 * otherwise the component's name will be used as the tab title. 901 * Cover method for <code>insertTab</code>. 902 * 903 * @param component the component to be displayed when this tab is clicked 904 * @param constraints the object to be displayed in the tab 905 * 906 * @see #insertTab 907 * @see #removeTabAt 908 */ 909 public void add(Component component, Object constraints) { 910 if (!(component instanceof UIResource)) { 911 if (constraints instanceof String) { 912 addTab((String)constraints, component); 913 } else if (constraints instanceof Icon) { 914 addTab(null, (Icon)constraints, component); 915 } else { 916 add(component); 917 } 918 } else { 919 super.add(component, constraints); 920 } 921 } 922 923 /** 924 * Adds a <code>component</code> at the specified tab index. 925 * If <code>constraints</code> is a <code>String</code> or an 926 * <code>Icon</code>, it will be used for the tab title, 927 * otherwise the component's name will be used as the tab title. 928 * Cover method for <code>insertTab</code>. 929 * 930 * @param component the component to be displayed when this tab is clicked 931 * @param constraints the object to be displayed in the tab 932 * @param index the position to insert this new tab 933 * 934 * @see #insertTab 935 * @see #removeTabAt 936 */ 937 public void add(Component component, Object constraints, int index) { 938 if (!(component instanceof UIResource)) { 939 940 Icon icon = constraints instanceof Icon? (Icon)constraints : null; 941 String title = constraints instanceof String? (String)constraints : null; 942 // Container.add() interprets -1 as "append", so convert 943 // the index appropriately to be handled by the vector 944 insertTab(title, icon, component, null, index == -1? getTabCount() : index); 945 } else { 946 super.add(component, constraints, index); 947 } 948 } 949 950 private void clearAccessibleParent(Component c) { 951 AccessibleContext ac = c.getAccessibleContext(); 952 if (ac != null) { 953 ac.setAccessibleParent(null); 954 } 955 } 956 957 /** 958 * Removes the tab at <code>index</code>. 959 * After the component associated with <code>index</code> is removed, 960 * its visibility is reset to true to ensure it will be visible 961 * if added to other containers. 962 * @param index the index of the tab to be removed 963 * @exception IndexOutOfBoundsException if index is out of range 964 * {@code (index < 0 || index >= tab count)} 965 * 966 * @see #addTab 967 * @see #insertTab 968 */ 969 @SuppressWarnings("deprecation") 970 public void removeTabAt(int index) { 971 checkIndex(index); 972 973 Component component = getComponentAt(index); 974 boolean shouldChangeFocus = false; 975 int selected = getSelectedIndex(); 976 String oldName = null; 977 978 /* if we're about to remove the visible component */ 979 if (component == visComp) { 980 shouldChangeFocus = (SwingUtilities.findFocusOwner(visComp) != null); 981 visComp = null; 982 } 983 984 if (accessibleContext != null) { 985 /* if we're removing the selected page */ 986 if (index == selected) { 987 /* fire an accessible notification that it's unselected */ 988 pages.get(index).firePropertyChange( 989 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 990 AccessibleState.SELECTED, null); 991 992 oldName = accessibleContext.getAccessibleName(); 993 } 994 995 accessibleContext.firePropertyChange( 996 AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, 997 component, null); 998 } 999 1000 // Force the tabComponent to be cleaned up. 1001 setTabComponentAt(index, null); 1002 pages.remove(index); 1003 1004 // NOTE 4/15/2002 (joutwate): 1005 // This fix is implemented using client properties since there is 1006 // currently no IndexPropertyChangeEvent. Once 1007 // IndexPropertyChangeEvents have been added this code should be 1008 // modified to use it. 1009 putClientProperty("__index_to_remove__", Integer.valueOf(index)); 1010 1011 /* if the selected tab is after the removal */ 1012 if (selected > index) { 1013 setSelectedIndexImpl(selected - 1, false); 1014 1015 /* if the selected tab is the last tab */ 1016 } else if (selected >= getTabCount()) { 1017 setSelectedIndexImpl(selected - 1, false); 1018 Page newSelected = (selected != 0) 1019 ? pages.get(selected - 1) 1020 : null; 1021 1022 changeAccessibleSelection(null, oldName, newSelected); 1023 1024 /* selected index hasn't changed, but the associated tab has */ 1025 } else if (index == selected) { 1026 fireStateChanged(); 1027 changeAccessibleSelection(null, oldName, pages.get(index)); 1028 } 1029 1030 // We can't assume the tab indices correspond to the 1031 // container's children array indices, so make sure we 1032 // remove the correct child! 1033 if (component != null) { 1034 Component[] components = getComponents(); 1035 for (int i = components.length; --i >= 0; ) { 1036 if (components[i] == component) { 1037 super.remove(i); 1038 component.setVisible(true); 1039 clearAccessibleParent(component); 1040 break; 1041 } 1042 } 1043 } 1044 1045 if (shouldChangeFocus) { 1046 SwingUtilities2.tabbedPaneChangeFocusTo(getSelectedComponent()); 1047 } 1048 1049 revalidate(); 1050 repaint(); 1051 } 1052 1053 /** 1054 * Removes the specified <code>Component</code> from the 1055 * <code>JTabbedPane</code>. The method does nothing 1056 * if the <code>component</code> is null. 1057 * 1058 * @param component the component to remove from the tabbedpane 1059 * @see #addTab 1060 * @see #removeTabAt 1061 */ 1062 public void remove(Component component) { 1063 int index = indexOfComponent(component); 1064 if (index != -1) { 1065 removeTabAt(index); 1066 } else { 1067 // Container#remove(comp) invokes Container#remove(int) 1068 // so make sure JTabbedPane#remove(int) isn't called here 1069 Component[] children = getComponents(); 1070 for (int i=0; i < children.length; i++) { 1071 if (component == children[i]) { 1072 super.remove(i); 1073 break; 1074 } 1075 } 1076 } 1077 } 1078 1079 /** 1080 * Removes the tab and component which corresponds to the specified index. 1081 * 1082 * @param index the index of the component to remove from the 1083 * <code>tabbedpane</code> 1084 * @exception IndexOutOfBoundsException if index is out of range 1085 * {@code (index < 0 || index >= tab count)} 1086 * @see #addTab 1087 * @see #removeTabAt 1088 */ 1089 public void remove(int index) { 1090 removeTabAt(index); 1091 } 1092 1093 /** 1094 * Removes all the tabs and their corresponding components 1095 * from the <code>tabbedpane</code>. 1096 * 1097 * @see #addTab 1098 * @see #removeTabAt 1099 */ 1100 public void removeAll() { 1101 setSelectedIndexImpl(-1, true); 1102 1103 int tabCount = getTabCount(); 1104 // We invoke removeTabAt for each tab, otherwise we may end up 1105 // removing Components added by the UI. 1106 while (tabCount-- > 0) { 1107 removeTabAt(tabCount); 1108 } 1109 } 1110 1111 /** 1112 * Returns the number of tabs in this <code>tabbedpane</code>. 1113 * 1114 * @return an integer specifying the number of tabbed pages 1115 */ 1116 @BeanProperty(bound = false) 1117 public int getTabCount() { 1118 return pages.size(); 1119 } 1120 1121 /** 1122 * Returns the number of tab runs currently used to display 1123 * the tabs. 1124 * @return an integer giving the number of rows if the 1125 * <code>tabPlacement</code> 1126 * is <code>TOP</code> or <code>BOTTOM</code> 1127 * and the number of columns if 1128 * <code>tabPlacement</code> 1129 * is <code>LEFT</code> or <code>RIGHT</code>, 1130 * or 0 if there is no UI set on this <code>tabbedpane</code> 1131 */ 1132 @BeanProperty(bound = false) 1133 public int getTabRunCount() { 1134 if (ui != null) { 1135 return ((TabbedPaneUI)ui).getTabRunCount(this); 1136 } 1137 return 0; 1138 } 1139 1140 1141 // Getters for the Pages 1142 1143 /** 1144 * Returns the tab title at <code>index</code>. 1145 * 1146 * @param index the index of the item being queried 1147 * @return the title at <code>index</code> 1148 * @exception IndexOutOfBoundsException if index is out of range 1149 * {@code (index < 0 || index >= tab count)} 1150 * @see #setTitleAt 1151 */ 1152 public String getTitleAt(int index) { 1153 return pages.get(index).title; 1154 } 1155 1156 /** 1157 * Returns the tab icon at <code>index</code>. 1158 * 1159 * @param index the index of the item being queried 1160 * @return the icon at <code>index</code> 1161 * @exception IndexOutOfBoundsException if index is out of range 1162 * {@code (index < 0 || index >= tab count)} 1163 * 1164 * @see #setIconAt 1165 */ 1166 public Icon getIconAt(int index) { 1167 return pages.get(index).icon; 1168 } 1169 1170 /** 1171 * Returns the tab disabled icon at <code>index</code>. 1172 * If the tab disabled icon doesn't exist at <code>index</code> 1173 * this will forward the call to the look and feel to construct 1174 * an appropriate disabled Icon from the corresponding enabled 1175 * Icon. Some look and feels might not render the disabled Icon, 1176 * in which case it won't be created. 1177 * 1178 * @param index the index of the item being queried 1179 * @return the icon at <code>index</code> 1180 * @exception IndexOutOfBoundsException if index is out of range 1181 * {@code (index < 0 || index >= tab count)} 1182 * 1183 * @see #setDisabledIconAt 1184 */ 1185 public Icon getDisabledIconAt(int index) { 1186 Page page = pages.get(index); 1187 if (page.disabledIcon == null) { 1188 page.disabledIcon = UIManager.getLookAndFeel().getDisabledIcon(this, page.icon); 1189 } 1190 return page.disabledIcon; 1191 } 1192 1193 /** 1194 * Returns the tab tooltip text at <code>index</code>. 1195 * 1196 * @param index the index of the item being queried 1197 * @return a string containing the tool tip text at <code>index</code> 1198 * @exception IndexOutOfBoundsException if index is out of range 1199 * {@code (index < 0 || index >= tab count)} 1200 * 1201 * @see #setToolTipTextAt 1202 * @since 1.3 1203 */ 1204 public String getToolTipTextAt(int index) { 1205 return pages.get(index).tip; 1206 } 1207 1208 /** 1209 * Returns the tab background color at <code>index</code>. 1210 * 1211 * @param index the index of the item being queried 1212 * @return the <code>Color</code> of the tab background at 1213 * <code>index</code> 1214 * @exception IndexOutOfBoundsException if index is out of range 1215 * {@code (index < 0 || index >= tab count)} 1216 * 1217 * @see #setBackgroundAt 1218 */ 1219 public Color getBackgroundAt(int index) { 1220 return pages.get(index).getBackground(); 1221 } 1222 1223 /** 1224 * Returns the tab foreground color at <code>index</code>. 1225 * 1226 * @param index the index of the item being queried 1227 * @return the <code>Color</code> of the tab foreground at 1228 * <code>index</code> 1229 * @exception IndexOutOfBoundsException if index is out of range 1230 * {@code (index < 0 || index >= tab count)} 1231 * 1232 * @see #setForegroundAt 1233 */ 1234 public Color getForegroundAt(int index) { 1235 return pages.get(index).getForeground(); 1236 } 1237 1238 /** 1239 * Returns whether or not the tab at <code>index</code> is 1240 * currently enabled. 1241 * 1242 * @param index the index of the item being queried 1243 * @return true if the tab at <code>index</code> is enabled; 1244 * false otherwise 1245 * @exception IndexOutOfBoundsException if index is out of range 1246 * {@code (index < 0 || index >= tab count)} 1247 * 1248 * @see #setEnabledAt 1249 */ 1250 public boolean isEnabledAt(int index) { 1251 return pages.get(index).isEnabled(); 1252 } 1253 1254 /** 1255 * Returns the component at <code>index</code>. 1256 * 1257 * @param index the index of the item being queried 1258 * @return the <code>Component</code> at <code>index</code> 1259 * @exception IndexOutOfBoundsException if index is out of range 1260 * {@code (index < 0 || index >= tab count)} 1261 * 1262 * @see #setComponentAt 1263 */ 1264 public Component getComponentAt(int index) { 1265 return pages.get(index).component; 1266 } 1267 1268 /** 1269 * Returns the keyboard mnemonic for accessing the specified tab. 1270 * The mnemonic is the key which when combined with the look and feel's 1271 * mouseless modifier (usually Alt) will activate the specified 1272 * tab. 1273 * 1274 * @since 1.4 1275 * @param tabIndex the index of the tab that the mnemonic refers to 1276 * @return the key code which represents the mnemonic; 1277 * -1 if a mnemonic is not specified for the tab 1278 * @exception IndexOutOfBoundsException if index is out of range 1279 * (<code>tabIndex</code> < 0 || 1280 * <code>tabIndex</code> >= tab count) 1281 * @see #setDisplayedMnemonicIndexAt(int,int) 1282 * @see #setMnemonicAt(int,int) 1283 */ 1284 public int getMnemonicAt(int tabIndex) { 1285 checkIndex(tabIndex); 1286 1287 Page page = pages.get(tabIndex); 1288 return page.getMnemonic(); 1289 } 1290 1291 /** 1292 * Returns the character, as an index, that the look and feel should 1293 * provide decoration for as representing the mnemonic character. 1294 * 1295 * @since 1.4 1296 * @param tabIndex the index of the tab that the mnemonic refers to 1297 * @return index representing mnemonic character if one exists; 1298 * otherwise returns -1 1299 * @exception IndexOutOfBoundsException if index is out of range 1300 * (<code>tabIndex</code> < 0 || 1301 * <code>tabIndex</code> >= tab count) 1302 * @see #setDisplayedMnemonicIndexAt(int,int) 1303 * @see #setMnemonicAt(int,int) 1304 */ 1305 public int getDisplayedMnemonicIndexAt(int tabIndex) { 1306 checkIndex(tabIndex); 1307 1308 Page page = pages.get(tabIndex); 1309 return page.getDisplayedMnemonicIndex(); 1310 } 1311 1312 /** 1313 * Returns the tab bounds at <code>index</code>. If the tab at 1314 * this index is not currently visible in the UI, then returns 1315 * <code>null</code>. 1316 * If there is no UI set on this <code>tabbedpane</code>, 1317 * then returns <code>null</code>. 1318 * 1319 * @param index the index to be queried 1320 * @return a <code>Rectangle</code> containing the tab bounds at 1321 * <code>index</code>, or <code>null</code> if tab at 1322 * <code>index</code> is not currently visible in the UI, 1323 * or if there is no UI set on this <code>tabbedpane</code> 1324 * @exception IndexOutOfBoundsException if index is out of range 1325 * {@code (index < 0 || index >= tab count)} 1326 */ 1327 public Rectangle getBoundsAt(int index) { 1328 checkIndex(index); 1329 if (ui != null) { 1330 return ((TabbedPaneUI)ui).getTabBounds(this, index); 1331 } 1332 return null; 1333 } 1334 1335 1336 // Setters for the Pages 1337 1338 /** 1339 * Sets the title at <code>index</code> to <code>title</code> which 1340 * can be <code>null</code>. 1341 * The title is not shown if a tab component for this tab was specified. 1342 * An internal exception is raised if there is no tab at that index. 1343 * 1344 * @param index the tab index where the title should be set 1345 * @param title the title to be displayed in the tab 1346 * @exception IndexOutOfBoundsException if index is out of range 1347 * {@code (index < 0 || index >= tab count)} 1348 * 1349 * @see #getTitleAt 1350 * @see #setTabComponentAt 1351 */ 1352 @BeanProperty(preferred = true, visualUpdate = true, description 1353 = "The title at the specified tab index.") 1354 public void setTitleAt(int index, String title) { 1355 Page page = pages.get(index); 1356 String oldTitle =page.title; 1357 page.title = title; 1358 1359 if (oldTitle != title) { 1360 firePropertyChange("indexForTitle", -1, index); 1361 } 1362 page.updateDisplayedMnemonicIndex(); 1363 if ((oldTitle != title) && (accessibleContext != null)) { 1364 accessibleContext.firePropertyChange( 1365 AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, 1366 oldTitle, title); 1367 } 1368 if (title == null || oldTitle == null || 1369 !title.equals(oldTitle)) { 1370 revalidate(); 1371 repaint(); 1372 } 1373 } 1374 1375 /** 1376 * Sets the icon at <code>index</code> to <code>icon</code> which can be 1377 * <code>null</code>. This does not set disabled icon at <code>icon</code>. 1378 * If the new Icon is different than the current Icon and disabled icon 1379 * is not explicitly set, the LookAndFeel will be asked to generate a disabled 1380 * Icon. To explicitly set disabled icon, use <code>setDisableIconAt()</code>. 1381 * The icon is not shown if a tab component for this tab was specified. 1382 * An internal exception is raised if there is no tab at that index. 1383 * 1384 * @param index the tab index where the icon should be set 1385 * @param icon the icon to be displayed in the tab 1386 * @exception IndexOutOfBoundsException if index is out of range 1387 * {@code (index < 0 || index >= tab count)} 1388 * 1389 * @see #setDisabledIconAt 1390 * @see #getIconAt 1391 * @see #getDisabledIconAt 1392 * @see #setTabComponentAt 1393 */ 1394 @BeanProperty(preferred = true, visualUpdate = true, description 1395 = "The icon at the specified tab index.") 1396 public void setIconAt(int index, Icon icon) { 1397 Page page = pages.get(index); 1398 Icon oldIcon = page.icon; 1399 if (icon != oldIcon) { 1400 page.icon = icon; 1401 1402 /* If the default icon has really changed and we had 1403 * generated the disabled icon for this page, then 1404 * clear the disabledIcon field of the page. 1405 */ 1406 if (page.disabledIcon instanceof UIResource) { 1407 page.disabledIcon = null; 1408 } 1409 1410 // Fire the accessibility Visible data change 1411 if (accessibleContext != null) { 1412 accessibleContext.firePropertyChange( 1413 AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, 1414 oldIcon, icon); 1415 } 1416 revalidate(); 1417 repaint(); 1418 } 1419 } 1420 1421 /** 1422 * Sets the disabled icon at <code>index</code> to <code>icon</code> 1423 * which can be <code>null</code>. 1424 * An internal exception is raised if there is no tab at that index. 1425 * 1426 * @param index the tab index where the disabled icon should be set 1427 * @param disabledIcon the icon to be displayed in the tab when disabled 1428 * @exception IndexOutOfBoundsException if index is out of range 1429 * {@code (index < 0 || index >= tab count)} 1430 * 1431 * @see #getDisabledIconAt 1432 */ 1433 @BeanProperty(preferred = true, visualUpdate = true, description 1434 = "The disabled icon at the specified tab index.") 1435 public void setDisabledIconAt(int index, Icon disabledIcon) { 1436 Icon oldIcon = pages.get(index).disabledIcon; 1437 pages.get(index).disabledIcon = disabledIcon; 1438 if (disabledIcon != oldIcon && !isEnabledAt(index)) { 1439 revalidate(); 1440 repaint(); 1441 } 1442 } 1443 1444 /** 1445 * Sets the tooltip text at <code>index</code> to <code>toolTipText</code> 1446 * which can be <code>null</code>. 1447 * An internal exception is raised if there is no tab at that index. 1448 * 1449 * @param index the tab index where the tooltip text should be set 1450 * @param toolTipText the tooltip text to be displayed for the tab 1451 * @exception IndexOutOfBoundsException if index is out of range 1452 * {@code (index < 0 || index >= tab count)} 1453 * 1454 * @see #getToolTipTextAt 1455 * @since 1.3 1456 */ 1457 @BeanProperty(preferred = true, description 1458 = "The tooltip text at the specified tab index.") 1459 public void setToolTipTextAt(int index, String toolTipText) { 1460 String oldToolTipText = pages.get(index).tip; 1461 pages.get(index).tip = toolTipText; 1462 1463 if ((oldToolTipText != toolTipText) && (accessibleContext != null)) { 1464 accessibleContext.firePropertyChange( 1465 AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY, 1466 oldToolTipText, toolTipText); 1467 } 1468 if (!haveRegistered && toolTipText != null) { 1469 ToolTipManager.sharedInstance().registerComponent(this); 1470 haveRegistered = true; 1471 } 1472 } 1473 1474 /** 1475 * Sets the background color at <code>index</code> to 1476 * <code>background</code> 1477 * which can be <code>null</code>, in which case the tab's background color 1478 * will default to the background color of the <code>tabbedpane</code>. 1479 * An internal exception is raised if there is no tab at that index. 1480 * <p> 1481 * It is up to the look and feel to honor this property, some may 1482 * choose to ignore it. 1483 * 1484 * @param index the tab index where the background should be set 1485 * @param background the color to be displayed in the tab's background 1486 * @exception IndexOutOfBoundsException if index is out of range 1487 * {@code (index < 0 || index >= tab count)} 1488 * 1489 * @see #getBackgroundAt 1490 */ 1491 @BeanProperty(preferred = true, visualUpdate = true, description 1492 = "The background color at the specified tab index.") 1493 public void setBackgroundAt(int index, Color background) { 1494 Color oldBg = pages.get(index).background; 1495 pages.get(index).setBackground(background); 1496 if (background == null || oldBg == null || 1497 !background.equals(oldBg)) { 1498 Rectangle tabBounds = getBoundsAt(index); 1499 if (tabBounds != null) { 1500 repaint(tabBounds); 1501 } 1502 } 1503 } 1504 1505 /** 1506 * Sets the foreground color at <code>index</code> to 1507 * <code>foreground</code> which can be 1508 * <code>null</code>, in which case the tab's foreground color 1509 * will default to the foreground color of this <code>tabbedpane</code>. 1510 * An internal exception is raised if there is no tab at that index. 1511 * <p> 1512 * It is up to the look and feel to honor this property, some may 1513 * choose to ignore it. 1514 * 1515 * @param index the tab index where the foreground should be set 1516 * @param foreground the color to be displayed as the tab's foreground 1517 * @exception IndexOutOfBoundsException if index is out of range 1518 * {@code (index < 0 || index >= tab count)} 1519 * 1520 * @see #getForegroundAt 1521 */ 1522 @BeanProperty(preferred = true, visualUpdate = true, description 1523 = "The foreground color at the specified tab index.") 1524 public void setForegroundAt(int index, Color foreground) { 1525 Color oldFg = pages.get(index).foreground; 1526 pages.get(index).setForeground(foreground); 1527 if (foreground == null || oldFg == null || 1528 !foreground.equals(oldFg)) { 1529 Rectangle tabBounds = getBoundsAt(index); 1530 if (tabBounds != null) { 1531 repaint(tabBounds); 1532 } 1533 } 1534 } 1535 1536 /** 1537 * Sets whether or not the tab at <code>index</code> is enabled. 1538 * An internal exception is raised if there is no tab at that index. 1539 * 1540 * @param index the tab index which should be enabled/disabled 1541 * @param enabled whether or not the tab should be enabled 1542 * @exception IndexOutOfBoundsException if index is out of range 1543 * {@code (index < 0 || index >= tab count)} 1544 * 1545 * @see #isEnabledAt 1546 */ 1547 public void setEnabledAt(int index, boolean enabled) { 1548 boolean oldEnabled = pages.get(index).isEnabled(); 1549 pages.get(index).setEnabled(enabled); 1550 if (enabled != oldEnabled) { 1551 revalidate(); 1552 repaint(); 1553 } 1554 } 1555 1556 /** 1557 * Sets the component at <code>index</code> to <code>component</code>. 1558 * An internal exception is raised if there is no tab at that index. 1559 * 1560 * @param index the tab index where this component is being placed 1561 * @param component the component for the tab 1562 * @exception IndexOutOfBoundsException if index is out of range 1563 * {@code (index < 0 || index >= tab count)} 1564 * 1565 * @see #getComponentAt 1566 */ 1567 @BeanProperty(visualUpdate = true, description 1568 = "The component at the specified tab index.") 1569 @SuppressWarnings("deprecation") 1570 public void setComponentAt(int index, Component component) { 1571 Page page = pages.get(index); 1572 if (component != page.component) { 1573 boolean shouldChangeFocus = false; 1574 1575 if (page.component != null) { 1576 shouldChangeFocus = 1577 (SwingUtilities.findFocusOwner(page.component) != null); 1578 1579 // REMIND(aim): this is really silly; 1580 // why not if (page.component.getParent() == this) remove(component) 1581 synchronized(getTreeLock()) { 1582 int count = getComponentCount(); 1583 Component[] children = getComponents(); 1584 for (int i = 0; i < count; i++) { 1585 if (children[i] == page.component) { 1586 super.remove(i); 1587 clearAccessibleParent(children[i]); 1588 } 1589 } 1590 } 1591 } 1592 1593 page.component = component; 1594 boolean selectedPage = (getSelectedIndex() == index); 1595 1596 if (selectedPage) { 1597 this.visComp = component; 1598 } 1599 1600 if (component != null) { 1601 component.setVisible(selectedPage); 1602 addImpl(component, null, -1); 1603 1604 if (shouldChangeFocus) { 1605 SwingUtilities2.tabbedPaneChangeFocusTo(component); 1606 } 1607 } else { 1608 repaint(); 1609 } 1610 1611 revalidate(); 1612 } 1613 } 1614 1615 /** 1616 * Provides a hint to the look and feel as to which character in the 1617 * text should be decorated to represent the mnemonic. Not all look and 1618 * feels may support this. A value of -1 indicates either there is 1619 * no mnemonic for this tab, or you do not wish the mnemonic to be 1620 * displayed for this tab. 1621 * <p> 1622 * The value of this is updated as the properties relating to the 1623 * mnemonic change (such as the mnemonic itself, the text...). 1624 * You should only ever have to call this if 1625 * you do not wish the default character to be underlined. For example, if 1626 * the text at tab index 3 was 'Apple Price', with a mnemonic of 'p', 1627 * and you wanted the 'P' 1628 * to be decorated, as 'Apple <u>P</u>rice', you would have to invoke 1629 * <code>setDisplayedMnemonicIndex(3, 6)</code> after invoking 1630 * <code>setMnemonicAt(3, KeyEvent.VK_P)</code>. 1631 * <p>Note that it is the programmer's responsibility to ensure 1632 * that each tab has a unique mnemonic or unpredictable results may 1633 * occur. 1634 * 1635 * @since 1.4 1636 * @param tabIndex the index of the tab that the mnemonic refers to 1637 * @param mnemonicIndex index into the <code>String</code> to underline 1638 * @exception IndexOutOfBoundsException if <code>tabIndex</code> is 1639 * out of range ({@code tabIndex < 0 || tabIndex >= tab 1640 * count}) 1641 * @exception IllegalArgumentException will be thrown if 1642 * <code>mnemonicIndex</code> is >= length of the tab 1643 * title , or < -1 1644 * @see #setMnemonicAt(int,int) 1645 * @see #getDisplayedMnemonicIndexAt(int) 1646 */ 1647 @BeanProperty(visualUpdate = true, description 1648 = "the index into the String to draw the keyboard character mnemonic at") 1649 public void setDisplayedMnemonicIndexAt(int tabIndex, int mnemonicIndex) { 1650 checkIndex(tabIndex); 1651 1652 Page page = pages.get(tabIndex); 1653 1654 page.setDisplayedMnemonicIndex(mnemonicIndex); 1655 } 1656 1657 /** 1658 * Sets the keyboard mnemonic for accessing the specified tab. 1659 * The mnemonic is the key which when combined with the look and feel's 1660 * mouseless modifier (usually Alt) will activate the specified 1661 * tab. 1662 * <p> 1663 * A mnemonic must correspond to a single key on the keyboard 1664 * and should be specified using one of the <code>VK_XXX</code> 1665 * keycodes defined in <code>java.awt.event.KeyEvent</code> 1666 * or one of the extended keycodes obtained through 1667 * <code>java.awt.event.KeyEvent.getExtendedKeyCodeForChar</code>. 1668 * Mnemonics are case-insensitive, therefore a key event 1669 * with the corresponding keycode would cause the button to be 1670 * activated whether or not the Shift modifier was pressed. 1671 * <p> 1672 * This will update the displayed mnemonic property for the specified 1673 * tab. 1674 * 1675 * @since 1.4 1676 * @param tabIndex the index of the tab that the mnemonic refers to 1677 * @param mnemonic the key code which represents the mnemonic 1678 * @exception IndexOutOfBoundsException if <code>tabIndex</code> is out 1679 * of range ({@code tabIndex < 0 || tabIndex >= tab count}) 1680 * @see #getMnemonicAt(int) 1681 * @see #setDisplayedMnemonicIndexAt(int,int) 1682 */ 1683 @BeanProperty(visualUpdate = true, description 1684 = "The keyboard mnenmonic, as a KeyEvent VK constant, for the specified tab") 1685 public void setMnemonicAt(int tabIndex, int mnemonic) { 1686 checkIndex(tabIndex); 1687 1688 Page page = pages.get(tabIndex); 1689 page.setMnemonic(mnemonic); 1690 1691 firePropertyChange("mnemonicAt", null, null); 1692 } 1693 1694 // end of Page setters 1695 1696 /** 1697 * Returns the first tab index with a given <code>title</code>, or 1698 * -1 if no tab has this title. 1699 * 1700 * @param title the title for the tab 1701 * @return the first tab index which matches <code>title</code>, or 1702 * -1 if no tab has this title 1703 */ 1704 public int indexOfTab(String title) { 1705 for(int i = 0; i < getTabCount(); i++) { 1706 if (getTitleAt(i).equals(title == null? "" : title)) { 1707 return i; 1708 } 1709 } 1710 return -1; 1711 } 1712 1713 /** 1714 * Returns the first tab index with a given <code>icon</code>, 1715 * or -1 if no tab has this icon. 1716 * 1717 * @param icon the icon for the tab 1718 * @return the first tab index which matches <code>icon</code>, 1719 * or -1 if no tab has this icon 1720 */ 1721 public int indexOfTab(Icon icon) { 1722 for(int i = 0; i < getTabCount(); i++) { 1723 Icon tabIcon = getIconAt(i); 1724 if ((tabIcon != null && tabIcon.equals(icon)) || 1725 (tabIcon == null && tabIcon == icon)) { 1726 return i; 1727 } 1728 } 1729 return -1; 1730 } 1731 1732 /** 1733 * Returns the index of the tab for the specified component. 1734 * Returns -1 if there is no tab for this component. 1735 * 1736 * @param component the component for the tab 1737 * @return the first tab which matches this component, or -1 1738 * if there is no tab for this component 1739 */ 1740 public int indexOfComponent(Component component) { 1741 for(int i = 0; i < getTabCount(); i++) { 1742 Component c = getComponentAt(i); 1743 if ((c != null && c.equals(component)) || 1744 (c == null && c == component)) { 1745 return i; 1746 } 1747 } 1748 return -1; 1749 } 1750 1751 /** 1752 * Returns the tab index corresponding to the tab whose bounds 1753 * intersect the specified location. Returns -1 if no tab 1754 * intersects the location. 1755 * 1756 * @param x the x location relative to this tabbedpane 1757 * @param y the y location relative to this tabbedpane 1758 * @return the tab index which intersects the location, or 1759 * -1 if no tab intersects the location 1760 * @since 1.4 1761 */ 1762 public int indexAtLocation(int x, int y) { 1763 if (ui != null) { 1764 return ((TabbedPaneUI)ui).tabForCoordinate(this, x, y); 1765 } 1766 return -1; 1767 } 1768 1769 1770 /** 1771 * Returns the tooltip text for the component determined by the 1772 * mouse event location. 1773 * 1774 * @param event the <code>MouseEvent</code> that tells where the 1775 * cursor is lingering 1776 * @return the <code>String</code> containing the tooltip text 1777 */ 1778 public String getToolTipText(MouseEvent event) { 1779 if (ui != null) { 1780 int index = ((TabbedPaneUI)ui).tabForCoordinate(this, event.getX(), event.getY()); 1781 1782 if (index != -1) { 1783 return pages.get(index).tip; 1784 } 1785 } 1786 return super.getToolTipText(event); 1787 } 1788 1789 private void checkIndex(int index) { 1790 if (index < 0 || index >= pages.size()) { 1791 throw new IndexOutOfBoundsException("Index: "+index+", Tab count: "+pages.size()); 1792 } 1793 } 1794 1795 1796 /** 1797 * See <code>readObject</code> and <code>writeObject</code> in 1798 * <code>JComponent</code> for more 1799 * information about serialization in Swing. 1800 */ 1801 private void writeObject(ObjectOutputStream s) throws IOException { 1802 s.defaultWriteObject(); 1803 if (getUIClassID().equals(uiClassID)) { 1804 byte count = JComponent.getWriteObjCounter(this); 1805 JComponent.setWriteObjCounter(this, --count); 1806 if (count == 0 && ui != null) { 1807 ui.installUI(this); 1808 } 1809 } 1810 } 1811 1812 /* Called from the <code>JComponent</code>'s 1813 * <code>EnableSerializationFocusListener</code> to 1814 * do any Swing-specific pre-serialization configuration. 1815 */ 1816 void compWriteObjectNotify() { 1817 super.compWriteObjectNotify(); 1818 // If ToolTipText != null, then the tooltip has already been 1819 // unregistered by JComponent.compWriteObjectNotify() 1820 if (getToolTipText() == null && haveRegistered) { 1821 ToolTipManager.sharedInstance().unregisterComponent(this); 1822 } 1823 } 1824 1825 /** 1826 * See <code>readObject</code> and <code>writeObject</code> in 1827 * <code>JComponent</code> for more 1828 * information about serialization in Swing. 1829 */ 1830 private void readObject(ObjectInputStream s) 1831 throws IOException, ClassNotFoundException 1832 { 1833 ObjectInputStream.GetField f = s.readFields(); 1834 1835 int newTabPlacement = f.get("tabPlacement", TOP); 1836 checkTabPlacement(newTabPlacement); 1837 tabPlacement = newTabPlacement; 1838 int newTabLayoutPolicy = f.get("tabLayoutPolicy", 0); 1839 checkTabLayoutPolicy(newTabLayoutPolicy); 1840 tabLayoutPolicy = newTabLayoutPolicy; 1841 model = (SingleSelectionModel) f.get("model", null); 1842 haveRegistered = f.get("haveRegistered", false); 1843 changeListener = (ChangeListener) f.get("changeListener", null); 1844 visComp = (Component) f.get("visComp", null); 1845 1846 if ((ui != null) && (getUIClassID().equals(uiClassID))) { 1847 ui.installUI(this); 1848 } 1849 // If ToolTipText != null, then the tooltip has already been 1850 // registered by JComponent.readObject() 1851 if (getToolTipText() == null && haveRegistered) { 1852 ToolTipManager.sharedInstance().registerComponent(this); 1853 } 1854 } 1855 1856 1857 /** 1858 * Returns a string representation of this <code>JTabbedPane</code>. 1859 * This method 1860 * is intended to be used only for debugging purposes, and the 1861 * content and format of the returned string may vary between 1862 * implementations. The returned string may be empty but may not 1863 * be <code>null</code>. 1864 * 1865 * @return a string representation of this JTabbedPane. 1866 */ 1867 protected String paramString() { 1868 String tabPlacementString; 1869 if (tabPlacement == TOP) { 1870 tabPlacementString = "TOP"; 1871 } else if (tabPlacement == BOTTOM) { 1872 tabPlacementString = "BOTTOM"; 1873 } else if (tabPlacement == LEFT) { 1874 tabPlacementString = "LEFT"; 1875 } else if (tabPlacement == RIGHT) { 1876 tabPlacementString = "RIGHT"; 1877 } else tabPlacementString = ""; 1878 String haveRegisteredString = (haveRegistered ? 1879 "true" : "false"); 1880 1881 return super.paramString() + 1882 ",haveRegistered=" + haveRegisteredString + 1883 ",tabPlacement=" + tabPlacementString; 1884 } 1885 1886 ///////////////// 1887 // Accessibility support 1888 //////////////// 1889 1890 /** 1891 * Gets the AccessibleContext associated with this JTabbedPane. 1892 * For tabbed panes, the AccessibleContext takes the form of an 1893 * AccessibleJTabbedPane. 1894 * A new AccessibleJTabbedPane instance is created if necessary. 1895 * 1896 * @return an AccessibleJTabbedPane that serves as the 1897 * AccessibleContext of this JTabbedPane 1898 */ 1899 @BeanProperty(bound = false) 1900 public AccessibleContext getAccessibleContext() { 1901 if (accessibleContext == null) { 1902 accessibleContext = new AccessibleJTabbedPane(); 1903 1904 // initialize AccessibleContext for the existing pages 1905 int count = getTabCount(); 1906 for (int i = 0; i < count; i++) { 1907 pages.get(i).initAccessibleContext(); 1908 } 1909 } 1910 return accessibleContext; 1911 } 1912 1913 /** 1914 * This class implements accessibility support for the 1915 * <code>JTabbedPane</code> class. It provides an implementation of the 1916 * Java Accessibility API appropriate to tabbed pane user-interface 1917 * elements. 1918 * <p> 1919 * <strong>Warning:</strong> 1920 * Serialized objects of this class will not be compatible with 1921 * future Swing releases. The current serialization support is 1922 * appropriate for short term storage or RMI between applications running 1923 * the same version of Swing. As of 1.4, support for long term storage 1924 * of all JavaBeans 1925 * has been added to the <code>java.beans</code> package. 1926 * Please see {@link java.beans.XMLEncoder}. 1927 */ 1928 @SuppressWarnings("serial") // Same-version serialization only 1929 protected class AccessibleJTabbedPane extends AccessibleJComponent 1930 implements AccessibleSelection, ChangeListener { 1931 1932 /** 1933 * Returns the accessible name of this object, or {@code null} if 1934 * there is no accessible name. 1935 * 1936 * @return the accessible name of this object, or {@code null}. 1937 * @since 1.6 1938 */ 1939 public String getAccessibleName() { 1940 if (accessibleName != null) { 1941 return accessibleName; 1942 } 1943 1944 String cp = (String)getClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY); 1945 1946 if (cp != null) { 1947 return cp; 1948 } 1949 1950 int index = getSelectedIndex(); 1951 1952 if (index >= 0) { 1953 return pages.get(index).getAccessibleName(); 1954 } 1955 1956 return super.getAccessibleName(); 1957 } 1958 1959 /** 1960 * Constructs an AccessibleJTabbedPane 1961 */ 1962 public AccessibleJTabbedPane() { 1963 super(); 1964 JTabbedPane.this.model.addChangeListener(this); 1965 } 1966 1967 public void stateChanged(ChangeEvent e) { 1968 Object o = e.getSource(); 1969 firePropertyChange(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY, 1970 null, o); 1971 } 1972 1973 /** 1974 * Get the role of this object. 1975 * 1976 * @return an instance of AccessibleRole describing the role of 1977 * the object 1978 */ 1979 public AccessibleRole getAccessibleRole() { 1980 return AccessibleRole.PAGE_TAB_LIST; 1981 } 1982 1983 /** 1984 * Returns the number of accessible children in the object. 1985 * 1986 * @return the number of accessible children in the object. 1987 */ 1988 public int getAccessibleChildrenCount() { 1989 return getTabCount(); 1990 } 1991 1992 /** 1993 * Return the specified Accessible child of the object. 1994 * 1995 * @param i zero-based index of child 1996 * @return the Accessible child of the object 1997 * @exception IllegalArgumentException if index is out of bounds 1998 */ 1999 public Accessible getAccessibleChild(int i) { 2000 if (i < 0 || i >= getTabCount()) { 2001 return null; 2002 } 2003 return pages.get(i); 2004 } 2005 2006 /** 2007 * Gets the <code>AccessibleSelection</code> associated with 2008 * this object. In the implementation of the Java 2009 * Accessibility API for this class, 2010 * returns this object, which is responsible for implementing the 2011 * <code>AccessibleSelection</code> interface on behalf of itself. 2012 * 2013 * @return this object 2014 */ 2015 public AccessibleSelection getAccessibleSelection() { 2016 return this; 2017 } 2018 2019 /** 2020 * Returns the <code>Accessible</code> child contained at 2021 * the local coordinate <code>Point</code>, if one exists. 2022 * Otherwise returns the currently selected tab. 2023 * 2024 * @return the <code>Accessible</code> at the specified 2025 * location, if it exists 2026 */ 2027 public Accessible getAccessibleAt(Point p) { 2028 int tab = ((TabbedPaneUI) ui).tabForCoordinate(JTabbedPane.this, 2029 p.x, p.y); 2030 if (tab == -1) { 2031 tab = getSelectedIndex(); 2032 } 2033 return getAccessibleChild(tab); 2034 } 2035 2036 public int getAccessibleSelectionCount() { 2037 return 1; 2038 } 2039 2040 public Accessible getAccessibleSelection(int i) { 2041 int index = getSelectedIndex(); 2042 if (index == -1) { 2043 return null; 2044 } 2045 return pages.get(index); 2046 } 2047 2048 public boolean isAccessibleChildSelected(int i) { 2049 return (i == getSelectedIndex()); 2050 } 2051 2052 public void addAccessibleSelection(int i) { 2053 setSelectedIndex(i); 2054 } 2055 2056 public void removeAccessibleSelection(int i) { 2057 // can't do 2058 } 2059 2060 public void clearAccessibleSelection() { 2061 // can't do 2062 } 2063 2064 public void selectAllAccessibleSelection() { 2065 // can't do 2066 } 2067 } 2068 2069 private class Page extends AccessibleContext 2070 implements Serializable, Accessible, AccessibleComponent { 2071 String title; 2072 Color background; 2073 Color foreground; 2074 Icon icon; 2075 Icon disabledIcon; 2076 JTabbedPane parent; 2077 Component component; 2078 String tip; 2079 boolean enabled = true; 2080 boolean needsUIUpdate; 2081 int mnemonic = -1; 2082 int mnemonicIndex = -1; 2083 Component tabComponent; 2084 2085 Page(JTabbedPane parent, 2086 String title, Icon icon, Icon disabledIcon, Component component, String tip) { 2087 this.title = title; 2088 this.icon = icon; 2089 this.disabledIcon = disabledIcon; 2090 this.parent = parent; 2091 this.setAccessibleParent(parent); 2092 this.component = component; 2093 this.tip = tip; 2094 2095 initAccessibleContext(); 2096 } 2097 2098 /* 2099 * initializes the AccessibleContext for the page 2100 */ 2101 void initAccessibleContext() { 2102 if (JTabbedPane.this.accessibleContext != null && 2103 component instanceof Accessible) { 2104 /* 2105 * Do initialization if the AccessibleJTabbedPane 2106 * has been instantiated. We do not want to load 2107 * Accessibility classes unnecessarily. 2108 */ 2109 AccessibleContext ac; 2110 ac = component.getAccessibleContext(); 2111 if (ac != null) { 2112 ac.setAccessibleParent(this); 2113 } 2114 } 2115 } 2116 2117 void setMnemonic(int mnemonic) { 2118 this.mnemonic = mnemonic; 2119 updateDisplayedMnemonicIndex(); 2120 } 2121 2122 int getMnemonic() { 2123 return mnemonic; 2124 } 2125 2126 /* 2127 * Sets the page displayed mnemonic index 2128 */ 2129 void setDisplayedMnemonicIndex(int mnemonicIndex) { 2130 if (this.mnemonicIndex != mnemonicIndex) { 2131 String t = getTitle(); 2132 if (mnemonicIndex != -1 && (t == null || 2133 mnemonicIndex < 0 || 2134 mnemonicIndex >= t.length())) { 2135 throw new IllegalArgumentException( 2136 "Invalid mnemonic index: " + mnemonicIndex); 2137 } 2138 this.mnemonicIndex = mnemonicIndex; 2139 JTabbedPane.this.firePropertyChange("displayedMnemonicIndexAt", 2140 null, null); 2141 } 2142 } 2143 2144 /* 2145 * Returns the page displayed mnemonic index 2146 */ 2147 int getDisplayedMnemonicIndex() { 2148 return this.mnemonicIndex; 2149 } 2150 2151 void updateDisplayedMnemonicIndex() { 2152 setDisplayedMnemonicIndex( 2153 SwingUtilities.findDisplayedMnemonicIndex(getTitle(), mnemonic)); 2154 } 2155 2156 ///////////////// 2157 // Accessibility support 2158 //////////////// 2159 2160 public AccessibleContext getAccessibleContext() { 2161 return this; 2162 } 2163 2164 2165 // AccessibleContext methods 2166 2167 public String getAccessibleName() { 2168 if (accessibleName != null) { 2169 return accessibleName; 2170 } else { 2171 return getTitle(); 2172 } 2173 } 2174 2175 public String getAccessibleDescription() { 2176 if (accessibleDescription != null) { 2177 return accessibleDescription; 2178 } else if (tip != null) { 2179 return tip; 2180 } 2181 return null; 2182 } 2183 2184 public AccessibleRole getAccessibleRole() { 2185 return AccessibleRole.PAGE_TAB; 2186 } 2187 2188 public AccessibleStateSet getAccessibleStateSet() { 2189 AccessibleStateSet states; 2190 states = parent.getAccessibleContext().getAccessibleStateSet(); 2191 states.add(AccessibleState.SELECTABLE); 2192 if (getPageIndex() == parent.getSelectedIndex()) { 2193 states.add(AccessibleState.SELECTED); 2194 } 2195 return states; 2196 } 2197 2198 public int getAccessibleIndexInParent() { 2199 return getPageIndex(); 2200 } 2201 2202 public int getAccessibleChildrenCount() { 2203 if (component instanceof Accessible) { 2204 return 1; 2205 } else { 2206 return 0; 2207 } 2208 } 2209 2210 public Accessible getAccessibleChild(int i) { 2211 if (component instanceof Accessible) { 2212 return (Accessible) component; 2213 } else { 2214 return null; 2215 } 2216 } 2217 2218 public Locale getLocale() { 2219 return parent.getLocale(); 2220 } 2221 2222 public AccessibleComponent getAccessibleComponent() { 2223 return this; 2224 } 2225 2226 2227 // AccessibleComponent methods 2228 2229 public Color getBackground() { 2230 return background != null? background : parent.getBackground(); 2231 } 2232 2233 public void setBackground(Color c) { 2234 background = c; 2235 } 2236 2237 public Color getForeground() { 2238 return foreground != null? foreground : parent.getForeground(); 2239 } 2240 2241 public void setForeground(Color c) { 2242 foreground = c; 2243 } 2244 2245 public Cursor getCursor() { 2246 return parent.getCursor(); 2247 } 2248 2249 public void setCursor(Cursor c) { 2250 parent.setCursor(c); 2251 } 2252 2253 public Font getFont() { 2254 return parent.getFont(); 2255 } 2256 2257 public void setFont(Font f) { 2258 parent.setFont(f); 2259 } 2260 2261 public FontMetrics getFontMetrics(Font f) { 2262 return parent.getFontMetrics(f); 2263 } 2264 2265 public boolean isEnabled() { 2266 return enabled; 2267 } 2268 2269 public void setEnabled(boolean b) { 2270 enabled = b; 2271 } 2272 2273 public boolean isVisible() { 2274 return parent.isVisible(); 2275 } 2276 2277 public void setVisible(boolean b) { 2278 parent.setVisible(b); 2279 } 2280 2281 public boolean isShowing() { 2282 return parent.isShowing(); 2283 } 2284 2285 public boolean contains(Point p) { 2286 Rectangle r = getBounds(); 2287 return r.contains(p); 2288 } 2289 2290 public Point getLocationOnScreen() { 2291 Point parentLocation = parent.getLocationOnScreen(); 2292 Point componentLocation = getLocation(); 2293 componentLocation.translate(parentLocation.x, parentLocation.y); 2294 return componentLocation; 2295 } 2296 2297 public Point getLocation() { 2298 Rectangle r = getBounds(); 2299 return new Point(r.x, r.y); 2300 } 2301 2302 public void setLocation(Point p) { 2303 // do nothing 2304 } 2305 2306 public Rectangle getBounds() { 2307 return parent.getUI().getTabBounds(parent, getPageIndex()); 2308 } 2309 2310 public void setBounds(Rectangle r) { 2311 // do nothing 2312 } 2313 2314 public Dimension getSize() { 2315 Rectangle r = getBounds(); 2316 return new Dimension(r.width, r.height); 2317 } 2318 2319 public void setSize(Dimension d) { 2320 // do nothing 2321 } 2322 2323 public Accessible getAccessibleAt(Point p) { 2324 if (component instanceof Accessible) { 2325 return (Accessible) component; 2326 } else { 2327 return null; 2328 } 2329 } 2330 2331 public boolean isFocusTraversable() { 2332 return false; 2333 } 2334 2335 public void requestFocus() { 2336 // do nothing 2337 } 2338 2339 public void addFocusListener(FocusListener l) { 2340 // do nothing 2341 } 2342 2343 public void removeFocusListener(FocusListener l) { 2344 // do nothing 2345 } 2346 2347 // TIGER - 4732339 2348 /** 2349 * Returns an AccessibleIcon 2350 * 2351 * @return the enabled icon if one exists and the page 2352 * is enabled. Otherwise, returns the disabled icon if 2353 * one exists and the page is disabled. Otherwise, null 2354 * is returned. 2355 */ 2356 public AccessibleIcon [] getAccessibleIcon() { 2357 AccessibleIcon accessibleIcon = null; 2358 if (enabled && icon instanceof ImageIcon) { 2359 AccessibleContext ac = 2360 ((ImageIcon)icon).getAccessibleContext(); 2361 accessibleIcon = (AccessibleIcon)ac; 2362 } else if (!enabled && disabledIcon instanceof ImageIcon) { 2363 AccessibleContext ac = 2364 ((ImageIcon)disabledIcon).getAccessibleContext(); 2365 accessibleIcon = (AccessibleIcon)ac; 2366 } 2367 if (accessibleIcon != null) { 2368 AccessibleIcon [] returnIcons = new AccessibleIcon[1]; 2369 returnIcons[0] = accessibleIcon; 2370 return returnIcons; 2371 } else { 2372 return null; 2373 } 2374 } 2375 2376 private String getTitle() { 2377 return getTitleAt(getPageIndex()); 2378 } 2379 2380 /* 2381 * getPageIndex() has three valid scenarios: 2382 * - null component and null tabComponent: use indexOfcomponent 2383 * - non-null component: use indexOfComponent 2384 * - null component and non-null tabComponent: use indexOfTabComponent 2385 * 2386 * Note: It's valid to have have a titled tab with a null component, e.g. 2387 * myPane.add("my title", null); 2388 * but it's only useful to have one of those because indexOfComponent(null) 2389 * will find the first one. 2390 * 2391 * Note: indexofTab(title) is not useful because there are cases, due to 2392 * subclassing, where Page.title is not set and title is managed in a subclass 2393 * and fetched with an overridden JTabbedPane.getTitleAt(index). 2394 */ 2395 private int getPageIndex() { 2396 int index; 2397 if (component != null || (component == null && tabComponent == null)) { 2398 index = parent.indexOfComponent(component); 2399 } else { 2400 // component is null, tabComponent is non-null 2401 index = parent.indexOfTabComponent(tabComponent); 2402 } 2403 return index; 2404 } 2405 2406 } 2407 2408 /** 2409 * Sets the component that is responsible for rendering the 2410 * title for the specified tab. A null value means 2411 * <code>JTabbedPane</code> will render the title and/or icon for 2412 * the specified tab. A non-null value means the component will 2413 * render the title and <code>JTabbedPane</code> will not render 2414 * the title and/or icon. 2415 * <p> 2416 * Note: The component must not be one that the developer has 2417 * already added to the tabbed pane. 2418 * 2419 * @param index the tab index where the component should be set 2420 * @param component the component to render the title for the 2421 * specified tab 2422 * @exception IndexOutOfBoundsException if index is out of range 2423 * {@code (index < 0 || index >= tab count)} 2424 * @exception IllegalArgumentException if component has already been 2425 * added to this <code>JTabbedPane</code> 2426 * 2427 * @see #getTabComponentAt 2428 * @since 1.6 2429 */ 2430 @BeanProperty(preferred = true, visualUpdate = true, description 2431 = "The tab component at the specified tab index.") 2432 public void setTabComponentAt(int index, Component component) { 2433 if (component != null && indexOfComponent(component) != -1) { 2434 throw new IllegalArgumentException("Component is already added to this JTabbedPane"); 2435 } 2436 Component oldValue = getTabComponentAt(index); 2437 if (component != oldValue) { 2438 int tabComponentIndex = indexOfTabComponent(component); 2439 if (tabComponentIndex != -1) { 2440 setTabComponentAt(tabComponentIndex, null); 2441 } 2442 pages.get(index).tabComponent = component; 2443 firePropertyChange("indexForTabComponent", -1, index); 2444 } 2445 } 2446 2447 /** 2448 * Returns the tab component at <code>index</code>. 2449 * 2450 * @param index the index of the item being queried 2451 * @return the tab component at <code>index</code> 2452 * @exception IndexOutOfBoundsException if index is out of range 2453 * {@code (index < 0 || index >= tab count)} 2454 * 2455 * @see #setTabComponentAt 2456 * @since 1.6 2457 */ 2458 public Component getTabComponentAt(int index) { 2459 return pages.get(index).tabComponent; 2460 } 2461 2462 /** 2463 * Returns the index of the tab for the specified tab component. 2464 * Returns -1 if there is no tab for this tab component. 2465 * 2466 * @param tabComponent the tab component for the tab 2467 * @return the first tab which matches this tab component, or -1 2468 * if there is no tab for this tab component 2469 * @see #setTabComponentAt 2470 * @see #getTabComponentAt 2471 * @since 1.6 2472 */ 2473 public int indexOfTabComponent(Component tabComponent) { 2474 for(int i = 0; i < getTabCount(); i++) { 2475 Component c = getTabComponentAt(i); 2476 if (c == tabComponent) { 2477 return i; 2478 } 2479 } 2480 return -1; 2481 } 2482 }