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