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