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