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