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