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