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