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