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