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