1/*
2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.swing.plaf.basic;
27
28import sun.swing.SwingUtilities2;
29
30import javax.swing.*;
31import javax.swing.event.*;
32import javax.swing.plaf.*;
33import javax.swing.text.View;
34
35import java.awt.*;
36import java.awt.event.*;
37import java.beans.PropertyChangeListener;
38import java.beans.PropertyChangeEvent;
39import java.util.Vector;
40import java.util.Hashtable;
41
42import sun.swing.DefaultLookup;
43import sun.swing.UIAction;
44
45/**
46 * A Basic L&F implementation of TabbedPaneUI.
47 *
48 * @author Amy Fowler
49 * @author Philip Milne
50 * @author Steve Wilson
51 * @author Tom Santos
52 * @author Dave Moore
53 */
54public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants {
55
56
57// Instance variables initialized at installation
58
59    /** The tab pane */
60    protected JTabbedPane tabPane;
61
62    /** Highlight color */
63    protected Color highlight;
64    /** Light highlight color */
65    protected Color lightHighlight;
66    /** Shadow color */
67    protected Color shadow;
68    /** Dark shadow color */
69    protected Color darkShadow;
70    /** Focus color */
71    protected Color focus;
72    private   Color selectedColor;
73
74    /** Text icon gap */
75    protected int textIconGap;
76    /** Tab run overlay */
77    protected int tabRunOverlay;
78
79    /** Tab insets */
80    protected Insets tabInsets;
81    /** Selected tab insets */
82    protected Insets selectedTabPadInsets;
83    /** Tab area insets */
84    protected Insets tabAreaInsets;
85    /** Content border insets */
86    protected Insets contentBorderInsets;
87    private boolean tabsOverlapBorder;
88    private boolean tabsOpaque = true;
89    private boolean contentOpaque = true;
90
91    /**
92     * As of Java 2 platform v1.3 this previously undocumented field is no
93     * longer used.
94     * Key bindings are now defined by the LookAndFeel, please refer to
95     * the key bindings specification for further details.
96     *
97     * @deprecated As of Java 2 platform v1.3.
98     */
99    @Deprecated
100    protected KeyStroke upKey;
101    /**
102     * As of Java 2 platform v1.3 this previously undocumented field is no
103     * longer used.
104     * Key bindings are now defined by the LookAndFeel, please refer to
105     * the key bindings specification for further details.
106     *
107     * @deprecated As of Java 2 platform v1.3.
108     */
109    @Deprecated
110    protected KeyStroke downKey;
111    /**
112     * As of Java 2 platform v1.3 this previously undocumented field is no
113     * longer used.
114     * Key bindings are now defined by the LookAndFeel, please refer to
115     * the key bindings specification for further details.
116     *
117     * @deprecated As of Java 2 platform v1.3.
118     */
119    @Deprecated
120    protected KeyStroke leftKey;
121    /**
122     * As of Java 2 platform v1.3 this previously undocumented field is no
123     * longer used.
124     * Key bindings are now defined by the LookAndFeel, please refer to
125     * the key bindings specification for further details.
126     *
127     * @deprecated As of Java 2 platform v1.3.
128     */
129    @Deprecated
130    protected KeyStroke rightKey;
131
132
133// Transient variables (recalculated each time TabbedPane is layed out)
134    /** Tab runs */
135    protected int tabRuns[] = new int[10];
136    /** Run count */
137    protected int runCount = 0;
138    /** Selected run */
139    protected int selectedRun = -1;
140    /** Tab rects */
141    protected Rectangle rects[] = new Rectangle[0];
142    /** Maximum tab height */
143    protected int maxTabHeight;
144    /** Maximum tab width */
145    protected int maxTabWidth;
146
147// Listeners
148
149    /** Tab change listener */
150    protected ChangeListener tabChangeListener;
151    /** Property change listener */
152    protected PropertyChangeListener propertyChangeListener;
153    /** Mouse change listener */
154    protected MouseListener mouseListener;
155    /** Focus change listener */
156    protected FocusListener focusListener;
157
158// Private instance data
159
160    private Insets currentPadInsets = new Insets(0,0,0,0);
161    private Insets currentTabAreaInsets = new Insets(0,0,0,0);
162
163    private Component visibleComponent;
164    // PENDING(api): See comment for ContainerHandler
165    private Vector<View> htmlViews;
166
167    private Hashtable<Integer, Integer> mnemonicToIndexMap;
168
169    /**
170     * InputMap used for mnemonics. Only non-null if the JTabbedPane has
171     * mnemonics associated with it. Lazily created in initMnemonics.
172     */
173    private InputMap mnemonicInputMap;
174
175    // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
176    private ScrollableTabSupport tabScroller;
177
178    private TabContainer tabContainer;
179
180    /**
181     * A rectangle used for general layout calculations in order
182     * to avoid constructing many new Rectangles on the fly.
183     */
184    protected transient Rectangle calcRect = new Rectangle(0,0,0,0);
185
186    /**
187     * Tab that has focus.
188     */
189    private int focusIndex;
190
191    /**
192     * Combined listeners.
193     */
194    private Handler handler;
195
196    /**
197     * Index of the tab the mouse is over.
198     */
199    private int rolloverTabIndex;
200
201    /**
202     * This is set to true when a component is added/removed from the tab
203     * pane and set to false when layout happens.  If true it indicates that
204     * tabRuns is not valid and shouldn't be used.
205     */
206    private boolean isRunsDirty;
207
208    private boolean calculatedBaseline;
209    private int baseline;
210
211// UI creation
212
213    /**
214     * Create a UI.
215     * @param c a component
216     * @return a UI
217     */
218    public static ComponentUI createUI(JComponent c) {
219        return new BasicTabbedPaneUI();
220    }
221
222    static void loadActionMap(LazyActionMap map) {
223        map.put(new Actions(Actions.NEXT));
224        map.put(new Actions(Actions.PREVIOUS));
225        map.put(new Actions(Actions.RIGHT));
226        map.put(new Actions(Actions.LEFT));
227        map.put(new Actions(Actions.UP));
228        map.put(new Actions(Actions.DOWN));
229        map.put(new Actions(Actions.PAGE_UP));
230        map.put(new Actions(Actions.PAGE_DOWN));
231        map.put(new Actions(Actions.REQUEST_FOCUS));
232        map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE));
233        map.put(new Actions(Actions.SET_SELECTED));
234        map.put(new Actions(Actions.SELECT_FOCUSED));
235        map.put(new Actions(Actions.SCROLL_FORWARD));
236        map.put(new Actions(Actions.SCROLL_BACKWARD));
237    }
238
239// UI Installation/De-installation
240
241    public void installUI(JComponent c) {
242        this.tabPane = (JTabbedPane)c;
243
244        calculatedBaseline = false;
245        rolloverTabIndex = -1;
246        focusIndex = -1;
247        c.setLayout(createLayoutManager());
248        installComponents();
249        installDefaults();
250        installListeners();
251        installKeyboardActions();
252    }
253
254    public void uninstallUI(JComponent c) {
255        uninstallKeyboardActions();
256        uninstallListeners();
257        uninstallDefaults();
258        uninstallComponents();
259        c.setLayout(null);
260
261        this.tabPane = null;
262    }
263
264    /**
265     * Invoked by <code>installUI</code> to create
266     * a layout manager object to manage
267     * the <code>JTabbedPane</code>.
268     *
269     * @return a layout manager object
270     *
271     * @see TabbedPaneLayout
272     * @see javax.swing.JTabbedPane#getTabLayoutPolicy
273     */
274    protected LayoutManager createLayoutManager() {
275        if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
276            return new TabbedPaneScrollLayout();
277        } else { /* WRAP_TAB_LAYOUT */
278            return new TabbedPaneLayout();
279        }
280    }
281
282    /* In an attempt to preserve backward compatibility for programs
283     * which have extended BasicTabbedPaneUI to do their own layout, the
284     * UI uses the installed layoutManager (and not tabLayoutPolicy) to
285     * determine if scrollTabLayout is enabled.
286     */
287    private boolean scrollableTabLayoutEnabled() {
288        return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
289    }
290
291    /**
292     * Creates and installs any required subcomponents for the JTabbedPane.
293     * Invoked by installUI.
294     *
295     * @since 1.4
296     */
297    protected void installComponents() {
298        if (scrollableTabLayoutEnabled()) {
299            if (tabScroller == null) {
300                tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
301                tabPane.add(tabScroller.viewport);
302            }
303        }
304        installTabContainer();
305    }
306
307    private void installTabContainer() {
308         for (int i = 0; i < tabPane.getTabCount(); i++) {
309             Component tabComponent = tabPane.getTabComponentAt(i);
310             if (tabComponent != null) {
311                 if(tabContainer == null) {
312                     tabContainer = new TabContainer();
313                 }
314                 tabContainer.add(tabComponent);
315             }
316         }
317         if(tabContainer == null) {
318             return;
319         }
320         if (scrollableTabLayoutEnabled()) {
321             tabScroller.tabPanel.add(tabContainer);
322         } else {
323             tabPane.add(tabContainer);
324         }
325    }
326
327    /**
328     * Creates and returns a JButton that will provide the user
329     * with a way to scroll the tabs in a particular direction. The
330     * returned JButton must be instance of UIResource.
331     *
332     * @param direction One of the SwingConstants constants:
333     * SOUTH, NORTH, EAST or WEST
334     * @return Widget for user to
335     * @see javax.swing.JTabbedPane#setTabPlacement
336     * @see javax.swing.SwingConstants
337     * @throws IllegalArgumentException if direction is not one of
338     *         NORTH, SOUTH, EAST or WEST
339     * @since 1.5
340     */
341    protected JButton createScrollButton(int direction) {
342        if (direction != SOUTH && direction != NORTH && direction != EAST &&
343                                  direction != WEST) {
344            throw new IllegalArgumentException("Direction must be one of: " +
345                                               "SOUTH, NORTH, EAST or WEST");
346        }
347        return new ScrollableTabButton(direction);
348    }
349
350    /**
351     * Removes any installed subcomponents from the JTabbedPane.
352     * Invoked by uninstallUI.
353     *
354     * @since 1.4
355     */
356    protected void uninstallComponents() {
357        uninstallTabContainer();
358        if (scrollableTabLayoutEnabled()) {
359            tabPane.remove(tabScroller.viewport);
360            tabPane.remove(tabScroller.scrollForwardButton);
361            tabPane.remove(tabScroller.scrollBackwardButton);
362            tabScroller = null;
363        }
364    }
365
366    private void uninstallTabContainer() {
367         if(tabContainer == null) {
368             return;
369         }
370         // Remove all the tabComponents, making sure not to notify
371         // the tabbedpane.
372         tabContainer.notifyTabbedPane = false;
373         tabContainer.removeAll();
374         if(scrollableTabLayoutEnabled()) {
375             tabContainer.remove(tabScroller.croppedEdge);
376             tabScroller.tabPanel.remove(tabContainer);
377         } else {
378           tabPane.remove(tabContainer);
379         }
380         tabContainer = null;
381    }
382
383    /**
384     * Install the defaults.
385     */
386    protected void installDefaults() {
387        LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
388                                    "TabbedPane.foreground", "TabbedPane.font");
389        highlight = UIManager.getColor("TabbedPane.light");
390        lightHighlight = UIManager.getColor("TabbedPane.highlight");
391        shadow = UIManager.getColor("TabbedPane.shadow");
392        darkShadow = UIManager.getColor("TabbedPane.darkShadow");
393        focus = UIManager.getColor("TabbedPane.focus");
394        selectedColor = UIManager.getColor("TabbedPane.selected");
395
396        textIconGap = UIManager.getInt("TabbedPane.textIconGap");
397        tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
398        selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
399        tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
400        tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder");
401        contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
402        tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
403        tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
404        contentOpaque = UIManager.getBoolean("TabbedPane.contentOpaque");
405        Object opaque = UIManager.get("TabbedPane.opaque");
406        if (opaque == null) {
407            opaque = Boolean.FALSE;
408        }
409        LookAndFeel.installProperty(tabPane, "opaque", opaque);
410
411        // Fix for 6711145 BasicTabbedPanuUI should not throw a NPE if these
412        // keys are missing. So we are setting them to there default values here
413        // if the keys are missing.
414        if (tabInsets == null) tabInsets = new Insets(0,4,1,4);
415        if (selectedTabPadInsets == null) selectedTabPadInsets = new Insets(2,2,2,1);
416        if (tabAreaInsets == null) tabAreaInsets = new Insets(3,2,0,2);
417        if (contentBorderInsets == null) contentBorderInsets = new Insets(2,2,3,3);
418    }
419
420    /**
421     * Uninstall the defaults.
422     */
423    protected void uninstallDefaults() {
424        highlight = null;
425        lightHighlight = null;
426        shadow = null;
427        darkShadow = null;
428        focus = null;
429        tabInsets = null;
430        selectedTabPadInsets = null;
431        tabAreaInsets = null;
432        contentBorderInsets = null;
433    }
434
435    /**
436     * Install the listeners.
437     */
438    protected void installListeners() {
439        if ((propertyChangeListener = createPropertyChangeListener()) != null) {
440            tabPane.addPropertyChangeListener(propertyChangeListener);
441        }
442        if ((tabChangeListener = createChangeListener()) != null) {
443            tabPane.addChangeListener(tabChangeListener);
444        }
445        if ((mouseListener = createMouseListener()) != null) {
446            tabPane.addMouseListener(mouseListener);
447        }
448        tabPane.addMouseMotionListener(getHandler());
449        if ((focusListener = createFocusListener()) != null) {
450            tabPane.addFocusListener(focusListener);
451        }
452        tabPane.addContainerListener(getHandler());
453        if (tabPane.getTabCount()>0) {
454            htmlViews = createHTMLVector();
455        }
456    }
457
458    /**
459     * Uninstall the listeners.
460     */
461    protected void uninstallListeners() {
462        if (mouseListener != null) {
463            tabPane.removeMouseListener(mouseListener);
464            mouseListener = null;
465        }
466        tabPane.removeMouseMotionListener(getHandler());
467        if (focusListener != null) {
468            tabPane.removeFocusListener(focusListener);
469            focusListener = null;
470        }
471
472        tabPane.removeContainerListener(getHandler());
473        if (htmlViews!=null) {
474            htmlViews.removeAllElements();
475            htmlViews = null;
476        }
477        if (tabChangeListener != null) {
478            tabPane.removeChangeListener(tabChangeListener);
479            tabChangeListener = null;
480        }
481        if (propertyChangeListener != null) {
482            tabPane.removePropertyChangeListener(propertyChangeListener);
483            propertyChangeListener = null;
484        }
485        handler = null;
486    }
487
488    /**
489     * Creates a mouse listener.
490     * @return a mouse listener
491     */
492    protected MouseListener createMouseListener() {
493        return getHandler();
494    }
495
496    /**
497     * Creates a focus listener.
498     * @return a focus listener
499     */
500    protected FocusListener createFocusListener() {
501        return getHandler();
502    }
503
504    /**
505     * Creates a change listener.
506     * @return a change listener
507     */
508    protected ChangeListener createChangeListener() {
509        return getHandler();
510    }
511
512    /**
513     * Creates a property change listener.
514     * @return a property change listener
515     */
516    protected PropertyChangeListener createPropertyChangeListener() {
517        return getHandler();
518    }
519
520    private Handler getHandler() {
521        if (handler == null) {
522            handler = new Handler();
523        }
524        return handler;
525    }
526
527    /**
528     * Installs the keyboard actions.
529     */
530    protected void installKeyboardActions() {
531        InputMap km = getInputMap(JComponent.
532                                  WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
533
534        SwingUtilities.replaceUIInputMap(tabPane, JComponent.
535                                         WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
536                                         km);
537        km = getInputMap(JComponent.WHEN_FOCUSED);
538        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
539
540        LazyActionMap.installLazyActionMap(tabPane, BasicTabbedPaneUI.class,
541                                           "TabbedPane.actionMap");
542        updateMnemonics();
543    }
544
545    InputMap getInputMap(int condition) {
546        if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
547            return (InputMap)DefaultLookup.get(tabPane, this,
548                                               "TabbedPane.ancestorInputMap");
549        }
550        else if (condition == JComponent.WHEN_FOCUSED) {
551            return (InputMap)DefaultLookup.get(tabPane, this,
552                                               "TabbedPane.focusInputMap");
553        }
554        return null;
555    }
556
557    /**
558     * Uninstalls the keyboard actions.
559     */
560    protected void uninstallKeyboardActions() {
561        SwingUtilities.replaceUIActionMap(tabPane, null);
562        SwingUtilities.replaceUIInputMap(tabPane, JComponent.
563                                         WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
564                                         null);
565        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED,
566                                         null);
567        SwingUtilities.replaceUIInputMap(tabPane,
568                                         JComponent.WHEN_IN_FOCUSED_WINDOW,
569                                         null);
570        mnemonicToIndexMap = null;
571        mnemonicInputMap = null;
572    }
573
574    /**
575     * Reloads the mnemonics. This should be invoked when a memonic changes,
576     * when the title of a mnemonic changes, or when tabs are added/removed.
577     */
578    private void updateMnemonics() {
579        resetMnemonics();
580        for (int counter = tabPane.getTabCount() - 1; counter >= 0;
581             counter--) {
582            int mnemonic = tabPane.getMnemonicAt(counter);
583
584            if (mnemonic > 0) {
585                addMnemonic(counter, mnemonic);
586            }
587        }
588    }
589
590    /**
591     * Resets the mnemonics bindings to an empty state.
592     */
593    private void resetMnemonics() {
594        if (mnemonicToIndexMap != null) {
595            mnemonicToIndexMap.clear();
596            mnemonicInputMap.clear();
597        }
598    }
599
600    /**
601     * Adds the specified mnemonic at the specified index.
602     */
603    private void addMnemonic(int index, int mnemonic) {
604        if (mnemonicToIndexMap == null) {
605            initMnemonics();
606        }
607        mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, BasicLookAndFeel.getFocusAcceleratorKeyMask()),
608                             "setSelectedIndex");
609        mnemonicToIndexMap.put(Integer.valueOf(mnemonic), Integer.valueOf(index));
610    }
611
612    /**
613     * Installs the state needed for mnemonics.
614     */
615    private void initMnemonics() {
616        mnemonicToIndexMap = new Hashtable<Integer, Integer>();
617        mnemonicInputMap = new ComponentInputMapUIResource(tabPane);
618        mnemonicInputMap.setParent(SwingUtilities.getUIInputMap(tabPane,
619                              JComponent.WHEN_IN_FOCUSED_WINDOW));
620        SwingUtilities.replaceUIInputMap(tabPane,
621                              JComponent.WHEN_IN_FOCUSED_WINDOW,
622                                         mnemonicInputMap);
623    }
624
625    /**
626     * Sets the tab the mouse is over by location. This is a cover method
627     * for <code>setRolloverTab(tabForCoordinate(x, y, false))</code>.
628     */
629    private void setRolloverTab(int x, int y) {
630        // NOTE:
631        // This calls in with false otherwise it could trigger a validate,
632        // which should NOT happen if the user is only dragging the
633        // mouse around.
634        setRolloverTab(tabForCoordinate(tabPane, x, y, false));
635    }
636
637    /**
638     * Sets the tab the mouse is currently over to <code>index</code>.
639     * <code>index</code> will be -1 if the mouse is no longer over any
640     * tab. No checking is done to ensure the passed in index identifies a
641     * valid tab.
642     *
643     * @param index Index of the tab the mouse is over.
644     * @since 1.5
645     */
646    protected void setRolloverTab(int index) {
647        rolloverTabIndex = index;
648    }
649
650    /**
651     * Returns the tab the mouse is currently over, or {@code -1} if the mouse is no
652     * longer over any tab.
653     *
654     * @return the tab the mouse is currently over, or {@code -1} if the mouse is no
655     * longer over any tab
656     * @since 1.5
657     */
658    protected int getRolloverTab() {
659        return rolloverTabIndex;
660    }
661
662    public Dimension getMinimumSize(JComponent c) {
663        // Default to LayoutManager's minimumLayoutSize
664        return null;
665    }
666
667    public Dimension getMaximumSize(JComponent c) {
668        // Default to LayoutManager's maximumLayoutSize
669        return null;
670    }
671
672    /**
673     * Returns the baseline.
674     *
675     * @throws NullPointerException {@inheritDoc}
676     * @throws IllegalArgumentException {@inheritDoc}
677     * @see javax.swing.JComponent#getBaseline(int, int)
678     * @since 1.6
679     */
680    public int getBaseline(JComponent c, int width, int height) {
681        super.getBaseline(c, width, height);
682        int baseline = calculateBaselineIfNecessary();
683        if (baseline != -1) {
684            int placement = tabPane.getTabPlacement();
685            Insets insets = tabPane.getInsets();
686            Insets tabAreaInsets = getTabAreaInsets(placement);
687            switch(placement) {
688            case JTabbedPane.TOP:
689                baseline += insets.top + tabAreaInsets.top;
690                return baseline;
691            case JTabbedPane.BOTTOM:
692                baseline = height - insets.bottom -
693                    tabAreaInsets.bottom - maxTabHeight + baseline;
694                return baseline;
695            case JTabbedPane.LEFT:
696            case JTabbedPane.RIGHT:
697                baseline += insets.top + tabAreaInsets.top;
698                return baseline;
699            }
700        }
701        return -1;
702    }
703
704    /**
705     * Returns an enum indicating how the baseline of the component
706     * changes as the size changes.
707     *
708     * @throws NullPointerException {@inheritDoc}
709     * @see javax.swing.JComponent#getBaseline(int, int)
710     * @since 1.6
711     */
712    public Component.BaselineResizeBehavior getBaselineResizeBehavior(
713            JComponent c) {
714        super.getBaselineResizeBehavior(c);
715        switch(tabPane.getTabPlacement()) {
716        case JTabbedPane.LEFT:
717        case JTabbedPane.RIGHT:
718        case JTabbedPane.TOP:
719            return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
720        case JTabbedPane.BOTTOM:
721            return Component.BaselineResizeBehavior.CONSTANT_DESCENT;
722        }
723        return Component.BaselineResizeBehavior.OTHER;
724    }
725
726    /**
727     * Returns the baseline for the specified tab.
728     *
729     * @param tab index of tab to get baseline for
730     * @exception IndexOutOfBoundsException if index is out of range
731     *            (index &lt; 0 || index &gt;= tab count)
732     * @return baseline or a value &lt; 0 indicating there is no reasonable
733     *                  baseline
734     * @since 1.6
735     */
736    protected int getBaseline(int tab) {
737        if (tabPane.getTabComponentAt(tab) != null) {
738            int offset = getBaselineOffset();
739            if (offset != 0) {
740                // The offset is not applied to the tab component, and so
741                // in general we can't get good alignment like with components
742                // in the tab.
743                return -1;
744            }
745            Component c = tabPane.getTabComponentAt(tab);
746            Dimension pref = c.getPreferredSize();
747            Insets tabInsets = getTabInsets(tabPane.getTabPlacement(), tab);
748            int cellHeight = maxTabHeight - tabInsets.top - tabInsets.bottom;
749            return c.getBaseline(pref.width, pref.height) +
750                    (cellHeight - pref.height) / 2 + tabInsets.top;
751        }
752        else {
753            View view = getTextViewForTab(tab);
754            if (view != null) {
755                int viewHeight = (int)view.getPreferredSpan(View.Y_AXIS);
756                int baseline = BasicHTML.getHTMLBaseline(
757                    view, (int)view.getPreferredSpan(View.X_AXIS), viewHeight);
758                if (baseline >= 0) {
759                    return maxTabHeight / 2 - viewHeight / 2 + baseline +
760                        getBaselineOffset();
761                }
762                return -1;
763            }
764        }
765        FontMetrics metrics = getFontMetrics();
766        int fontHeight = metrics.getHeight();
767        int fontBaseline = metrics.getAscent();
768        return maxTabHeight / 2 - fontHeight / 2 + fontBaseline +
769                getBaselineOffset();
770    }
771
772    /**
773     * Returns the amount the baseline is offset by.  This is typically
774     * the same as <code>getTabLabelShiftY</code>.
775     *
776     * @return amount to offset the baseline by
777     * @since 1.6
778     */
779    protected int getBaselineOffset() {
780        switch(tabPane.getTabPlacement()) {
781        case JTabbedPane.TOP:
782            if (tabPane.getTabCount() > 1) {
783                return 1;
784            }
785            else {
786                return -1;
787            }
788        case JTabbedPane.BOTTOM:
789            if (tabPane.getTabCount() > 1) {
790                return -1;
791            }
792            else {
793                return 1;
794            }
795        default: // RIGHT|LEFT
796            return (maxTabHeight % 2);
797        }
798    }
799
800    private int calculateBaselineIfNecessary() {
801        if (!calculatedBaseline) {
802            calculatedBaseline = true;
803            baseline = -1;
804            if (tabPane.getTabCount() > 0) {
805                calculateBaseline();
806            }
807        }
808        return baseline;
809    }
810
811    private void calculateBaseline() {
812        int tabCount = tabPane.getTabCount();
813        int tabPlacement = tabPane.getTabPlacement();
814        maxTabHeight = calculateMaxTabHeight(tabPlacement);
815        baseline = getBaseline(0);
816        if (isHorizontalTabPlacement()) {
817            for(int i = 1; i < tabCount; i++) {
818                if (getBaseline(i) != baseline) {
819                    baseline = -1;
820                    break;
821                }
822            }
823        }
824        else {
825            // left/right, tabs may be different sizes.
826            FontMetrics fontMetrics = getFontMetrics();
827            int fontHeight = fontMetrics.getHeight();
828            int height = calculateTabHeight(tabPlacement, 0, fontHeight);
829            for(int i = 1; i < tabCount; i++) {
830                int newHeight = calculateTabHeight(tabPlacement, i,fontHeight);
831                if (height != newHeight) {
832                    // assume different baseline
833                    baseline = -1;
834                    break;
835                }
836            }
837        }
838    }
839
840// UI Rendering
841
842    public void paint(Graphics g, JComponent c) {
843        int selectedIndex = tabPane.getSelectedIndex();
844        int tabPlacement = tabPane.getTabPlacement();
845
846        ensureCurrentLayout();
847
848        // Paint content border and tab area
849        if (tabsOverlapBorder) {
850            paintContentBorder(g, tabPlacement, selectedIndex);
851        }
852        // If scrollable tabs are enabled, the tab area will be
853        // painted by the scrollable tab panel instead.
854        //
855        if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
856            paintTabArea(g, tabPlacement, selectedIndex);
857        }
858        if (!tabsOverlapBorder) {
859            paintContentBorder(g, tabPlacement, selectedIndex);
860        }
861    }
862
863    /**
864     * Paints the tabs in the tab area.
865     * Invoked by paint().
866     * The graphics parameter must be a valid <code>Graphics</code>
867     * object.  Tab placement may be either:
868     * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
869     * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
870     * The selected index must be a valid tabbed pane tab index (0 to
871     * tab count - 1, inclusive) or -1 if no tab is currently selected.
872     * The handling of invalid parameters is unspecified.
873     *
874     * @param g the graphics object to use for rendering
875     * @param tabPlacement the placement for the tabs within the JTabbedPane
876     * @param selectedIndex the tab index of the selected component
877     *
878     * @since 1.4
879     */
880    protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
881        int tabCount = tabPane.getTabCount();
882
883        Rectangle iconRect = new Rectangle(),
884                  textRect = new Rectangle();
885        Rectangle clipRect = g.getClipBounds();
886
887        // Paint tabRuns of tabs from back to front
888        for (int i = runCount - 1; i >= 0; i--) {
889            int start = tabRuns[i];
890            int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
891            int end = (next != 0? next - 1: tabCount - 1);
892            for (int j = start; j <= end; j++) {
893                if (j != selectedIndex && rects[j].intersects(clipRect)) {
894                    paintTab(g, tabPlacement, rects, j, iconRect, textRect);
895                }
896            }
897        }
898
899        // Paint selected tab if its in the front run
900        // since it may overlap other tabs
901        if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
902            paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
903        }
904    }
905
906    /**
907     * Paints a tab.
908     * @param g the graphics
909     * @param tabPlacement the tab placement
910     * @param rects rectangles
911     * @param tabIndex the tab index
912     * @param iconRect the icon rectangle
913     * @param textRect the text rectangle
914     */
915    protected void paintTab(Graphics g, int tabPlacement,
916                            Rectangle[] rects, int tabIndex,
917                            Rectangle iconRect, Rectangle textRect) {
918        Rectangle tabRect = rects[tabIndex];
919        int selectedIndex = tabPane.getSelectedIndex();
920        boolean isSelected = selectedIndex == tabIndex;
921
922        if (tabsOpaque || tabPane.isOpaque()) {
923            paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
924                    tabRect.width, tabRect.height, isSelected);
925        }
926
927        paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
928                       tabRect.width, tabRect.height, isSelected);
929
930        String title = tabPane.getTitleAt(tabIndex);
931        Font font = tabPane.getFont();
932        FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
933        Icon icon = getIconForTab(tabIndex);
934
935        layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
936                    tabRect, iconRect, textRect, isSelected);
937
938        if (tabPane.getTabComponentAt(tabIndex) == null) {
939            String clippedTitle = title;
940
941            if (scrollableTabLayoutEnabled() && tabScroller.croppedEdge.isParamsSet() &&
942                    tabScroller.croppedEdge.getTabIndex() == tabIndex && isHorizontalTabPlacement()) {
943                int availTextWidth = tabScroller.croppedEdge.getCropline() -
944                        (textRect.x - tabRect.x) - tabScroller.croppedEdge.getCroppedSideWidth();
945                clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, availTextWidth);
946            } else if (!scrollableTabLayoutEnabled() && isHorizontalTabPlacement()) {
947                clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, textRect.width);
948            }
949
950            paintText(g, tabPlacement, font, metrics,
951                    tabIndex, clippedTitle, textRect, isSelected);
952
953            paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
954        }
955        paintFocusIndicator(g, tabPlacement, rects, tabIndex,
956                  iconRect, textRect, isSelected);
957    }
958
959    private boolean isHorizontalTabPlacement() {
960        return tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM;
961    }
962
963    /* This method will create and return a polygon shape for the given tab rectangle
964     * which has been cropped at the specified cropline with a torn edge visual.
965     * e.g. A "File" tab which has cropped been cropped just after the "i":
966     *             -------------
967     *             |  .....     |
968     *             |  .          |
969     *             |  ...  .    |
970     *             |  .    .   |
971     *             |  .    .    |
972     *             |  .    .     |
973     *             --------------
974     *
975     * The x, y arrays below define the pattern used to create a "torn" edge
976     * segment which is repeated to fill the edge of the tab.
977     * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by
978     * line segments which are defined by coordinates obtained by
979     * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i]
980     * to (tab.y).
981     * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by
982     * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i]
983     * to (tab.x).
984     */
985    private static int xCropLen[] = {1,1,0,0,1,1,2,2};
986    private static int yCropLen[] = {0,3,3,6,6,9,9,12};
987    private static final int CROP_SEGMENT = 12;
988
989    private static Polygon createCroppedTabShape(int tabPlacement, Rectangle tabRect, int cropline) {
990        int rlen;
991        int start;
992        int end;
993        int ostart;
994
995        switch(tabPlacement) {
996          case LEFT:
997          case RIGHT:
998              rlen = tabRect.width;
999              start = tabRect.x;
1000              end = tabRect.x + tabRect.width;
1001              ostart = tabRect.y + tabRect.height;
1002              break;
1003          case TOP:
1004          case BOTTOM:
1005          default:
1006             rlen = tabRect.height;
1007             start = tabRect.y;
1008             end = tabRect.y + tabRect.height;
1009             ostart = tabRect.x + tabRect.width;
1010        }
1011        int rcnt = rlen/CROP_SEGMENT;
1012        if (rlen%CROP_SEGMENT > 0) {
1013            rcnt++;
1014        }
1015        int npts = 2 + (rcnt*8);
1016        int xp[] = new int[npts];
1017        int yp[] = new int[npts];
1018        int pcnt = 0;
1019
1020        xp[pcnt] = ostart;
1021        yp[pcnt++] = end;
1022        xp[pcnt] = ostart;
1023        yp[pcnt++] = start;
1024        for(int i = 0; i < rcnt; i++) {
1025            for(int j = 0; j < xCropLen.length; j++) {
1026                xp[pcnt] = cropline - xCropLen[j];
1027                yp[pcnt] = start + (i*CROP_SEGMENT) + yCropLen[j];
1028                if (yp[pcnt] >= end) {
1029                    yp[pcnt] = end;
1030                    pcnt++;
1031                    break;
1032                }
1033                pcnt++;
1034            }
1035        }
1036        if (tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM) {
1037           return new Polygon(xp, yp, pcnt);
1038
1039        } else { // LEFT or RIGHT
1040           return new Polygon(yp, xp, pcnt);
1041        }
1042    }
1043
1044    /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
1045     * indicating the tab is cropped in the viewport display
1046     */
1047    private void paintCroppedTabEdge(Graphics g) {
1048        int tabIndex = tabScroller.croppedEdge.getTabIndex();
1049        int cropline = tabScroller.croppedEdge.getCropline();
1050        int x,y;
1051        switch(tabPane.getTabPlacement()) {
1052          case LEFT:
1053          case RIGHT:
1054            x = rects[tabIndex].x;
1055            y = cropline;
1056            int xx = x;
1057            g.setColor(shadow);
1058            while(xx <= x+rects[tabIndex].width) {
1059                for (int i=0; i < xCropLen.length; i+=2) {
1060                    g.drawLine(xx+yCropLen[i],y-xCropLen[i],
1061                               xx+yCropLen[i+1]-1,y-xCropLen[i+1]);
1062                }
1063                xx+=CROP_SEGMENT;
1064            }
1065            break;
1066          case TOP:
1067          case BOTTOM:
1068          default:
1069            x = cropline;
1070            y = rects[tabIndex].y;
1071            int yy = y;
1072            g.setColor(shadow);
1073            while(yy <= y+rects[tabIndex].height) {
1074                for (int i=0; i < xCropLen.length; i+=2) {
1075                    g.drawLine(x-xCropLen[i],yy+yCropLen[i],
1076                               x-xCropLen[i+1],yy+yCropLen[i+1]-1);
1077                }
1078                yy+=CROP_SEGMENT;
1079            }
1080        }
1081    }
1082
1083    /**
1084     * Laysout a label.
1085     * @param tabPlacement the tab placement
1086     * @param metrics the font metric
1087     * @param tabIndex the tab index
1088     * @param title the title
1089     * @param icon the icon
1090     * @param tabRect the tab rectangle
1091     * @param iconRect the icon rectangle
1092     * @param textRect the text rectangle
1093     * @param isSelected selection status
1094     */
1095    protected void layoutLabel(int tabPlacement,
1096                               FontMetrics metrics, int tabIndex,
1097                               String title, Icon icon,
1098                               Rectangle tabRect, Rectangle iconRect,
1099                               Rectangle textRect, boolean isSelected ) {
1100        textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
1101
1102        View v = getTextViewForTab(tabIndex);
1103        if (v != null) {
1104            tabPane.putClientProperty("html", v);
1105        }
1106
1107        SwingUtilities.layoutCompoundLabel(tabPane,
1108                                           metrics, title, icon,
1109                                           SwingUtilities.CENTER,
1110                                           SwingUtilities.CENTER,
1111                                           SwingUtilities.CENTER,
1112                                           SwingUtilities.TRAILING,
1113                                           tabRect,
1114                                           iconRect,
1115                                           textRect,
1116                                           textIconGap);
1117
1118        tabPane.putClientProperty("html", null);
1119
1120        int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
1121        int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
1122        iconRect.x += xNudge;
1123        iconRect.y += yNudge;
1124        textRect.x += xNudge;
1125        textRect.y += yNudge;
1126    }
1127
1128    /**
1129     * Paints an icon.
1130     * @param g the graphics
1131     * @param tabPlacement the tab placement
1132     * @param tabIndex the tab index
1133     * @param icon the icon
1134     * @param iconRect the icon rectangle
1135     * @param isSelected selection status
1136     */
1137    protected void paintIcon(Graphics g, int tabPlacement,
1138                             int tabIndex, Icon icon, Rectangle iconRect,
1139                             boolean isSelected ) {
1140        if (icon != null) {
1141            icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
1142        }
1143    }
1144
1145    /**
1146     * Paints text.
1147     * @param g the graphics
1148     * @param tabPlacement the tab placement
1149     * @param font the font
1150     * @param metrics the font metrics
1151     * @param tabIndex the tab index
1152     * @param title the title
1153     * @param textRect the text rectangle
1154     * @param isSelected selection status
1155     */
1156    protected void paintText(Graphics g, int tabPlacement,
1157                             Font font, FontMetrics metrics, int tabIndex,
1158                             String title, Rectangle textRect,
1159                             boolean isSelected) {
1160
1161        g.setFont(font);
1162
1163        View v = getTextViewForTab(tabIndex);
1164        if (v != null) {
1165            // html
1166            v.paint(g, textRect);
1167        } else {
1168            // plain text
1169            int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1170
1171            if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
1172                Color fg = tabPane.getForegroundAt(tabIndex);
1173                if (isSelected && (fg instanceof UIResource)) {
1174                    Color selectedFG = UIManager.getColor(
1175                                  "TabbedPane.selectedForeground");
1176                    if (selectedFG != null) {
1177                        fg = selectedFG;
1178                    }
1179                }
1180                g.setColor(fg);
1181                SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1182                             title, mnemIndex,
1183                             textRect.x, textRect.y + metrics.getAscent());
1184
1185            } else { // tab disabled
1186                g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
1187                SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1188                             title, mnemIndex,
1189                             textRect.x, textRect.y + metrics.getAscent());
1190                g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
1191                SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1192                             title, mnemIndex,
1193                             textRect.x - 1, textRect.y + metrics.getAscent() - 1);
1194
1195            }
1196        }
1197    }
1198
1199    /**
1200     * Returns the tab label shift x.
1201     * @param tabPlacement the tab placement
1202     * @param tabIndex the tab index
1203     * @param isSelected selection status
1204     * @return the tab label shift x
1205     */
1206    protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
1207        Rectangle tabRect = rects[tabIndex];
1208        String propKey = (isSelected ? "selectedLabelShift" : "labelShift");
1209        int nudge = DefaultLookup.getInt(
1210                tabPane, this, "TabbedPane." + propKey, 1);
1211
1212        switch (tabPlacement) {
1213            case LEFT:
1214                return nudge;
1215            case RIGHT:
1216                return -nudge;
1217            case BOTTOM:
1218            case TOP:
1219            default:
1220                return tabRect.width % 2;
1221        }
1222    }
1223
1224    /**
1225     * Returns the tab label shift y.
1226     * @param tabPlacement the tab placement
1227     * @param tabIndex the tab index
1228     * @param isSelected selection status
1229     * @return the tab label shift y
1230     */
1231    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
1232        Rectangle tabRect = rects[tabIndex];
1233        int nudge = (isSelected ? DefaultLookup.getInt(tabPane, this, "TabbedPane.selectedLabelShift", -1) :
1234                DefaultLookup.getInt(tabPane, this, "TabbedPane.labelShift", 1));
1235
1236        switch (tabPlacement) {
1237            case BOTTOM:
1238                return -nudge;
1239            case LEFT:
1240            case RIGHT:
1241                return tabRect.height % 2;
1242            case TOP:
1243            default:
1244                return nudge;
1245        }
1246    }
1247
1248    /**
1249     * Paints the focus indicator.
1250     * @param g the graphics
1251     * @param tabPlacement the tab placement
1252     * @param rects rectangles
1253     * @param tabIndex the tab index
1254     * @param iconRect the icon rectangle
1255     * @param textRect the text rectangle
1256     * @param isSelected selection status
1257     */
1258    protected void paintFocusIndicator(Graphics g, int tabPlacement,
1259                                       Rectangle[] rects, int tabIndex,
1260                                       Rectangle iconRect, Rectangle textRect,
1261                                       boolean isSelected) {
1262        Rectangle tabRect = rects[tabIndex];
1263        if (tabPane.hasFocus() && isSelected) {
1264            int x, y, w, h;
1265            g.setColor(focus);
1266            switch(tabPlacement) {
1267              case LEFT:
1268                  x = tabRect.x + 3;
1269                  y = tabRect.y + 3;
1270                  w = tabRect.width - 5;
1271                  h = tabRect.height - 6;
1272                  break;
1273              case RIGHT:
1274                  x = tabRect.x + 2;
1275                  y = tabRect.y + 3;
1276                  w = tabRect.width - 5;
1277                  h = tabRect.height - 6;
1278                  break;
1279              case BOTTOM:
1280                  x = tabRect.x + 3;
1281                  y = tabRect.y + 2;
1282                  w = tabRect.width - 6;
1283                  h = tabRect.height - 5;
1284                  break;
1285              case TOP:
1286              default:
1287                  x = tabRect.x + 3;
1288                  y = tabRect.y + 3;
1289                  w = tabRect.width - 6;
1290                  h = tabRect.height - 5;
1291            }
1292            BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
1293        }
1294    }
1295
1296    /**
1297      * this function draws the border around each tab
1298      * note that this function does now draw the background of the tab.
1299      * that is done elsewhere
1300      *
1301      * @param g             the graphics context in which to paint
1302      * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1303      * @param tabIndex      the index of the tab with respect to other tabs
1304      * @param x             the x coordinate of tab
1305      * @param y             the y coordinate of tab
1306      * @param w             the width of the tab
1307      * @param h             the height of the tab
1308      * @param isSelected    a {@code boolean} which determines whether or not
1309      * the tab is selected
1310      */
1311    protected void paintTabBorder(Graphics g, int tabPlacement,
1312                                  int tabIndex,
1313                                  int x, int y, int w, int h,
1314                                  boolean isSelected ) {
1315        g.setColor(lightHighlight);
1316
1317        switch (tabPlacement) {
1318          case LEFT:
1319              g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1320              g.drawLine(x, y+2, x, y+h-3); // left highlight
1321              g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1322              g.drawLine(x+2, y, x+w-1, y); // top highlight
1323
1324              g.setColor(shadow);
1325              g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow
1326
1327              g.setColor(darkShadow);
1328              g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow
1329              break;
1330          case RIGHT:
1331              g.drawLine(x, y, x+w-3, y); // top highlight
1332
1333              g.setColor(shadow);
1334              g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow
1335              g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow
1336
1337              g.setColor(darkShadow);
1338              g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow
1339              g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1340              g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow
1341              g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1342              break;
1343          case BOTTOM:
1344              g.drawLine(x, y, x, y+h-3); // left highlight
1345              g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1346
1347              g.setColor(shadow);
1348              g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow
1349              g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow
1350
1351              g.setColor(darkShadow);
1352              g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1353              g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1354              g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
1355              break;
1356          case TOP:
1357          default:
1358              g.drawLine(x, y+2, x, y+h-1); // left highlight
1359              g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1360              g.drawLine(x+2, y, x+w-3, y); // top highlight
1361
1362              g.setColor(shadow);
1363              g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow
1364
1365              g.setColor(darkShadow);
1366              g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow
1367              g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow
1368        }
1369    }
1370
1371    /**
1372     * Paints the tab background.
1373     * @param g             the graphics context in which to paint
1374     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1375     * @param tabIndex      the index of the tab with respect to other tabs
1376     * @param x             the x coordinate of tab
1377     * @param y             the y coordinate of tab
1378     * @param w             the width of the tab
1379     * @param h             the height of the tab
1380     * @param isSelected    a {@code boolean} which determines whether or not
1381     * the tab is selected
1382     */
1383    protected void paintTabBackground(Graphics g, int tabPlacement,
1384                                      int tabIndex,
1385                                      int x, int y, int w, int h,
1386                                      boolean isSelected ) {
1387        g.setColor(!isSelected || selectedColor == null?
1388                   tabPane.getBackgroundAt(tabIndex) : selectedColor);
1389        switch(tabPlacement) {
1390          case LEFT:
1391              g.fillRect(x+1, y+1, w-1, h-3);
1392              break;
1393          case RIGHT:
1394              g.fillRect(x, y+1, w-2, h-3);
1395              break;
1396          case BOTTOM:
1397              g.fillRect(x+1, y, w-3, h-1);
1398              break;
1399          case TOP:
1400          default:
1401              g.fillRect(x+1, y+1, w-3, h-1);
1402        }
1403    }
1404
1405    /**
1406     * Paints the content border.
1407     * @param g             the graphics context in which to paint
1408     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1409     * @param selectedIndex the tab index of the selected component
1410     */
1411    protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
1412        int width = tabPane.getWidth();
1413        int height = tabPane.getHeight();
1414        Insets insets = tabPane.getInsets();
1415        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1416
1417        int x = insets.left;
1418        int y = insets.top;
1419        int w = width - insets.right - insets.left;
1420        int h = height - insets.top - insets.bottom;
1421
1422        switch(tabPlacement) {
1423          case LEFT:
1424              x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1425              if (tabsOverlapBorder) {
1426                  x -= tabAreaInsets.right;
1427              }
1428              w -= (x - insets.left);
1429              break;
1430          case RIGHT:
1431              w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1432              if (tabsOverlapBorder) {
1433                  w += tabAreaInsets.left;
1434              }
1435              break;
1436          case BOTTOM:
1437              h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1438              if (tabsOverlapBorder) {
1439                  h += tabAreaInsets.top;
1440              }
1441              break;
1442          case TOP:
1443          default:
1444              y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1445              if (tabsOverlapBorder) {
1446                  y -= tabAreaInsets.bottom;
1447              }
1448              h -= (y - insets.top);
1449        }
1450
1451            if ( tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque()) ) {
1452            // Fill region behind content area
1453            Color color = UIManager.getColor("TabbedPane.contentAreaColor");
1454            if (color != null) {
1455                g.setColor(color);
1456            }
1457            else if ( selectedColor == null || selectedIndex == -1 ) {
1458                g.setColor(tabPane.getBackground());
1459            }
1460            else {
1461                g.setColor(selectedColor);
1462            }
1463            g.fillRect(x,y,w,h);
1464        }
1465
1466        paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1467        paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1468        paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1469        paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1470
1471    }
1472
1473    /**
1474     * Paints the content border top edge.
1475     * @param g             the graphics context in which to paint
1476     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1477     * @param selectedIndex the tab index of the selected component
1478     * @param x             the x coordinate of tab
1479     * @param y             the y coordinate of tab
1480     * @param w             the width of the tab
1481     * @param h             the height of the tab
1482     */
1483    protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
1484                                         int selectedIndex,
1485                                         int x, int y, int w, int h) {
1486        Rectangle selRect = selectedIndex < 0? null :
1487                               getTabBounds(selectedIndex, calcRect);
1488
1489        g.setColor(lightHighlight);
1490
1491        // Draw unbroken line if tabs are not on TOP, OR
1492        // selected tab is not in run adjacent to content, OR
1493        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1494        //
1495        if (tabPlacement != TOP || selectedIndex < 0 ||
1496            (selRect.y + selRect.height + 1 < y) ||
1497            (selRect.x < x || selRect.x > x + w)) {
1498            g.drawLine(x, y, x+w-2, y);
1499        } else {
1500            // Break line to show visual connection to selected tab
1501            g.drawLine(x, y, selRect.x - 1, y);
1502            if (selRect.x + selRect.width < x + w - 2) {
1503                g.drawLine(selRect.x + selRect.width, y,
1504                           x+w-2, y);
1505            } else {
1506                g.setColor(shadow);
1507                g.drawLine(x+w-2, y, x+w-2, y);
1508            }
1509        }
1510    }
1511
1512    /**
1513     * Paints the content border left edge.
1514     * @param g             the graphics context in which to paint
1515     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1516     * @param selectedIndex the tab index of the selected component
1517     * @param x             the x coordinate of tab
1518     * @param y             the y coordinate of tab
1519     * @param w             the width of the tab
1520     * @param h             the height of the tab
1521     */
1522    protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
1523                                               int selectedIndex,
1524                                               int x, int y, int w, int h) {
1525        Rectangle selRect = selectedIndex < 0? null :
1526                               getTabBounds(selectedIndex, calcRect);
1527
1528        g.setColor(lightHighlight);
1529
1530        // Draw unbroken line if tabs are not on LEFT, OR
1531        // selected tab is not in run adjacent to content, OR
1532        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1533        //
1534        if (tabPlacement != LEFT || selectedIndex < 0 ||
1535            (selRect.x + selRect.width + 1 < x) ||
1536            (selRect.y < y || selRect.y > y + h)) {
1537            g.drawLine(x, y, x, y+h-2);
1538        } else {
1539            // Break line to show visual connection to selected tab
1540            g.drawLine(x, y, x, selRect.y - 1);
1541            if (selRect.y + selRect.height < y + h - 2) {
1542                g.drawLine(x, selRect.y + selRect.height,
1543                           x, y+h-2);
1544            }
1545        }
1546    }
1547
1548    /**
1549     * Paints the content border bottom edge.
1550     * @param g             the graphics context in which to paint
1551     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1552     * @param selectedIndex the tab index of the selected component
1553     * @param x             the x coordinate of tab
1554     * @param y             the y coordinate of tab
1555     * @param w             the width of the tab
1556     * @param h             the height of the tab
1557     */
1558    protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
1559                                               int selectedIndex,
1560                                               int x, int y, int w, int h) {
1561        Rectangle selRect = selectedIndex < 0? null :
1562                               getTabBounds(selectedIndex, calcRect);
1563
1564        g.setColor(shadow);
1565
1566        // Draw unbroken line if tabs are not on BOTTOM, OR
1567        // selected tab is not in run adjacent to content, OR
1568        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1569        //
1570        if (tabPlacement != BOTTOM || selectedIndex < 0 ||
1571             (selRect.y - 1 > h) ||
1572             (selRect.x < x || selRect.x > x + w)) {
1573            g.drawLine(x+1, y+h-2, x+w-2, y+h-2);
1574            g.setColor(darkShadow);
1575            g.drawLine(x, y+h-1, x+w-1, y+h-1);
1576        } else {
1577            // Break line to show visual connection to selected tab
1578            g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2);
1579            g.setColor(darkShadow);
1580            g.drawLine(x, y+h-1, selRect.x - 1, y+h-1);
1581            if (selRect.x + selRect.width < x + w - 2) {
1582                g.setColor(shadow);
1583                g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
1584                g.setColor(darkShadow);
1585                g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1);
1586            }
1587        }
1588
1589    }
1590
1591    /**
1592     * Paints the content border right edge.
1593     * @param g             the graphics context in which to paint
1594     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1595     * @param selectedIndex the tab index of the selected component
1596     * @param x             the x coordinate of tab
1597     * @param y             the y coordinate of tab
1598     * @param w             the width of the tab
1599     * @param h             the height of the tab
1600     */
1601    protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
1602                                               int selectedIndex,
1603                                               int x, int y, int w, int h) {
1604        Rectangle selRect = selectedIndex < 0? null :
1605                               getTabBounds(selectedIndex, calcRect);
1606
1607        g.setColor(shadow);
1608
1609        // Draw unbroken line if tabs are not on RIGHT, OR
1610        // selected tab is not in run adjacent to content, OR
1611        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1612        //
1613        if (tabPlacement != RIGHT || selectedIndex < 0 ||
1614             (selRect.x - 1 > w) ||
1615             (selRect.y < y || selRect.y > y + h)) {
1616            g.drawLine(x+w-2, y+1, x+w-2, y+h-3);
1617            g.setColor(darkShadow);
1618            g.drawLine(x+w-1, y, x+w-1, y+h-1);
1619        } else {
1620            // Break line to show visual connection to selected tab
1621            g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1);
1622            g.setColor(darkShadow);
1623            g.drawLine(x+w-1, y, x+w-1, selRect.y - 1);
1624
1625            if (selRect.y + selRect.height < y + h - 2) {
1626                g.setColor(shadow);
1627                g.drawLine(x+w-2, selRect.y + selRect.height,
1628                           x+w-2, y+h-2);
1629                g.setColor(darkShadow);
1630                g.drawLine(x+w-1, selRect.y + selRect.height,
1631                           x+w-1, y+h-2);
1632            }
1633        }
1634    }
1635
1636    private void ensureCurrentLayout() {
1637        if (!tabPane.isValid()) {
1638            tabPane.validate();
1639        }
1640        /* If tabPane doesn't have a peer yet, the validate() call will
1641         * silently fail.  We handle that by forcing a layout if tabPane
1642         * is still invalid.  See bug 4237677.
1643         */
1644        if (!tabPane.isValid()) {
1645            TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
1646            layout.calculateLayoutInfo();
1647        }
1648    }
1649
1650
1651// TabbedPaneUI methods
1652
1653    /**
1654     * Returns the bounds of the specified tab index.  The bounds are
1655     * with respect to the JTabbedPane's coordinate space.
1656     */
1657    public Rectangle getTabBounds(JTabbedPane pane, int i) {
1658        ensureCurrentLayout();
1659        Rectangle tabRect = new Rectangle();
1660        return getTabBounds(i, tabRect);
1661    }
1662
1663    public int getTabRunCount(JTabbedPane pane) {
1664        ensureCurrentLayout();
1665        return runCount;
1666    }
1667
1668    /**
1669     * Returns the tab index which intersects the specified point
1670     * in the JTabbedPane's coordinate space.
1671     */
1672    public int tabForCoordinate(JTabbedPane pane, int x, int y) {
1673        return tabForCoordinate(pane, x, y, true);
1674    }
1675
1676    private int tabForCoordinate(JTabbedPane pane, int x, int y,
1677                                 boolean validateIfNecessary) {
1678        if (validateIfNecessary) {
1679            ensureCurrentLayout();
1680        }
1681        if (isRunsDirty) {
1682            // We didn't recalculate the layout, runs and tabCount may not
1683            // line up, bail.
1684            return -1;
1685        }
1686        Point p = new Point(x, y);
1687
1688        if (scrollableTabLayoutEnabled()) {
1689            translatePointToTabPanel(x, y, p);
1690            Rectangle viewRect = tabScroller.viewport.getViewRect();
1691            if (!viewRect.contains(p)) {
1692                return -1;
1693            }
1694        }
1695        int tabCount = tabPane.getTabCount();
1696        for (int i = 0; i < tabCount; i++) {
1697            if (rects[i].contains(p.x, p.y)) {
1698                return i;
1699            }
1700        }
1701        return -1;
1702    }
1703
1704    /**
1705     * Returns the bounds of the specified tab in the coordinate space
1706     * of the JTabbedPane component.  This is required because the tab rects
1707     * are by default defined in the coordinate space of the component where
1708     * they are rendered, which could be the JTabbedPane
1709     * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
1710     * This method should be used whenever the tab rectangle must be relative
1711     * to the JTabbedPane itself and the result should be placed in a
1712     * designated Rectangle object (rather than instantiating and returning
1713     * a new Rectangle each time). The tab index parameter must be a valid
1714     * tabbed pane tab index (0 to tab count - 1, inclusive).  The destination
1715     * rectangle parameter must be a valid <code>Rectangle</code> instance.
1716     * The handling of invalid parameters is unspecified.
1717     *
1718     * @param tabIndex the index of the tab
1719     * @param dest the rectangle where the result should be placed
1720     * @return the resulting rectangle
1721     *
1722     * @since 1.4
1723     */
1724    protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
1725        dest.width = rects[tabIndex].width;
1726        dest.height = rects[tabIndex].height;
1727
1728        if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
1729            // Need to translate coordinates based on viewport location &
1730            // view position
1731            Point vpp = tabScroller.viewport.getLocation();
1732            Point viewp = tabScroller.viewport.getViewPosition();
1733            dest.x = rects[tabIndex].x + vpp.x - viewp.x;
1734            dest.y = rects[tabIndex].y + vpp.y - viewp.y;
1735
1736        } else { // WRAP_TAB_LAYOUT
1737            dest.x = rects[tabIndex].x;
1738            dest.y = rects[tabIndex].y;
1739        }
1740        return dest;
1741    }
1742
1743    /**
1744     * Returns the index of the tab closest to the passed in location, note
1745     * that the returned tab may not contain the location x,y.
1746     */
1747    private int getClosestTab(int x, int y) {
1748        int min = 0;
1749        int tabCount = Math.min(rects.length, tabPane.getTabCount());
1750        int max = tabCount;
1751        int tabPlacement = tabPane.getTabPlacement();
1752        boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
1753        int want = (useX) ? x : y;
1754
1755        while (min != max) {
1756            int current = (max + min) / 2;
1757            int minLoc;
1758            int maxLoc;
1759
1760            if (useX) {
1761                minLoc = rects[current].x;
1762                maxLoc = minLoc + rects[current].width;
1763            }
1764            else {
1765                minLoc = rects[current].y;
1766                maxLoc = minLoc + rects[current].height;
1767            }
1768            if (want < minLoc) {
1769                max = current;
1770                if (min == max) {
1771                    return Math.max(0, current - 1);
1772                }
1773            }
1774            else if (want >= maxLoc) {
1775                min = current;
1776                if (max - min <= 1) {
1777                    return Math.max(current + 1, tabCount - 1);
1778                }
1779            }
1780            else {
1781                return current;
1782            }
1783        }
1784        return min;
1785    }
1786
1787    /**
1788     * Returns a point which is translated from the specified point in the
1789     * JTabbedPane's coordinate space to the coordinate space of the
1790     * ScrollableTabPanel.  This is used for SCROLL_TAB_LAYOUT ONLY.
1791     */
1792    private Point translatePointToTabPanel(int srcx, int srcy, Point dest) {
1793        Point vpp = tabScroller.viewport.getLocation();
1794        Point viewp = tabScroller.viewport.getViewPosition();
1795        dest.x = srcx - vpp.x + viewp.x;
1796        dest.y = srcy - vpp.y + viewp.y;
1797        return dest;
1798    }
1799
1800// BasicTabbedPaneUI methods
1801
1802    /**
1803     * Returns the visible component.
1804     * @return the visible component
1805     */
1806    protected Component getVisibleComponent() {
1807        return visibleComponent;
1808    }
1809
1810    /**
1811     * Sets the visible component.
1812     * @param component the component
1813     */
1814    protected void setVisibleComponent(Component component) {
1815        if (visibleComponent != null
1816                && visibleComponent != component
1817                && visibleComponent.getParent() == tabPane
1818                && visibleComponent.isVisible()) {
1819
1820            visibleComponent.setVisible(false);
1821        }
1822        if (component != null && !component.isVisible()) {
1823            component.setVisible(true);
1824        }
1825        visibleComponent = component;
1826    }
1827
1828    /**
1829     * Assure the rectangles are created.
1830     * @param tabCount the tab count
1831     */
1832    protected void assureRectsCreated(int tabCount) {
1833        int rectArrayLen = rects.length;
1834        if (tabCount != rectArrayLen ) {
1835            Rectangle[] tempRectArray = new Rectangle[tabCount];
1836            System.arraycopy(rects, 0, tempRectArray, 0,
1837                             Math.min(rectArrayLen, tabCount));
1838            rects = tempRectArray;
1839            for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
1840                rects[rectIndex] = new Rectangle();
1841            }
1842        }
1843
1844    }
1845
1846    /**
1847     * Expands the tab runs array.
1848     */
1849    protected void expandTabRunsArray() {
1850        int rectLen = tabRuns.length;
1851        int[] newArray = new int[rectLen+10];
1852        System.arraycopy(tabRuns, 0, newArray, 0, runCount);
1853        tabRuns = newArray;
1854    }
1855
1856    /**
1857     * Returns the run for a tab.
1858     * @param tabCount the tab count
1859     * @param tabIndex the tab index.
1860     * @return the run for a tab
1861     */
1862    protected int getRunForTab(int tabCount, int tabIndex) {
1863        for (int i = 0; i < runCount; i++) {
1864            int first = tabRuns[i];
1865            int last = lastTabInRun(tabCount, i);
1866            if (tabIndex >= first && tabIndex <= last) {
1867                return i;
1868            }
1869        }
1870        return 0;
1871    }
1872
1873    /**
1874     * Returns the last tab in a run.
1875     * @param tabCount the tab count
1876     * @param run the run
1877     * @return the last tab in a run
1878     */
1879    protected int lastTabInRun(int tabCount, int run) {
1880        if (runCount == 1) {
1881            return tabCount - 1;
1882        }
1883        int nextRun = (run == runCount - 1? 0 : run + 1);
1884        if (tabRuns[nextRun] == 0) {
1885            return tabCount - 1;
1886        }
1887        return tabRuns[nextRun]-1;
1888    }
1889
1890    /**
1891     * Returns the tab run overlay.
1892     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1893     * @return the tab run overlay
1894     */
1895    protected int getTabRunOverlay(int tabPlacement) {
1896        return tabRunOverlay;
1897    }
1898
1899    /**
1900     * Returns the tab run indent.
1901     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1902     * @param run the tab run
1903     * @return the tab run indent
1904     */
1905    protected int getTabRunIndent(int tabPlacement, int run) {
1906        return 0;
1907    }
1908
1909    /**
1910     * Returns whether or not the tab run should be padded.
1911     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1912     * @param run the tab run
1913     * @return whether or not the tab run should be padded
1914     */
1915    protected boolean shouldPadTabRun(int tabPlacement, int run) {
1916        return runCount > 1;
1917    }
1918
1919    /**
1920     * Returns whether or not the tab run should be rotated.
1921     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1922     * @return whether or not the tab run should be rotated
1923     */
1924    protected boolean shouldRotateTabRuns(int tabPlacement) {
1925        return true;
1926    }
1927
1928    /**
1929     * Returns the icon for a tab.
1930     * @param tabIndex the index of the tab
1931     * @return the icon for a tab
1932     */
1933    protected Icon getIconForTab(int tabIndex) {
1934        return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))?
1935                          tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex);
1936    }
1937
1938    /**
1939     * Returns the text View object required to render stylized text (HTML) for
1940     * the specified tab or null if no specialized text rendering is needed
1941     * for this tab. This is provided to support html rendering inside tabs.
1942     *
1943     * @param tabIndex the index of the tab
1944     * @return the text view to render the tab's text or null if no
1945     *         specialized rendering is required
1946     *
1947     * @since 1.4
1948     */
1949    protected View getTextViewForTab(int tabIndex) {
1950        if (htmlViews != null) {
1951            return htmlViews.elementAt(tabIndex);
1952        }
1953        return null;
1954    }
1955
1956    /**
1957     * Calculates the tab height.
1958     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1959     * @param tabIndex      the index of the tab with respect to other tabs
1960     * @param fontHeight    the font height
1961     * @return the tab height
1962     */
1963    protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
1964        int height = 0;
1965        Component c = tabPane.getTabComponentAt(tabIndex);
1966        if (c != null) {
1967            height = c.getPreferredSize().height;
1968        } else {
1969            View v = getTextViewForTab(tabIndex);
1970            if (v != null) {
1971                // html
1972                height += (int) v.getPreferredSpan(View.Y_AXIS);
1973            } else {
1974                // plain text
1975                height += fontHeight;
1976            }
1977            Icon icon = getIconForTab(tabIndex);
1978
1979            if (icon != null) {
1980                height = Math.max(height, icon.getIconHeight());
1981            }
1982        }
1983        Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1984        height += tabInsets.top + tabInsets.bottom + 2;
1985        return height;
1986    }
1987
1988    /**
1989     * Calculates the maximum tab height.
1990     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1991     * @return the maximum tab height
1992     */
1993    protected int calculateMaxTabHeight(int tabPlacement) {
1994        FontMetrics metrics = getFontMetrics();
1995        int tabCount = tabPane.getTabCount();
1996        int result = 0;
1997        int fontHeight = metrics.getHeight();
1998        for(int i = 0; i < tabCount; i++) {
1999            result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
2000        }
2001        return result;
2002    }
2003
2004    /**
2005     * Calculates the tab width.
2006     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2007     * @param tabIndex      the index of the tab with respect to other tabs
2008     * @param metrics       the font metrics
2009     * @return the tab width
2010     */
2011    protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
2012        Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
2013        int width = tabInsets.left + tabInsets.right + 3;
2014        Component tabComponent = tabPane.getTabComponentAt(tabIndex);
2015        if (tabComponent != null) {
2016            width += tabComponent.getPreferredSize().width;
2017        } else {
2018            Icon icon = getIconForTab(tabIndex);
2019            if (icon != null) {
2020                width += icon.getIconWidth() + textIconGap;
2021            }
2022            View v = getTextViewForTab(tabIndex);
2023            if (v != null) {
2024                // html
2025                width += (int) v.getPreferredSpan(View.X_AXIS);
2026            } else {
2027                // plain text
2028                String title = tabPane.getTitleAt(tabIndex);
2029                width += SwingUtilities2.stringWidth(tabPane, metrics, title);
2030            }
2031        }
2032        return width;
2033    }
2034
2035    /**
2036     * Calculates the maximum tab width.
2037     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2038     * @return the maximum tab width
2039     */
2040    protected int calculateMaxTabWidth(int tabPlacement) {
2041        FontMetrics metrics = getFontMetrics();
2042        int tabCount = tabPane.getTabCount();
2043        int result = 0;
2044        for(int i = 0; i < tabCount; i++) {
2045            result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
2046        }
2047        return result;
2048    }
2049
2050    /**
2051     * Calculates the tab area height.
2052     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2053     * @param horizRunCount horizontal run count
2054     * @param maxTabHeight maximum tab height
2055     * @return the tab area height
2056     */
2057    protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) {
2058        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2059        int tabRunOverlay = getTabRunOverlay(tabPlacement);
2060        return (horizRunCount > 0?
2061                horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay +
2062                tabAreaInsets.top + tabAreaInsets.bottom :
2063                0);
2064    }
2065
2066    /**
2067     * Calculates the tab area width.
2068     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2069     * @param vertRunCount vertical run count
2070     * @param maxTabWidth maximum tab width
2071     * @return the tab area width
2072     */
2073    protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) {
2074        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2075        int tabRunOverlay = getTabRunOverlay(tabPlacement);
2076        return (vertRunCount > 0?
2077                vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay +
2078                tabAreaInsets.left + tabAreaInsets.right :
2079                0);
2080    }
2081
2082    /**
2083     * Returns the tab insets.
2084     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2085     * @param tabIndex the tab index
2086     * @return the tab insets
2087     */
2088    protected Insets getTabInsets(int tabPlacement, int tabIndex) {
2089        return tabInsets;
2090    }
2091
2092    /**
2093     * Returns the selected tab pad insets.
2094     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2095     * @return the selected tab pad insets
2096     */
2097    protected Insets getSelectedTabPadInsets(int tabPlacement) {
2098        rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
2099        return currentPadInsets;
2100    }
2101
2102    /**
2103     * Returns the tab area insets.
2104     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2105     * @return the pad area insets
2106     */
2107    protected Insets getTabAreaInsets(int tabPlacement) {
2108        rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
2109        return currentTabAreaInsets;
2110    }
2111
2112    /**
2113     * Returns the content border insets.
2114     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2115     * @return the content border insets
2116     */
2117    protected Insets getContentBorderInsets(int tabPlacement) {
2118        return contentBorderInsets;
2119    }
2120
2121    /**
2122     * Returns the font metrics.
2123     * @return the font metrics
2124     */
2125    protected FontMetrics getFontMetrics() {
2126        Font font = tabPane.getFont();
2127        return tabPane.getFontMetrics(font);
2128    }
2129
2130
2131// Tab Navigation methods
2132
2133    /**
2134     * Navigate the selected tab.
2135     * @param direction the direction
2136     */
2137    protected void navigateSelectedTab(int direction) {
2138        int tabPlacement = tabPane.getTabPlacement();
2139        int current = DefaultLookup.getBoolean(tabPane, this,
2140                             "TabbedPane.selectionFollowsFocus", true) ?
2141                             tabPane.getSelectedIndex() : getFocusIndex();
2142        int tabCount = tabPane.getTabCount();
2143        boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
2144
2145        // If we have no tabs then don't navigate.
2146        if (tabCount <= 0) {
2147            return;
2148        }
2149
2150        int offset;
2151        switch(tabPlacement) {
2152          case LEFT:
2153          case RIGHT:
2154              switch(direction) {
2155                 case NEXT:
2156                     selectNextTab(current);
2157                     break;
2158                 case PREVIOUS:
2159                     selectPreviousTab(current);
2160                     break;
2161                case NORTH:
2162                    selectPreviousTabInRun(current);
2163                    break;
2164                case SOUTH:
2165                    selectNextTabInRun(current);
2166                    break;
2167                case WEST:
2168                    offset = getTabRunOffset(tabPlacement, tabCount, current, false);
2169                    selectAdjacentRunTab(tabPlacement, current, offset);
2170                    break;
2171                case EAST:
2172                    offset = getTabRunOffset(tabPlacement, tabCount, current, true);
2173                    selectAdjacentRunTab(tabPlacement, current, offset);
2174                    break;
2175                default:
2176              }
2177              break;
2178          case BOTTOM:
2179          case TOP:
2180          default:
2181              switch(direction) {
2182                case NEXT:
2183                    selectNextTab(current);
2184                    break;
2185                case PREVIOUS:
2186                    selectPreviousTab(current);
2187                    break;
2188                case NORTH:
2189                    offset = getTabRunOffset(tabPlacement, tabCount, current, false);
2190                    selectAdjacentRunTab(tabPlacement, current, offset);
2191                    break;
2192                case SOUTH:
2193                    offset = getTabRunOffset(tabPlacement, tabCount, current, true);
2194                    selectAdjacentRunTab(tabPlacement, current, offset);
2195                    break;
2196                case EAST:
2197                    if (leftToRight) {
2198                        selectNextTabInRun(current);
2199                    } else {
2200                        selectPreviousTabInRun(current);
2201                    }
2202                    break;
2203                case WEST:
2204                    if (leftToRight) {
2205                        selectPreviousTabInRun(current);
2206                    } else {
2207                        selectNextTabInRun(current);
2208                    }
2209                    break;
2210                default:
2211              }
2212        }
2213    }
2214
2215    /**
2216     * Select the next tab in the run.
2217     * @param current the current tab
2218     */
2219    protected void selectNextTabInRun(int current) {
2220        int tabCount = tabPane.getTabCount();
2221        int tabIndex = getNextTabIndexInRun(tabCount, current);
2222
2223        while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2224            tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
2225        }
2226        navigateTo(tabIndex);
2227    }
2228
2229    /**
2230     * Select the previous tab in the run.
2231     * @param current the current tab
2232     */
2233    protected void selectPreviousTabInRun(int current) {
2234        int tabCount = tabPane.getTabCount();
2235        int tabIndex = getPreviousTabIndexInRun(tabCount, current);
2236
2237        while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2238            tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
2239        }
2240        navigateTo(tabIndex);
2241    }
2242
2243    /**
2244     * Select the next tab.
2245     * @param current the current tab
2246     */
2247    protected void selectNextTab(int current) {
2248        int tabIndex = getNextTabIndex(current);
2249
2250        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2251            tabIndex = getNextTabIndex(tabIndex);
2252        }
2253        navigateTo(tabIndex);
2254    }
2255
2256    /**
2257     * Select the previous tab.
2258     * @param current the current tab
2259     */
2260    protected void selectPreviousTab(int current) {
2261        int tabIndex = getPreviousTabIndex(current);
2262
2263        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2264            tabIndex = getPreviousTabIndex(tabIndex);
2265        }
2266        navigateTo(tabIndex);
2267    }
2268
2269    /**
2270     * Selects an adjacent run of tabs.
2271     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2272     * @param tabIndex      the index of the tab with respect to other tabs
2273     * @param offset        selection offset
2274     */
2275    protected void selectAdjacentRunTab(int tabPlacement,
2276                                        int tabIndex, int offset) {
2277        if ( runCount < 2 ) {
2278            return;
2279        }
2280        int newIndex;
2281        Rectangle r = rects[tabIndex];
2282        switch(tabPlacement) {
2283          case LEFT:
2284          case RIGHT:
2285              newIndex = tabForCoordinate(tabPane, r.x + r.width/2 + offset,
2286                                       r.y + r.height/2);
2287              break;
2288          case BOTTOM:
2289          case TOP:
2290          default:
2291              newIndex = tabForCoordinate(tabPane, r.x + r.width/2,
2292                                       r.y + r.height/2 + offset);
2293        }
2294        if (newIndex != -1) {
2295            while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
2296                newIndex = getNextTabIndex(newIndex);
2297            }
2298            navigateTo(newIndex);
2299        }
2300    }
2301
2302    private void navigateTo(int index) {
2303        if (DefaultLookup.getBoolean(tabPane, this,
2304                             "TabbedPane.selectionFollowsFocus", true)) {
2305            tabPane.setSelectedIndex(index);
2306        } else {
2307            // Just move focus (not selection)
2308            setFocusIndex(index, true);
2309        }
2310    }
2311
2312    void setFocusIndex(int index, boolean repaint) {
2313        if (repaint && !isRunsDirty) {
2314            repaintTab(focusIndex);
2315            focusIndex = index;
2316            repaintTab(focusIndex);
2317        }
2318        else {
2319            focusIndex = index;
2320        }
2321    }
2322
2323    /**
2324     * Repaints the specified tab.
2325     */
2326    private void repaintTab(int index) {
2327        // If we're not valid that means we will shortly be validated and
2328        // painted, which means we don't have to do anything here.
2329        if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) {
2330            tabPane.repaint(getTabBounds(tabPane, index));
2331        }
2332    }
2333
2334    /**
2335     * Makes sure the focusIndex is valid.
2336     */
2337    private void validateFocusIndex() {
2338        if (focusIndex >= tabPane.getTabCount()) {
2339            setFocusIndex(tabPane.getSelectedIndex(), false);
2340        }
2341    }
2342
2343    /**
2344     * Returns the index of the tab that has focus.
2345     *
2346     * @return index of tab that has focus
2347     * @since 1.5
2348     */
2349    protected int getFocusIndex() {
2350        return focusIndex;
2351    }
2352
2353    /**
2354     * Returns the tab run offset.
2355     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2356     * @param tabCount the tab count
2357     * @param tabIndex      the index of the tab with respect to other tabs
2358     * @param forward forward or not
2359     * @return the tab run offset
2360     */
2361    protected int getTabRunOffset(int tabPlacement, int tabCount,
2362                                  int tabIndex, boolean forward) {
2363        int run = getRunForTab(tabCount, tabIndex);
2364        int offset;
2365        switch(tabPlacement) {
2366          case LEFT: {
2367              if (run == 0) {
2368                  offset = (forward?
2369                            -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2370                            -maxTabWidth);
2371
2372              } else if (run == runCount - 1) {
2373                  offset = (forward?
2374                            maxTabWidth :
2375                            calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2376              } else {
2377                  offset = (forward? maxTabWidth : -maxTabWidth);
2378              }
2379              break;
2380          }
2381          case RIGHT: {
2382              if (run == 0) {
2383                  offset = (forward?
2384                            maxTabWidth :
2385                            calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2386              } else if (run == runCount - 1) {
2387                  offset = (forward?
2388                            -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2389                            -maxTabWidth);
2390              } else {
2391                  offset = (forward? maxTabWidth : -maxTabWidth);
2392              }
2393              break;
2394          }
2395          case BOTTOM: {
2396              if (run == 0) {
2397                  offset = (forward?
2398                            maxTabHeight :
2399                            calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2400              } else if (run == runCount - 1) {
2401                  offset = (forward?
2402                            -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2403                            -maxTabHeight);
2404              } else {
2405                  offset = (forward? maxTabHeight : -maxTabHeight);
2406              }
2407              break;
2408          }
2409          case TOP:
2410          default: {
2411              if (run == 0) {
2412                  offset = (forward?
2413                            -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2414                            -maxTabHeight);
2415              } else if (run == runCount - 1) {
2416                  offset = (forward?
2417                            maxTabHeight :
2418                            calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2419              } else {
2420                  offset = (forward? maxTabHeight : -maxTabHeight);
2421              }
2422          }
2423        }
2424        return offset;
2425    }
2426
2427    /**
2428     * Returns the previous tab index.
2429     * @param base the base
2430     * @return the previous tab index
2431     */
2432    protected int getPreviousTabIndex(int base) {
2433        int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1);
2434        return (tabIndex >= 0? tabIndex : 0);
2435    }
2436
2437    /**
2438     * Returns the next tab index.
2439     * @param base the base
2440     * @return the next tab index
2441     */
2442    protected int getNextTabIndex(int base) {
2443        return (base+1)%tabPane.getTabCount();
2444    }
2445
2446    /**
2447     * Returns the next tab index in the run.
2448     * @param tabCount the tab count
2449     * @param base the base
2450     * @return the next tab index in the run
2451     */
2452    protected int getNextTabIndexInRun(int tabCount, int base) {
2453        if (runCount < 2) {
2454            return getNextTabIndex(base);
2455        }
2456        int currentRun = getRunForTab(tabCount, base);
2457        int next = getNextTabIndex(base);
2458        if (next == tabRuns[getNextTabRun(currentRun)]) {
2459            return tabRuns[currentRun];
2460        }
2461        return next;
2462    }
2463
2464    /**
2465     * Returns the previous tab index in the run.
2466     * @param tabCount the tab count
2467     * @param base the base
2468     * @return the previous tab index in the run
2469     */
2470    protected int getPreviousTabIndexInRun(int tabCount, int base) {
2471        if (runCount < 2) {
2472            return getPreviousTabIndex(base);
2473        }
2474        int currentRun = getRunForTab(tabCount, base);
2475        if (base == tabRuns[currentRun]) {
2476            int previous = tabRuns[getNextTabRun(currentRun)]-1;
2477            return (previous != -1? previous : tabCount-1);
2478        }
2479        return getPreviousTabIndex(base);
2480    }
2481
2482    /**
2483     * Returns the previous tab run.
2484     * @param baseRun the base run
2485     * @return the previous tab run
2486     */
2487    protected int getPreviousTabRun(int baseRun) {
2488        int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1);
2489        return (runIndex >= 0? runIndex : 0);
2490    }
2491
2492    /**
2493     * Returns the next tab run.
2494     * @param baseRun the base run
2495     * @return the next tab run
2496     */
2497    protected int getNextTabRun(int baseRun) {
2498        return (baseRun+1)%runCount;
2499    }
2500
2501    /**
2502     * Rotates the insets.
2503     * @param topInsets the top insets
2504     * @param targetInsets the target insets
2505     * @param targetPlacement the target placement
2506     */
2507    protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) {
2508
2509        switch(targetPlacement) {
2510          case LEFT:
2511              targetInsets.top = topInsets.left;
2512              targetInsets.left = topInsets.top;
2513              targetInsets.bottom = topInsets.right;
2514              targetInsets.right = topInsets.bottom;
2515              break;
2516          case BOTTOM:
2517              targetInsets.top = topInsets.bottom;
2518              targetInsets.left = topInsets.left;
2519              targetInsets.bottom = topInsets.top;
2520              targetInsets.right = topInsets.right;
2521              break;
2522          case RIGHT:
2523              targetInsets.top = topInsets.left;
2524              targetInsets.left = topInsets.bottom;
2525              targetInsets.bottom = topInsets.right;
2526              targetInsets.right = topInsets.top;
2527              break;
2528          case TOP:
2529          default:
2530              targetInsets.top = topInsets.top;
2531              targetInsets.left = topInsets.left;
2532              targetInsets.bottom = topInsets.bottom;
2533              targetInsets.right = topInsets.right;
2534        }
2535    }
2536
2537    // REMIND(aim,7/29/98): This method should be made
2538    // protected in the next release where
2539    // API changes are allowed
2540    boolean requestFocusForVisibleComponent() {
2541        return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent());
2542    }
2543
2544    private static class Actions extends UIAction {
2545        static final String NEXT = "navigateNext";
2546        static final String PREVIOUS = "navigatePrevious";
2547        static final String RIGHT = "navigateRight";
2548        static final String LEFT = "navigateLeft";
2549        static final String UP = "navigateUp";
2550        static final String DOWN = "navigateDown";
2551        static final String PAGE_UP = "navigatePageUp";
2552        static final String PAGE_DOWN = "navigatePageDown";
2553        static final String REQUEST_FOCUS = "requestFocus";
2554        static final String REQUEST_FOCUS_FOR_VISIBLE =
2555                                    "requestFocusForVisibleComponent";
2556        static final String SET_SELECTED = "setSelectedIndex";
2557        static final String SELECT_FOCUSED = "selectTabWithFocus";
2558        static final String SCROLL_FORWARD = "scrollTabsForwardAction";
2559        static final String SCROLL_BACKWARD = "scrollTabsBackwardAction";
2560
2561        Actions(String key) {
2562            super(key);
2563        }
2564
2565        public void actionPerformed(ActionEvent e) {
2566            String key = getName();
2567            JTabbedPane pane = (JTabbedPane)e.getSource();
2568            BasicTabbedPaneUI ui = (BasicTabbedPaneUI)BasicLookAndFeel.
2569                       getUIOfType(pane.getUI(), BasicTabbedPaneUI.class);
2570
2571            if (ui == null) {
2572                return;
2573            }
2574            if (key == NEXT) {
2575                ui.navigateSelectedTab(SwingConstants.NEXT);
2576            }
2577            else if (key == PREVIOUS) {
2578                ui.navigateSelectedTab(SwingConstants.PREVIOUS);
2579            }
2580            else if (key == RIGHT) {
2581                ui.navigateSelectedTab(SwingConstants.EAST);
2582            }
2583            else if (key == LEFT) {
2584                ui.navigateSelectedTab(SwingConstants.WEST);
2585            }
2586            else if (key == UP) {
2587                ui.navigateSelectedTab(SwingConstants.NORTH);
2588            }
2589            else if (key == DOWN) {
2590                ui.navigateSelectedTab(SwingConstants.SOUTH);
2591            }
2592            else if (key == PAGE_UP) {
2593                int tabPlacement = pane.getTabPlacement();
2594                if (tabPlacement == TOP|| tabPlacement == BOTTOM) {
2595                    ui.navigateSelectedTab(SwingConstants.WEST);
2596                } else {
2597                    ui.navigateSelectedTab(SwingConstants.NORTH);
2598                }
2599            }
2600            else if (key == PAGE_DOWN) {
2601                int tabPlacement = pane.getTabPlacement();
2602                if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2603                    ui.navigateSelectedTab(SwingConstants.EAST);
2604                } else {
2605                    ui.navigateSelectedTab(SwingConstants.SOUTH);
2606                }
2607            }
2608            else if (key == REQUEST_FOCUS) {
2609                pane.requestFocus();
2610            }
2611            else if (key == REQUEST_FOCUS_FOR_VISIBLE) {
2612                ui.requestFocusForVisibleComponent();
2613            }
2614            else if (key == SET_SELECTED) {
2615                String command = e.getActionCommand();
2616
2617                if (command != null && command.length() > 0) {
2618                    int mnemonic = (int)e.getActionCommand().charAt(0);
2619                    if (mnemonic >= 'a' && mnemonic <='z') {
2620                        mnemonic  -= ('a' - 'A');
2621                    }
2622                    Integer index = ui.mnemonicToIndexMap.get(Integer.valueOf(mnemonic));
2623                    if (index != null && pane.isEnabledAt(index.intValue())) {
2624                        pane.setSelectedIndex(index.intValue());
2625                    }
2626                }
2627            }
2628            else if (key == SELECT_FOCUSED) {
2629                int focusIndex = ui.getFocusIndex();
2630                if (focusIndex != -1) {
2631                    pane.setSelectedIndex(focusIndex);
2632                }
2633            }
2634            else if (key == SCROLL_FORWARD) {
2635                if (ui.scrollableTabLayoutEnabled()) {
2636                    ui.tabScroller.scrollForward(pane.getTabPlacement());
2637                }
2638            }
2639            else if (key == SCROLL_BACKWARD) {
2640                if (ui.scrollableTabLayoutEnabled()) {
2641                    ui.tabScroller.scrollBackward(pane.getTabPlacement());
2642                }
2643            }
2644        }
2645    }
2646
2647    /**
2648     * This class should be treated as a &quot;protected&quot; inner class.
2649     * Instantiate it only within subclasses of BasicTabbedPaneUI.
2650     */
2651    public class TabbedPaneLayout implements LayoutManager {
2652
2653        public void addLayoutComponent(String name, Component comp) {}
2654
2655        public void removeLayoutComponent(Component comp) {}
2656
2657        public Dimension preferredLayoutSize(Container parent) {
2658            return calculateSize(false);
2659        }
2660
2661        public Dimension minimumLayoutSize(Container parent) {
2662            return calculateSize(true);
2663        }
2664
2665        /**
2666         * Returns the calculated size.
2667         * @param minimum use the minimum size or preferred size
2668         * @return the calculated size
2669         */
2670        protected Dimension calculateSize(boolean minimum) {
2671            int tabPlacement = tabPane.getTabPlacement();
2672            Insets insets = tabPane.getInsets();
2673            Insets contentInsets = getContentBorderInsets(tabPlacement);
2674            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2675
2676            Dimension zeroSize = new Dimension(0,0);
2677            int height = 0;
2678            int width = 0;
2679            int cWidth = 0;
2680            int cHeight = 0;
2681
2682            // Determine minimum size required to display largest
2683            // child in each dimension
2684            //
2685            for (int i = 0; i < tabPane.getTabCount(); i++) {
2686                Component component = tabPane.getComponentAt(i);
2687                if (component != null) {
2688                    Dimension size = minimum ? component.getMinimumSize() :
2689                                component.getPreferredSize();
2690
2691                    if (size != null) {
2692                        cHeight = Math.max(size.height, cHeight);
2693                        cWidth = Math.max(size.width, cWidth);
2694                    }
2695                }
2696            }
2697            // Add content border insets to minimum size
2698            width += cWidth;
2699            height += cHeight;
2700            int tabExtent;
2701
2702            // Calculate how much space the tabs will need, based on the
2703            // minimum size required to display largest child + content border
2704            //
2705            switch(tabPlacement) {
2706              case LEFT:
2707              case RIGHT:
2708                  height = Math.max(height, calculateMaxTabHeight(tabPlacement));
2709                  tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
2710                  width += tabExtent;
2711                  break;
2712              case TOP:
2713              case BOTTOM:
2714              default:
2715                  width = Math.max(width, calculateMaxTabWidth(tabPlacement));
2716                  tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
2717                  height += tabExtent;
2718            }
2719            return new Dimension(width + insets.left + insets.right + contentInsets.left + contentInsets.right,
2720                             height + insets.bottom + insets.top + contentInsets.top + contentInsets.bottom);
2721
2722        }
2723
2724        /**
2725         * Returns the preferred tab area height.
2726         * @param tabPlacement the tab placement
2727         * @param width the width
2728         * @return the preferred tab area height
2729         */
2730        protected int preferredTabAreaHeight(int tabPlacement, int width) {
2731            FontMetrics metrics = getFontMetrics();
2732            int tabCount = tabPane.getTabCount();
2733            int total = 0;
2734            if (tabCount > 0) {
2735                int rows = 1;
2736                int x = 0;
2737
2738                int maxTabHeight = calculateMaxTabHeight(tabPlacement);
2739
2740                for (int i = 0; i < tabCount; i++) {
2741                    int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
2742
2743                    if (x != 0 && x + tabWidth > width) {
2744                        rows++;
2745                        x = 0;
2746                    }
2747                    x += tabWidth;
2748                }
2749                total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
2750            }
2751            return total;
2752        }
2753
2754        /**
2755         * Returns the preferred tab area width.
2756         * @param tabPlacement the tab placement
2757         * @param height the height
2758         * @return the preferred tab area widty
2759         */
2760        protected int preferredTabAreaWidth(int tabPlacement, int height) {
2761            FontMetrics metrics = getFontMetrics();
2762            int tabCount = tabPane.getTabCount();
2763            int total = 0;
2764            if (tabCount > 0) {
2765                int columns = 1;
2766                int y = 0;
2767                int fontHeight = metrics.getHeight();
2768
2769                maxTabWidth = calculateMaxTabWidth(tabPlacement);
2770
2771                for (int i = 0; i < tabCount; i++) {
2772                    int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
2773
2774                    if (y != 0 && y + tabHeight > height) {
2775                        columns++;
2776                        y = 0;
2777                    }
2778                    y += tabHeight;
2779                }
2780                total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
2781            }
2782            return total;
2783        }
2784
2785        /** {@inheritDoc} */
2786        @SuppressWarnings("deprecation")
2787        public void layoutContainer(Container parent) {
2788            /* Some of the code in this method deals with changing the
2789            * visibility of components to hide and show the contents for the
2790            * selected tab. This is older code that has since been duplicated
2791            * in JTabbedPane.fireStateChanged(), so as to allow visibility
2792            * changes to happen sooner (see the note there). This code remains
2793            * for backward compatibility as there are some cases, such as
2794            * subclasses that don't fireStateChanged() where it may be used.
2795            * Any changes here need to be kept in synch with
2796            * JTabbedPane.fireStateChanged().
2797            */
2798
2799            setRolloverTab(-1);
2800
2801            int tabPlacement = tabPane.getTabPlacement();
2802            Insets insets = tabPane.getInsets();
2803            int selectedIndex = tabPane.getSelectedIndex();
2804            Component visibleComponent = getVisibleComponent();
2805
2806            calculateLayoutInfo();
2807
2808            Component selectedComponent = null;
2809            if (selectedIndex < 0) {
2810                if (visibleComponent != null) {
2811                    // The last tab was removed, so remove the component
2812                    setVisibleComponent(null);
2813                }
2814            } else {
2815                selectedComponent = tabPane.getComponentAt(selectedIndex);
2816            }
2817            int cx, cy, cw, ch;
2818            int totalTabWidth = 0;
2819            int totalTabHeight = 0;
2820            Insets contentInsets = getContentBorderInsets(tabPlacement);
2821
2822            boolean shouldChangeFocus = false;
2823
2824            // In order to allow programs to use a single component
2825            // as the display for multiple tabs, we will not change
2826            // the visible compnent if the currently selected tab
2827            // has a null component.  This is a bit dicey, as we don't
2828            // explicitly state we support this in the spec, but since
2829            // programs are now depending on this, we're making it work.
2830            //
2831            if(selectedComponent != null) {
2832                if(selectedComponent != visibleComponent &&
2833                        visibleComponent != null) {
2834                    if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
2835                        shouldChangeFocus = true;
2836                    }
2837                }
2838                setVisibleComponent(selectedComponent);
2839            }
2840
2841            Rectangle bounds = tabPane.getBounds();
2842            int numChildren = tabPane.getComponentCount();
2843
2844            if(numChildren > 0) {
2845
2846                switch(tabPlacement) {
2847                    case LEFT:
2848                        totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2849                        cx = insets.left + totalTabWidth + contentInsets.left;
2850                        cy = insets.top + contentInsets.top;
2851                        break;
2852                    case RIGHT:
2853                        totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2854                        cx = insets.left + contentInsets.left;
2855                        cy = insets.top + contentInsets.top;
2856                        break;
2857                    case BOTTOM:
2858                        totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2859                        cx = insets.left + contentInsets.left;
2860                        cy = insets.top + contentInsets.top;
2861                        break;
2862                    case TOP:
2863                    default:
2864                        totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2865                        cx = insets.left + contentInsets.left;
2866                        cy = insets.top + totalTabHeight + contentInsets.top;
2867                }
2868
2869                cw = bounds.width - totalTabWidth -
2870                        insets.left - insets.right -
2871                        contentInsets.left - contentInsets.right;
2872                ch = bounds.height - totalTabHeight -
2873                        insets.top - insets.bottom -
2874                        contentInsets.top - contentInsets.bottom;
2875
2876                for(int i = 0; i < numChildren; i++) {
2877                    Component child = tabPane.getComponent(i);
2878                    if(child == tabContainer) {
2879
2880                        int tabContainerWidth = totalTabWidth == 0 ? bounds.width :
2881                                totalTabWidth + insets.left + insets.right +
2882                                        contentInsets.left + contentInsets.right;
2883                        int tabContainerHeight = totalTabHeight == 0 ? bounds.height :
2884                                totalTabHeight + insets.top + insets.bottom +
2885                                        contentInsets.top + contentInsets.bottom;
2886
2887                        int tabContainerX = 0;
2888                        int tabContainerY = 0;
2889                        if(tabPlacement == BOTTOM) {
2890                            tabContainerY = bounds.height - tabContainerHeight;
2891                        } else if(tabPlacement == RIGHT) {
2892                            tabContainerX = bounds.width - tabContainerWidth;
2893                        }
2894                        child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
2895                    } else {
2896                        child.setBounds(cx, cy, cw, ch);
2897                    }
2898                }
2899            }
2900            layoutTabComponents();
2901            if(shouldChangeFocus) {
2902                if(!requestFocusForVisibleComponent()) {
2903                    tabPane.requestFocus();
2904                }
2905            }
2906        }
2907
2908        /**
2909         * Calculates the layout info.
2910         */
2911        public void calculateLayoutInfo() {
2912            int tabCount = tabPane.getTabCount();
2913            assureRectsCreated(tabCount);
2914            calculateTabRects(tabPane.getTabPlacement(), tabCount);
2915            isRunsDirty = false;
2916        }
2917
2918        private void layoutTabComponents() {
2919            if (tabContainer == null) {
2920                return;
2921            }
2922            Rectangle rect = new Rectangle();
2923            Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
2924            if (scrollableTabLayoutEnabled()) {
2925                translatePointToTabPanel(0, 0, delta);
2926            }
2927            for (int i = 0; i < tabPane.getTabCount(); i++) {
2928                Component c = tabPane.getTabComponentAt(i);
2929                if (c == null) {
2930                    continue;
2931                }
2932                getTabBounds(i, rect);
2933                Dimension preferredSize = c.getPreferredSize();
2934                Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
2935                int outerX = rect.x + insets.left + delta.x;
2936                int outerY = rect.y + insets.top + delta.y;
2937                int outerWidth = rect.width - insets.left - insets.right;
2938                int outerHeight = rect.height - insets.top - insets.bottom;
2939                //centralize component
2940                int x = outerX + (outerWidth - preferredSize.width) / 2;
2941                int y = outerY + (outerHeight - preferredSize.height) / 2;
2942                int tabPlacement = tabPane.getTabPlacement();
2943                boolean isSeleceted = i == tabPane.getSelectedIndex();
2944                c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted),
2945                            y + getTabLabelShiftY(tabPlacement, i, isSeleceted),
2946                        preferredSize.width, preferredSize.height);
2947            }
2948        }
2949
2950        /**
2951         * Calculate the tab rectangles.
2952         * @param tabPlacement the tab placement
2953         * @param tabCount the tab count
2954         */
2955        protected void calculateTabRects(int tabPlacement, int tabCount) {
2956            FontMetrics metrics = getFontMetrics();
2957            Dimension size = tabPane.getSize();
2958            Insets insets = tabPane.getInsets();
2959            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2960            int fontHeight = metrics.getHeight();
2961            int selectedIndex = tabPane.getSelectedIndex();
2962            int tabRunOverlay;
2963            int i, j;
2964            int x, y;
2965            int returnAt;
2966            boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2967            boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
2968
2969            //
2970            // Calculate bounds within which a tab run must fit
2971            //
2972            switch(tabPlacement) {
2973              case LEFT:
2974                  maxTabWidth = calculateMaxTabWidth(tabPlacement);
2975                  x = insets.left + tabAreaInsets.left;
2976                  y = insets.top + tabAreaInsets.top;
2977                  returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2978                  break;
2979              case RIGHT:
2980                  maxTabWidth = calculateMaxTabWidth(tabPlacement);
2981                  x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
2982                  y = insets.top + tabAreaInsets.top;
2983                  returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2984                  break;
2985              case BOTTOM:
2986                  maxTabHeight = calculateMaxTabHeight(tabPlacement);
2987                  x = insets.left + tabAreaInsets.left;
2988                  y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
2989                  returnAt = size.width - (insets.right + tabAreaInsets.right);
2990                  break;
2991              case TOP:
2992              default:
2993                  maxTabHeight = calculateMaxTabHeight(tabPlacement);
2994                  x = insets.left + tabAreaInsets.left;
2995                  y = insets.top + tabAreaInsets.top;
2996                  returnAt = size.width - (insets.right + tabAreaInsets.right);
2997                  break;
2998            }
2999
3000            tabRunOverlay = getTabRunOverlay(tabPlacement);
3001
3002            runCount = 0;
3003            selectedRun = -1;
3004
3005            if (tabCount == 0) {
3006                return;
3007            }
3008
3009            // Run through tabs and partition them into runs
3010            Rectangle rect;
3011            for (i = 0; i < tabCount; i++) {
3012                rect = rects[i];
3013
3014                if (!verticalTabRuns) {
3015                    // Tabs on TOP or BOTTOM....
3016                    if (i > 0) {
3017                        rect.x = rects[i-1].x + rects[i-1].width;
3018                    } else {
3019                        tabRuns[0] = 0;
3020                        runCount = 1;
3021                        maxTabWidth = 0;
3022                        rect.x = x;
3023                    }
3024                    rect.width = calculateTabWidth(tabPlacement, i, metrics);
3025                    maxTabWidth = Math.max(maxTabWidth, rect.width);
3026
3027                    // Never move a TAB down a run if it is in the first column.
3028                    // Even if there isn't enough room, moving it to a fresh
3029                    // line won't help.
3030                    if (rect.x != x && rect.x + rect.width > returnAt) {
3031                        if (runCount > tabRuns.length - 1) {
3032                            expandTabRunsArray();
3033                        }
3034                        tabRuns[runCount] = i;
3035                        runCount++;
3036                        rect.x = x;
3037                    }
3038                    // Initialize y position in case there's just one run
3039                    rect.y = y;
3040                    rect.height = maxTabHeight/* - 2*/;
3041
3042                } else {
3043                    // Tabs on LEFT or RIGHT...
3044                    if (i > 0) {
3045                        rect.y = rects[i-1].y + rects[i-1].height;
3046                    } else {
3047                        tabRuns[0] = 0;
3048                        runCount = 1;
3049                        maxTabHeight = 0;
3050                        rect.y = y;
3051                    }
3052                    rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3053                    maxTabHeight = Math.max(maxTabHeight, rect.height);
3054
3055                    // Never move a TAB over a run if it is in the first run.
3056                    // Even if there isn't enough room, moving it to a fresh
3057                    // column won't help.
3058                    if (rect.y != y && rect.y + rect.height > returnAt) {
3059                        if (runCount > tabRuns.length - 1) {
3060                            expandTabRunsArray();
3061                        }
3062                        tabRuns[runCount] = i;
3063                        runCount++;
3064                        rect.y = y;
3065                    }
3066                    // Initialize x position in case there's just one column
3067                    rect.x = x;
3068                    rect.width = maxTabWidth/* - 2*/;
3069
3070                }
3071                if (i == selectedIndex) {
3072                    selectedRun = runCount - 1;
3073                }
3074            }
3075
3076            if (runCount > 1) {
3077                // Re-distribute tabs in case last run has leftover space
3078                normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
3079
3080                selectedRun = getRunForTab(tabCount, selectedIndex);
3081
3082                // Rotate run array so that selected run is first
3083                if (shouldRotateTabRuns(tabPlacement)) {
3084                    rotateTabRuns(tabPlacement, selectedRun);
3085                }
3086            }
3087
3088            // Step through runs from back to front to calculate
3089            // tab y locations and to pad runs appropriately
3090            for (i = runCount - 1; i >= 0; i--) {
3091                int start = tabRuns[i];
3092                int next = tabRuns[i == (runCount - 1)? 0 : i + 1];
3093                int end = (next != 0? next - 1 : tabCount - 1);
3094                if (!verticalTabRuns) {
3095                    for (j = start; j <= end; j++) {
3096                        rect = rects[j];
3097                        rect.y = y;
3098                        rect.x += getTabRunIndent(tabPlacement, i);
3099                    }
3100                    if (shouldPadTabRun(tabPlacement, i)) {
3101                        padTabRun(tabPlacement, start, end, returnAt);
3102                    }
3103                    if (tabPlacement == BOTTOM) {
3104                        y -= (maxTabHeight - tabRunOverlay);
3105                    } else {
3106                        y += (maxTabHeight - tabRunOverlay);
3107                    }
3108                } else {
3109                    for (j = start; j <= end; j++) {
3110                        rect = rects[j];
3111                        rect.x = x;
3112                        rect.y += getTabRunIndent(tabPlacement, i);
3113                    }
3114                    if (shouldPadTabRun(tabPlacement, i)) {
3115                        padTabRun(tabPlacement, start, end, returnAt);
3116                    }
3117                    if (tabPlacement == RIGHT) {
3118                        x -= (maxTabWidth - tabRunOverlay);
3119                    } else {
3120                        x += (maxTabWidth - tabRunOverlay);
3121                    }
3122                }
3123            }
3124
3125            // Pad the selected tab so that it appears raised in front
3126            padSelectedTab(tabPlacement, selectedIndex);
3127
3128            // if right to left and tab placement on the top or
3129            // the bottom, flip x positions and adjust by widths
3130            if (!leftToRight && !verticalTabRuns) {
3131                int rightMargin = size.width
3132                                  - (insets.right + tabAreaInsets.right);
3133                for (i = 0; i < tabCount; i++) {
3134                    rects[i].x = rightMargin - rects[i].x - rects[i].width;
3135                }
3136            }
3137        }
3138
3139
3140        /**
3141         * Rotates the run-index array so that the selected run is run[0].
3142         * @param tabPlacement the tab placement
3143         * @param selectedRun the selected run
3144         */
3145        protected void rotateTabRuns(int tabPlacement, int selectedRun) {
3146            for (int i = 0; i < selectedRun; i++) {
3147                int save = tabRuns[0];
3148                for (int j = 1; j < runCount; j++) {
3149                    tabRuns[j - 1] = tabRuns[j];
3150                }
3151                tabRuns[runCount-1] = save;
3152            }
3153        }
3154
3155        /**
3156         * Normalizes the tab runs.
3157         * @param tabPlacement the tab placement
3158         * @param tabCount the tab count
3159         * @param start the start
3160         * @param max the max
3161         */
3162        protected void normalizeTabRuns(int tabPlacement, int tabCount,
3163                                     int start, int max) {
3164            boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
3165            int run = runCount - 1;
3166            boolean keepAdjusting = true;
3167            double weight = 1.25;
3168
3169            // At this point the tab runs are packed to fit as many
3170            // tabs as possible, which can leave the last run with a lot
3171            // of extra space (resulting in very fat tabs on the last run).
3172            // So we'll attempt to distribute this extra space more evenly
3173            // across the runs in order to make the runs look more consistent.
3174            //
3175            // Starting with the last run, determine whether the last tab in
3176            // the previous run would fit (generously) in this run; if so,
3177            // move tab to current run and shift tabs accordingly.  Cycle
3178            // through remaining runs using the same algorithm.
3179            //
3180            while (keepAdjusting) {
3181                int last = lastTabInRun(tabCount, run);
3182                int prevLast = lastTabInRun(tabCount, run-1);
3183                int end;
3184                int prevLastLen;
3185
3186                if (!verticalTabRuns) {
3187                    end = rects[last].x + rects[last].width;
3188                    prevLastLen = (int)(maxTabWidth*weight);
3189                } else {
3190                    end = rects[last].y + rects[last].height;
3191                    prevLastLen = (int)(maxTabHeight*weight*2);
3192                }
3193
3194                // Check if the run has enough extra space to fit the last tab
3195                // from the previous row...
3196                if (max - end > prevLastLen) {
3197
3198                    // Insert tab from previous row and shift rest over
3199                    tabRuns[run] = prevLast;
3200                    if (!verticalTabRuns) {
3201                        rects[prevLast].x = start;
3202                    } else {
3203                        rects[prevLast].y = start;
3204                    }
3205                    for (int i = prevLast+1; i <= last; i++) {
3206                        if (!verticalTabRuns) {
3207                            rects[i].x = rects[i-1].x + rects[i-1].width;
3208                        } else {
3209                            rects[i].y = rects[i-1].y + rects[i-1].height;
3210                        }
3211                    }
3212
3213                } else if (run == runCount - 1) {
3214                    // no more room left in last run, so we're done!
3215                    keepAdjusting = false;
3216                }
3217                if (run - 1 > 0) {
3218                    // check previous run next...
3219                    run -= 1;
3220                } else {
3221                    // check last run again...but require a higher ratio
3222                    // of extraspace-to-tabsize because we don't want to
3223                    // end up with too many tabs on the last run!
3224                    run = runCount - 1;
3225                    weight += .25;
3226                }
3227            }
3228        }
3229
3230        /**
3231         * Pads the tab run.
3232         * @param tabPlacement the tab placement
3233         * @param start the start
3234         * @param end the end
3235         * @param max the max
3236         */
3237        protected void padTabRun(int tabPlacement, int start, int end, int max) {
3238            Rectangle lastRect = rects[end];
3239            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3240                int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
3241                int deltaWidth = max - (lastRect.x + lastRect.width);
3242                float factor = (float)deltaWidth / (float)runWidth;
3243
3244                for (int j = start; j <= end; j++) {
3245                    Rectangle pastRect = rects[j];
3246                    if (j > start) {
3247                        pastRect.x = rects[j-1].x + rects[j-1].width;
3248                    }
3249                    pastRect.width += Math.round((float)pastRect.width * factor);
3250                }
3251                lastRect.width = max - lastRect.x;
3252            } else {
3253                int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
3254                int deltaHeight = max - (lastRect.y + lastRect.height);
3255                float factor = (float)deltaHeight / (float)runHeight;
3256
3257                for (int j = start; j <= end; j++) {
3258                    Rectangle pastRect = rects[j];
3259                    if (j > start) {
3260                        pastRect.y = rects[j-1].y + rects[j-1].height;
3261                    }
3262                    pastRect.height += Math.round((float)pastRect.height * factor);
3263                }
3264                lastRect.height = max - lastRect.y;
3265            }
3266        }
3267
3268        /**
3269         * Pads selected tab.
3270         * @param tabPlacement the tab placement
3271         * @param selectedIndex the selected index
3272         */
3273        protected void padSelectedTab(int tabPlacement, int selectedIndex) {
3274
3275            if (selectedIndex >= 0) {
3276                Rectangle selRect = rects[selectedIndex];
3277                Insets padInsets = getSelectedTabPadInsets(tabPlacement);
3278                selRect.x -= padInsets.left;
3279                selRect.width += (padInsets.left + padInsets.right);
3280                selRect.y -= padInsets.top;
3281                selRect.height += (padInsets.top + padInsets.bottom);
3282
3283                if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
3284                    // do not expand selected tab more then necessary
3285                    Dimension size = tabPane.getSize();
3286                    Insets insets = tabPane.getInsets();
3287
3288                    if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) {
3289                        int top = insets.top - selRect.y;
3290                        if (top > 0) {
3291                            selRect.y += top;
3292                            selRect.height -= top;
3293                        }
3294                        int bottom = (selRect.y + selRect.height) + insets.bottom - size.height;
3295                        if (bottom > 0) {
3296                            selRect.height -= bottom;
3297                        }
3298                    } else {
3299                        int left = insets.left - selRect.x;
3300                        if (left > 0) {
3301                            selRect.x += left;
3302                            selRect.width -= left;
3303                        }
3304                        int right = (selRect.x + selRect.width) + insets.right - size.width;
3305                        if (right > 0) {
3306                            selRect.width -= right;
3307                        }
3308                    }
3309                }
3310            }
3311        }
3312    }
3313
3314    private class TabbedPaneScrollLayout extends TabbedPaneLayout {
3315
3316        protected int preferredTabAreaHeight(int tabPlacement, int width) {
3317            return calculateMaxTabHeight(tabPlacement);
3318        }
3319
3320        protected int preferredTabAreaWidth(int tabPlacement, int height) {
3321            return calculateMaxTabWidth(tabPlacement);
3322        }
3323
3324        @SuppressWarnings("deprecation")
3325        public void layoutContainer(Container parent) {
3326            /* Some of the code in this method deals with changing the
3327             * visibility of components to hide and show the contents for the
3328             * selected tab. This is older code that has since been duplicated
3329             * in JTabbedPane.fireStateChanged(), so as to allow visibility
3330             * changes to happen sooner (see the note there). This code remains
3331             * for backward compatibility as there are some cases, such as
3332             * subclasses that don't fireStateChanged() where it may be used.
3333             * Any changes here need to be kept in synch with
3334             * JTabbedPane.fireStateChanged().
3335             */
3336
3337            setRolloverTab(-1);
3338
3339            int tabPlacement = tabPane.getTabPlacement();
3340            int tabCount = tabPane.getTabCount();
3341            Insets insets = tabPane.getInsets();
3342            int selectedIndex = tabPane.getSelectedIndex();
3343            Component visibleComponent = getVisibleComponent();
3344
3345            calculateLayoutInfo();
3346
3347            Component selectedComponent = null;
3348            if (selectedIndex < 0) {
3349                if (visibleComponent != null) {
3350                    // The last tab was removed, so remove the component
3351                    setVisibleComponent(null);
3352                }
3353            } else {
3354                selectedComponent = tabPane.getComponentAt(selectedIndex);
3355            }
3356
3357            if (tabPane.getTabCount() == 0) {
3358                tabScroller.croppedEdge.resetParams();
3359                tabScroller.scrollForwardButton.setVisible(false);
3360                tabScroller.scrollBackwardButton.setVisible(false);
3361                return;
3362            }
3363
3364            boolean shouldChangeFocus = false;
3365
3366            // In order to allow programs to use a single component
3367            // as the display for multiple tabs, we will not change
3368            // the visible compnent if the currently selected tab
3369            // has a null component.  This is a bit dicey, as we don't
3370            // explicitly state we support this in the spec, but since
3371            // programs are now depending on this, we're making it work.
3372            //
3373            if(selectedComponent != null) {
3374                if(selectedComponent != visibleComponent &&
3375                        visibleComponent != null) {
3376                    if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
3377                        shouldChangeFocus = true;
3378                    }
3379                }
3380                setVisibleComponent(selectedComponent);
3381            }
3382            int tx, ty, tw, th; // tab area bounds
3383            int cx, cy, cw, ch; // content area bounds
3384            Insets contentInsets = getContentBorderInsets(tabPlacement);
3385            Rectangle bounds = tabPane.getBounds();
3386            int numChildren = tabPane.getComponentCount();
3387
3388            if(numChildren > 0) {
3389                switch(tabPlacement) {
3390                    case LEFT:
3391                        // calculate tab area bounds
3392                        tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
3393                        th = bounds.height - insets.top - insets.bottom;
3394                        tx = insets.left;
3395                        ty = insets.top;
3396
3397                        // calculate content area bounds
3398                        cx = tx + tw + contentInsets.left;
3399                        cy = ty + contentInsets.top;
3400                        cw = bounds.width - insets.left - insets.right - tw -
3401                                contentInsets.left - contentInsets.right;
3402                        ch = bounds.height - insets.top - insets.bottom -
3403                                contentInsets.top - contentInsets.bottom;
3404                        break;
3405                    case RIGHT:
3406                        // calculate tab area bounds
3407                        tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
3408                        th = bounds.height - insets.top - insets.bottom;
3409                        tx = bounds.width - insets.right - tw;
3410                        ty = insets.top;
3411
3412                        // calculate content area bounds
3413                        cx = insets.left + contentInsets.left;
3414                        cy = insets.top + contentInsets.top;
3415                        cw = bounds.width - insets.left - insets.right - tw -
3416                                contentInsets.left - contentInsets.right;
3417                        ch = bounds.height - insets.top - insets.bottom -
3418                                contentInsets.top - contentInsets.bottom;
3419                        break;
3420                    case BOTTOM:
3421                        // calculate tab area bounds
3422                        tw = bounds.width - insets.left - insets.right;
3423                        th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
3424                        tx = insets.left;
3425                        ty = bounds.height - insets.bottom - th;
3426
3427                        // calculate content area bounds
3428                        cx = insets.left + contentInsets.left;
3429                        cy = insets.top + contentInsets.top;
3430                        cw = bounds.width - insets.left - insets.right -
3431                                contentInsets.left - contentInsets.right;
3432                        ch = bounds.height - insets.top - insets.bottom - th -
3433                                contentInsets.top - contentInsets.bottom;
3434                        break;
3435                    case TOP:
3436                    default:
3437                        // calculate tab area bounds
3438                        tw = bounds.width - insets.left - insets.right;
3439                        th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
3440                        tx = insets.left;
3441                        ty = insets.top;
3442
3443                        // calculate content area bounds
3444                        cx = tx + contentInsets.left;
3445                        cy = ty + th + contentInsets.top;
3446                        cw = bounds.width - insets.left - insets.right -
3447                                contentInsets.left - contentInsets.right;
3448                        ch = bounds.height - insets.top - insets.bottom - th -
3449                                contentInsets.top - contentInsets.bottom;
3450                }
3451
3452                for(int i = 0; i < numChildren; i++) {
3453                    Component child = tabPane.getComponent(i);
3454
3455                    if(tabScroller != null && child == tabScroller.viewport) {
3456                        JViewport viewport = (JViewport) child;
3457                        Rectangle viewRect = viewport.getViewRect();
3458                        int vw = tw;
3459                        int vh = th;
3460                        Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
3461                        switch(tabPlacement) {
3462                            case LEFT:
3463                            case RIGHT:
3464                                int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3465                                if(totalTabHeight > th) {
3466                                    // Allow space for scrollbuttons
3467                                    vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0;
3468                                    if(totalTabHeight - viewRect.y <= vh) {
3469                                        // Scrolled to the end, so ensure the viewport size is
3470                                        // such that the scroll offset aligns with a tab
3471                                        vh = totalTabHeight - viewRect.y;
3472                                    }
3473                                }
3474                                break;
3475                            case BOTTOM:
3476                            case TOP:
3477                            default:
3478                                int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3479                                if(totalTabWidth > tw) {
3480                                    // Need to allow space for scrollbuttons
3481                                    vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0;
3482                                    if(totalTabWidth - viewRect.x <= vw) {
3483                                        // Scrolled to the end, so ensure the viewport size is
3484                                        // such that the scroll offset aligns with a tab
3485                                        vw = totalTabWidth - viewRect.x;
3486                                    }
3487                                }
3488                        }
3489                        child.setBounds(tx, ty, vw, vh);
3490
3491                    } else if(tabScroller != null &&
3492                            (child == tabScroller.scrollForwardButton ||
3493                            child == tabScroller.scrollBackwardButton)) {
3494                        Component scrollbutton = child;
3495                        Dimension bsize = scrollbutton.getPreferredSize();
3496                        int bx = 0;
3497                        int by = 0;
3498                        int bw = bsize.width;
3499                        int bh = bsize.height;
3500                        boolean visible = false;
3501
3502                        switch(tabPlacement) {
3503                            case LEFT:
3504                            case RIGHT:
3505                                int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3506                                if(totalTabHeight > th) {
3507                                    visible = true;
3508                                    bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx);
3509                                    by = (child == tabScroller.scrollForwardButton) ?
3510                                            bounds.height - insets.bottom - bsize.height :
3511                                            bounds.height - insets.bottom - 2 * bsize.height;
3512                                }
3513                                break;
3514
3515                            case BOTTOM:
3516                            case TOP:
3517                            default:
3518                                int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3519
3520                                if(totalTabWidth > tw) {
3521                                    visible = true;
3522                                    bx = (child == tabScroller.scrollForwardButton) ?
3523                                            bounds.width - insets.left - bsize.width :
3524                                            bounds.width - insets.left - 2 * bsize.width;
3525                                    by = (tabPlacement == TOP ? ty + th - bsize.height : ty);
3526                                }
3527                        }
3528                        child.setVisible(visible);
3529                        if(visible) {
3530                            child.setBounds(bx, by, bw, bh);
3531                        }
3532
3533                    } else {
3534                        // All content children...
3535                        child.setBounds(cx, cy, cw, ch);
3536                    }
3537                }
3538                super.layoutTabComponents();
3539                layoutCroppedEdge();
3540                if(shouldChangeFocus) {
3541                    if(!requestFocusForVisibleComponent()) {
3542                        tabPane.requestFocus();
3543                    }
3544                }
3545            }
3546        }
3547
3548        private void layoutCroppedEdge() {
3549            tabScroller.croppedEdge.resetParams();
3550            Rectangle viewRect = tabScroller.viewport.getViewRect();
3551            int cropline;
3552            for (int i = 0; i < rects.length; i++) {
3553                Rectangle tabRect = rects[i];
3554                switch (tabPane.getTabPlacement()) {
3555                    case LEFT:
3556                    case RIGHT:
3557                        cropline = viewRect.y + viewRect.height;
3558                        if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
3559                            tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1,
3560                                    -currentTabAreaInsets.left,  0);
3561                        }
3562                        break;
3563                    case TOP:
3564                    case BOTTOM:
3565                    default:
3566                        cropline = viewRect.x + viewRect.width;
3567                        if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) {
3568                            tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1,
3569                                    0, -currentTabAreaInsets.top);
3570                        }
3571                }
3572            }
3573        }
3574
3575        protected void calculateTabRects(int tabPlacement, int tabCount) {
3576            FontMetrics metrics = getFontMetrics();
3577            Dimension size = tabPane.getSize();
3578            Insets insets = tabPane.getInsets();
3579            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
3580            int fontHeight = metrics.getHeight();
3581            int selectedIndex = tabPane.getSelectedIndex();
3582            int i;
3583            boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
3584            boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
3585            int x = tabAreaInsets.left;
3586            int y = tabAreaInsets.top;
3587            int totalWidth = 0;
3588            int totalHeight = 0;
3589
3590            //
3591            // Calculate bounds within which a tab run must fit
3592            //
3593            switch(tabPlacement) {
3594              case LEFT:
3595              case RIGHT:
3596                  maxTabWidth = calculateMaxTabWidth(tabPlacement);
3597                  break;
3598              case BOTTOM:
3599              case TOP:
3600              default:
3601                  maxTabHeight = calculateMaxTabHeight(tabPlacement);
3602            }
3603
3604            runCount = 0;
3605            selectedRun = -1;
3606
3607            if (tabCount == 0) {
3608                return;
3609            }
3610
3611            selectedRun = 0;
3612            runCount = 1;
3613
3614            // Run through tabs and lay them out in a single run
3615            Rectangle rect;
3616            for (i = 0; i < tabCount; i++) {
3617                rect = rects[i];
3618
3619                if (!verticalTabRuns) {
3620                    // Tabs on TOP or BOTTOM....
3621                    if (i > 0) {
3622                        rect.x = rects[i-1].x + rects[i-1].width;
3623                    } else {
3624                        tabRuns[0] = 0;
3625                        maxTabWidth = 0;
3626                        totalHeight += maxTabHeight;
3627                        rect.x = x;
3628                    }
3629                    rect.width = calculateTabWidth(tabPlacement, i, metrics);
3630                    totalWidth = rect.x + rect.width;
3631                    maxTabWidth = Math.max(maxTabWidth, rect.width);
3632
3633                    rect.y = y;
3634                    rect.height = maxTabHeight/* - 2*/;
3635
3636                } else {
3637                    // Tabs on LEFT or RIGHT...
3638                    if (i > 0) {
3639                        rect.y = rects[i-1].y + rects[i-1].height;
3640                    } else {
3641                        tabRuns[0] = 0;
3642                        maxTabHeight = 0;
3643                        totalWidth = maxTabWidth;
3644                        rect.y = y;
3645                    }
3646                    rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3647                    totalHeight = rect.y + rect.height;
3648                    maxTabHeight = Math.max(maxTabHeight, rect.height);
3649
3650                    rect.x = x;
3651                    rect.width = maxTabWidth/* - 2*/;
3652
3653                }
3654            }
3655
3656            if (tabsOverlapBorder) {
3657                // Pad the selected tab so that it appears raised in front
3658                padSelectedTab(tabPlacement, selectedIndex);
3659            }
3660
3661            // if right to left and tab placement on the top or
3662            // the bottom, flip x positions and adjust by widths
3663            if (!leftToRight && !verticalTabRuns) {
3664                int rightMargin = size.width
3665                                  - (insets.right + tabAreaInsets.right);
3666                for (i = 0; i < tabCount; i++) {
3667                    rects[i].x = rightMargin - rects[i].x - rects[i].width;
3668                }
3669            }
3670            tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
3671            tabScroller.tabPanel.invalidate();
3672        }
3673    }
3674
3675    private class ScrollableTabSupport implements ActionListener,
3676                            ChangeListener {
3677        public ScrollableTabViewport viewport;
3678        public ScrollableTabPanel tabPanel;
3679        public JButton scrollForwardButton;
3680        public JButton scrollBackwardButton;
3681        public CroppedEdge croppedEdge;
3682        public int leadingTabIndex;
3683
3684        private Point tabViewPosition = new Point(0,0);
3685
3686        ScrollableTabSupport(int tabPlacement) {
3687            viewport = new ScrollableTabViewport();
3688            tabPanel = new ScrollableTabPanel();
3689            viewport.setView(tabPanel);
3690            viewport.addChangeListener(this);
3691            croppedEdge = new CroppedEdge();
3692            createButtons();
3693        }
3694
3695        /**
3696         * Recreates the scroll buttons and adds them to the TabbedPane.
3697         */
3698        void createButtons() {
3699            if (scrollForwardButton != null) {
3700                tabPane.remove(scrollForwardButton);
3701                scrollForwardButton.removeActionListener(this);
3702                tabPane.remove(scrollBackwardButton);
3703                scrollBackwardButton.removeActionListener(this);
3704            }
3705            int tabPlacement = tabPane.getTabPlacement();
3706            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3707                scrollForwardButton = createScrollButton(EAST);
3708                scrollBackwardButton = createScrollButton(WEST);
3709
3710            } else { // tabPlacement = LEFT || RIGHT
3711                scrollForwardButton = createScrollButton(SOUTH);
3712                scrollBackwardButton = createScrollButton(NORTH);
3713            }
3714            scrollForwardButton.addActionListener(this);
3715            scrollBackwardButton.addActionListener(this);
3716            tabPane.add(scrollForwardButton);
3717            tabPane.add(scrollBackwardButton);
3718        }
3719
3720        public void scrollForward(int tabPlacement) {
3721            Dimension viewSize = viewport.getViewSize();
3722            Rectangle viewRect = viewport.getViewRect();
3723
3724            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3725                if (viewRect.width >= viewSize.width - viewRect.x) {
3726                    return; // no room left to scroll
3727                }
3728            } else { // tabPlacement == LEFT || tabPlacement == RIGHT
3729                if (viewRect.height >= viewSize.height - viewRect.y) {
3730                    return;
3731                }
3732            }
3733            setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
3734        }
3735
3736        public void scrollBackward(int tabPlacement) {
3737            if (leadingTabIndex == 0) {
3738                return; // no room left to scroll
3739            }
3740            setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
3741        }
3742
3743        public void setLeadingTabIndex(int tabPlacement, int index) {
3744            leadingTabIndex = index;
3745            Dimension viewSize = viewport.getViewSize();
3746            Rectangle viewRect = viewport.getViewRect();
3747
3748            switch(tabPlacement) {
3749              case TOP:
3750              case BOTTOM:
3751                tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x;
3752
3753                if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
3754                    // We've scrolled to the end, so adjust the viewport size
3755                    // to ensure the view position remains aligned on a tab boundary
3756                    Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x,
3757                                                         viewRect.height);
3758                    viewport.setExtentSize(extentSize);
3759                }
3760                break;
3761              case LEFT:
3762              case RIGHT:
3763                tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
3764
3765                if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
3766                // We've scrolled to the end, so adjust the viewport size
3767                // to ensure the view position remains aligned on a tab boundary
3768                     Dimension extentSize = new Dimension(viewRect.width,
3769                                                          viewSize.height - tabViewPosition.y);
3770                     viewport.setExtentSize(extentSize);
3771                }
3772            }
3773            viewport.setViewPosition(tabViewPosition);
3774        }
3775
3776        public void stateChanged(ChangeEvent e) {
3777            updateView();
3778        }
3779
3780        private void updateView() {
3781            int tabPlacement = tabPane.getTabPlacement();
3782            int tabCount = tabPane.getTabCount();
3783            assureRectsCreated(tabCount);
3784            Rectangle vpRect = viewport.getBounds();
3785            Dimension viewSize = viewport.getViewSize();
3786            Rectangle viewRect = viewport.getViewRect();
3787
3788            leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
3789
3790            // If the tab isn't right aligned, adjust it.
3791            if (leadingTabIndex + 1 < tabCount) {
3792                switch (tabPlacement) {
3793                case TOP:
3794                case BOTTOM:
3795                    if (rects[leadingTabIndex].x < viewRect.x) {
3796                        leadingTabIndex++;
3797                    }
3798                    break;
3799                case LEFT:
3800                case RIGHT:
3801                    if (rects[leadingTabIndex].y < viewRect.y) {
3802                        leadingTabIndex++;
3803                    }
3804                    break;
3805                }
3806            }
3807            Insets contentInsets = getContentBorderInsets(tabPlacement);
3808            switch(tabPlacement) {
3809              case LEFT:
3810                  tabPane.repaint(vpRect.x+vpRect.width, vpRect.y,
3811                                  contentInsets.left, vpRect.height);
3812                  scrollBackwardButton.setEnabled(
3813                          viewRect.y > 0 && leadingTabIndex > 0);
3814                  scrollForwardButton.setEnabled(
3815                          leadingTabIndex < tabCount-1 &&
3816                          viewSize.height-viewRect.y > viewRect.height);
3817                  break;
3818              case RIGHT:
3819                  tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y,
3820                                  contentInsets.right, vpRect.height);
3821                  scrollBackwardButton.setEnabled(
3822                          viewRect.y > 0 && leadingTabIndex > 0);
3823                  scrollForwardButton.setEnabled(
3824                          leadingTabIndex < tabCount-1 &&
3825                          viewSize.height-viewRect.y > viewRect.height);
3826                  break;
3827              case BOTTOM:
3828                  tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom,
3829                                  vpRect.width, contentInsets.bottom);
3830                  scrollBackwardButton.setEnabled(
3831                          viewRect.x > 0 && leadingTabIndex > 0);
3832                  scrollForwardButton.setEnabled(
3833                          leadingTabIndex < tabCount-1 &&
3834                          viewSize.width-viewRect.x > viewRect.width);
3835                  break;
3836              case TOP:
3837              default:
3838                  tabPane.repaint(vpRect.x, vpRect.y+vpRect.height,
3839                                  vpRect.width, contentInsets.top);
3840                  scrollBackwardButton.setEnabled(
3841                          viewRect.x > 0 && leadingTabIndex > 0);
3842                  scrollForwardButton.setEnabled(
3843                          leadingTabIndex < tabCount-1 &&
3844                          viewSize.width-viewRect.x > viewRect.width);
3845            }
3846        }
3847
3848        /**
3849         * ActionListener for the scroll buttons.
3850         */
3851        public void actionPerformed(ActionEvent e) {
3852            ActionMap map = tabPane.getActionMap();
3853
3854            if (map != null) {
3855                String actionKey;
3856
3857                if (e.getSource() == scrollForwardButton) {
3858                    actionKey = "scrollTabsForwardAction";
3859                }
3860                else {
3861                    actionKey = "scrollTabsBackwardAction";
3862                }
3863                Action action = map.get(actionKey);
3864
3865                if (action != null && action.isEnabled()) {
3866                    action.actionPerformed(new ActionEvent(tabPane,
3867                        ActionEvent.ACTION_PERFORMED, null, e.getWhen(),
3868                        e.getModifiers()));
3869                }
3870            }
3871        }
3872
3873        public String toString() {
3874            return "viewport.viewSize=" + viewport.getViewSize() + "\n" +
3875                              "viewport.viewRectangle="+viewport.getViewRect()+"\n"+
3876                              "leadingTabIndex="+leadingTabIndex+"\n"+
3877                              "tabViewPosition=" + tabViewPosition;
3878        }
3879
3880    }
3881
3882    @SuppressWarnings("serial") // Superclass is not serializable across versions
3883    private class ScrollableTabViewport extends JViewport implements UIResource {
3884        public ScrollableTabViewport() {
3885            super();
3886            setName("TabbedPane.scrollableViewport");
3887            setScrollMode(SIMPLE_SCROLL_MODE);
3888            setOpaque(tabPane.isOpaque());
3889            Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3890            if (bgColor == null) {
3891                bgColor = tabPane.getBackground();
3892            }
3893            setBackground(bgColor);
3894        }
3895    }
3896
3897    @SuppressWarnings("serial") // Superclass is not serializable across versions
3898    private class ScrollableTabPanel extends JPanel implements UIResource {
3899        public ScrollableTabPanel() {
3900            super(null);
3901            setOpaque(tabPane.isOpaque());
3902            Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3903            if (bgColor == null) {
3904                bgColor = tabPane.getBackground();
3905            }
3906            setBackground(bgColor);
3907        }
3908        public void paintComponent(Graphics g) {
3909            super.paintComponent(g);
3910            BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(),
3911                                                tabPane.getSelectedIndex());
3912            if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
3913                Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
3914                g.translate(croppedRect.x, croppedRect.y);
3915                tabScroller.croppedEdge.paintComponent(g);
3916                g.translate(-croppedRect.x, -croppedRect.y);
3917            }
3918        }
3919
3920        public void doLayout() {
3921            if (getComponentCount() > 0) {
3922                Component child = getComponent(0);
3923                child.setBounds(0, 0, getWidth(), getHeight());
3924            }
3925        }
3926    }
3927
3928    @SuppressWarnings("serial") // Superclass is not serializable across versions
3929    private class ScrollableTabButton extends BasicArrowButton implements UIResource,
3930                                                                            SwingConstants {
3931        public ScrollableTabButton(int direction) {
3932            super(direction,
3933                  UIManager.getColor("TabbedPane.selected"),
3934                  UIManager.getColor("TabbedPane.shadow"),
3935                  UIManager.getColor("TabbedPane.darkShadow"),
3936                  UIManager.getColor("TabbedPane.highlight"));
3937        }
3938    }
3939
3940
3941// Controller: event listeners
3942
3943    private class Handler implements ChangeListener, ContainerListener,
3944                  FocusListener, MouseListener, MouseMotionListener,
3945                  PropertyChangeListener {
3946        //
3947        // PropertyChangeListener
3948        //
3949        public void propertyChange(PropertyChangeEvent e) {
3950            JTabbedPane pane = (JTabbedPane)e.getSource();
3951            String name = e.getPropertyName();
3952            boolean isScrollLayout = scrollableTabLayoutEnabled();
3953            if (name == "mnemonicAt") {
3954                updateMnemonics();
3955                pane.repaint();
3956            }
3957            else if (name == "displayedMnemonicIndexAt") {
3958                pane.repaint();
3959            }
3960            else if (name =="indexForTitle") {
3961                calculatedBaseline = false;
3962                Integer index = (Integer) e.getNewValue();
3963                updateHtmlViews(index, false);
3964            } else if (name == "tabLayoutPolicy") {
3965                BasicTabbedPaneUI.this.uninstallUI(pane);
3966                BasicTabbedPaneUI.this.installUI(pane);
3967                calculatedBaseline = false;
3968            } else if (name == "tabPlacement") {
3969                if (scrollableTabLayoutEnabled()) {
3970                    tabScroller.createButtons();
3971                }
3972                calculatedBaseline = false;
3973            } else if (name == "opaque" && isScrollLayout) {
3974                boolean newVal = ((Boolean)e.getNewValue()).booleanValue();
3975                tabScroller.tabPanel.setOpaque(newVal);
3976                tabScroller.viewport.setOpaque(newVal);
3977            } else if (name == "background" && isScrollLayout) {
3978                Color newVal = (Color)e.getNewValue();
3979                tabScroller.tabPanel.setBackground(newVal);
3980                tabScroller.viewport.setBackground(newVal);
3981                Color newColor = selectedColor == null ? newVal : selectedColor;
3982                tabScroller.scrollForwardButton.setBackground(newColor);
3983                tabScroller.scrollBackwardButton.setBackground(newColor);
3984            } else if (name == "indexForTabComponent") {
3985                if (tabContainer != null) {
3986                    tabContainer.removeUnusedTabComponents();
3987                }
3988                Component c = tabPane.getTabComponentAt(
3989                        (Integer)e.getNewValue());
3990                if (c != null) {
3991                    if (tabContainer == null) {
3992                        installTabContainer();
3993                    } else {
3994                        tabContainer.add(c);
3995                    }
3996                }
3997                tabPane.revalidate();
3998                tabPane.repaint();
3999                calculatedBaseline = false;
4000            } else if (name == "indexForNullComponent") {
4001                isRunsDirty = true;
4002                updateHtmlViews((Integer)e.getNewValue(), true);
4003            } else if (name == "font") {
4004                calculatedBaseline = false;
4005            }
4006        }
4007
4008        private void updateHtmlViews(int index, boolean inserted) {
4009            String title = tabPane.getTitleAt(index);
4010            boolean isHTML = BasicHTML.isHTMLString(title);
4011            if (isHTML) {
4012                if (htmlViews==null) {    // Initialize vector
4013                    htmlViews = createHTMLVector();
4014                } else {                  // Vector already exists
4015                    View v = BasicHTML.createHTMLView(tabPane, title);
4016                    setHtmlView(v, inserted, index);
4017                }
4018            } else {                             // Not HTML
4019                if (htmlViews!=null) {           // Add placeholder
4020                    setHtmlView(null, inserted, index);
4021                }                                // else nada!
4022            }
4023            updateMnemonics();
4024        }
4025
4026        private void setHtmlView(View v, boolean inserted, int index) {
4027            if (inserted || index >= htmlViews.size()) {
4028                htmlViews.insertElementAt(v, index);
4029            } else {
4030                htmlViews.setElementAt(v, index);
4031            }
4032        }
4033
4034        //
4035        // ChangeListener
4036        //
4037        public void stateChanged(ChangeEvent e) {
4038            JTabbedPane tabPane = (JTabbedPane)e.getSource();
4039            tabPane.revalidate();
4040            tabPane.repaint();
4041
4042            setFocusIndex(tabPane.getSelectedIndex(), false);
4043
4044            if (scrollableTabLayoutEnabled()) {
4045                ensureCurrentLayout();
4046                int index = tabPane.getSelectedIndex();
4047                if (index < rects.length && index != -1) {
4048                    tabScroller.tabPanel.scrollRectToVisible(
4049                            (Rectangle)rects[index].clone());
4050                }
4051            }
4052        }
4053
4054        //
4055        // MouseListener
4056        //
4057        public void mouseClicked(MouseEvent e) {
4058        }
4059
4060        public void mouseReleased(MouseEvent e) {
4061        }
4062
4063        public void mouseEntered(MouseEvent e) {
4064            setRolloverTab(e.getX(), e.getY());
4065        }
4066
4067        public void mouseExited(MouseEvent e) {
4068            setRolloverTab(-1);
4069        }
4070
4071        public void mousePressed(MouseEvent e) {
4072            if (!tabPane.isEnabled()) {
4073                return;
4074            }
4075            int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
4076            if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
4077                if (tabIndex != tabPane.getSelectedIndex()) {
4078                    // Clicking on unselected tab, change selection, do NOT
4079                    // request focus.
4080                    // This will trigger the focusIndex to change by way
4081                    // of stateChanged.
4082                    tabPane.setSelectedIndex(tabIndex);
4083                }
4084                else if (tabPane.isRequestFocusEnabled()) {
4085                    // Clicking on selected tab, try and give the tabbedpane
4086                    // focus.  Repaint will occur in focusGained.
4087                    tabPane.requestFocus();
4088                }
4089            }
4090        }
4091
4092        //
4093        // MouseMotionListener
4094        //
4095        public void mouseDragged(MouseEvent e) {
4096        }
4097
4098        public void mouseMoved(MouseEvent e) {
4099            setRolloverTab(e.getX(), e.getY());
4100        }
4101
4102        //
4103        // FocusListener
4104        //
4105        public void focusGained(FocusEvent e) {
4106           setFocusIndex(tabPane.getSelectedIndex(), true);
4107        }
4108        public void focusLost(FocusEvent e) {
4109           repaintTab(focusIndex);
4110        }
4111
4112
4113        //
4114        // ContainerListener
4115        //
4116    /* GES 2/3/99:
4117       The container listener code was added to support HTML
4118       rendering of tab titles.
4119
4120       Ideally, we would be able to listen for property changes
4121       when a tab is added or its text modified.  At the moment
4122       there are no such events because the Beans spec doesn't
4123       allow 'indexed' property changes (i.e. tab 2's text changed
4124       from A to B).
4125
4126       In order to get around this, we listen for tabs to be added
4127       or removed by listening for the container events.  we then
4128       queue up a runnable (so the component has a chance to complete
4129       the add) which checks the tab title of the new component to see
4130       if it requires HTML rendering.
4131
4132       The Views (one per tab title requiring HTML rendering) are
4133       stored in the htmlViews Vector, which is only allocated after
4134       the first time we run into an HTML tab.  Note that this vector
4135       is kept in step with the number of pages, and nulls are added
4136       for those pages whose tab title do not require HTML rendering.
4137
4138       This makes it easy for the paint and layout code to tell
4139       whether to invoke the HTML engine without having to check
4140       the string during time-sensitive operations.
4141
4142       When we have added a way to listen for tab additions and
4143       changes to tab text, this code should be removed and
4144       replaced by something which uses that.  */
4145
4146        public void componentAdded(ContainerEvent e) {
4147            JTabbedPane tp = (JTabbedPane)e.getContainer();
4148            Component child = e.getChild();
4149            if (child instanceof UIResource) {
4150                return;
4151            }
4152            isRunsDirty = true;
4153            updateHtmlViews(tp.indexOfComponent(child), true);
4154        }
4155        public void componentRemoved(ContainerEvent e) {
4156            JTabbedPane tp = (JTabbedPane)e.getContainer();
4157            Component child = e.getChild();
4158            if (child instanceof UIResource) {
4159                return;
4160            }
4161
4162            // NOTE 4/15/2002 (joutwate):
4163            // This fix is implemented using client properties since there is
4164            // currently no IndexPropertyChangeEvent.  Once
4165            // IndexPropertyChangeEvents have been added this code should be
4166            // modified to use it.
4167            Integer indexObj =
4168                (Integer)tp.getClientProperty("__index_to_remove__");
4169            if (indexObj != null) {
4170                int index = indexObj.intValue();
4171                if (htmlViews != null && htmlViews.size() > index) {
4172                    htmlViews.removeElementAt(index);
4173                }
4174                tp.putClientProperty("__index_to_remove__", null);
4175            }
4176            isRunsDirty = true;
4177            updateMnemonics();
4178
4179            validateFocusIndex();
4180        }
4181    }
4182
4183    /**
4184     * This class should be treated as a &quot;protected&quot; inner class.
4185     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4186     */
4187    public class PropertyChangeHandler implements PropertyChangeListener {
4188        // NOTE: This class exists only for backward compatibility. All
4189        // its functionality has been moved into Handler. If you need to add
4190        // new functionality add it to the Handler, but make sure this
4191        // class calls into the Handler.
4192        public void propertyChange(PropertyChangeEvent e) {
4193            getHandler().propertyChange(e);
4194        }
4195    }
4196
4197    /**
4198     * This class should be treated as a &quot;protected&quot; inner class.
4199     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4200     */
4201    public class TabSelectionHandler implements ChangeListener {
4202        // NOTE: This class exists only for backward compatibility. All
4203        // its functionality has been moved into Handler. If you need to add
4204        // new functionality add it to the Handler, but make sure this
4205        // class calls into the Handler.
4206        public void stateChanged(ChangeEvent e) {
4207            getHandler().stateChanged(e);
4208        }
4209    }
4210
4211    /**
4212     * This class should be treated as a &quot;protected&quot; inner class.
4213     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4214     */
4215    public class MouseHandler extends MouseAdapter {
4216        // NOTE: This class exists only for backward compatibility. All
4217        // its functionality has been moved into Handler. If you need to add
4218        // new functionality add it to the Handler, but make sure this
4219        // class calls into the Handler.
4220        public void mousePressed(MouseEvent e) {
4221            getHandler().mousePressed(e);
4222        }
4223    }
4224
4225    /**
4226     * This class should be treated as a &quot;protected&quot; inner class.
4227     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4228     */
4229    public class FocusHandler extends FocusAdapter {
4230        // NOTE: This class exists only for backward compatibility. All
4231        // its functionality has been moved into Handler. If you need to add
4232        // new functionality add it to the Handler, but make sure this
4233        // class calls into the Handler.
4234        public void focusGained(FocusEvent e) {
4235            getHandler().focusGained(e);
4236        }
4237        public void focusLost(FocusEvent e) {
4238            getHandler().focusLost(e);
4239        }
4240    }
4241
4242    private Vector<View> createHTMLVector() {
4243        Vector<View> htmlViews = new Vector<View>();
4244        int count = tabPane.getTabCount();
4245        if (count>0) {
4246            for (int i=0 ; i<count; i++) {
4247                String title = tabPane.getTitleAt(i);
4248                if (BasicHTML.isHTMLString(title)) {
4249                    htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
4250                } else {
4251                    htmlViews.addElement(null);
4252                }
4253            }
4254        }
4255        return htmlViews;
4256    }
4257
4258    @SuppressWarnings("serial") // Superclass is not serializable across versions
4259    private class TabContainer extends JPanel implements UIResource {
4260        private boolean notifyTabbedPane = true;
4261
4262        public TabContainer() {
4263            super(null);
4264            setOpaque(false);
4265        }
4266
4267        public void remove(Component comp) {
4268            int index = tabPane.indexOfTabComponent(comp);
4269            super.remove(comp);
4270            if (notifyTabbedPane && index != -1) {
4271                tabPane.setTabComponentAt(index, null);
4272            }
4273        }
4274
4275        private void removeUnusedTabComponents() {
4276            for (Component c : getComponents()) {
4277                if (!(c instanceof UIResource)) {
4278                    int index = tabPane.indexOfTabComponent(c);
4279                    if (index == -1) {
4280                        super.remove(c);
4281                    }
4282                }
4283            }
4284        }
4285
4286        public boolean isOptimizedDrawingEnabled() {
4287            return tabScroller != null && !tabScroller.croppedEdge.isParamsSet();
4288        }
4289
4290        public void doLayout() {
4291            // We layout tabComponents in JTabbedPane's layout manager
4292            // and use this method as a hook for repainting tabs
4293            // to update tabs area e.g. when the size of tabComponent was changed
4294            if (scrollableTabLayoutEnabled()) {
4295                tabScroller.tabPanel.repaint();
4296                tabScroller.updateView();
4297            } else {
4298                tabPane.repaint(getBounds());
4299            }
4300        }
4301    }
4302
4303    @SuppressWarnings("serial") // Superclass is not serializable across versions
4304    private class CroppedEdge extends JPanel implements UIResource {
4305        private Shape shape;
4306        private int tabIndex;
4307        private int cropline;
4308        private int cropx, cropy;
4309
4310        public CroppedEdge() {
4311            setOpaque(false);
4312        }
4313
4314        public void setParams(int tabIndex, int cropline, int cropx, int cropy) {
4315            this.tabIndex = tabIndex;
4316            this.cropline = cropline;
4317            this.cropx = cropx;
4318            this.cropy = cropy;
4319            Rectangle tabRect = rects[tabIndex];
4320            setBounds(tabRect);
4321            shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline);
4322            if (getParent() == null && tabContainer != null) {
4323                tabContainer.add(this, 0);
4324            }
4325        }
4326
4327        public void resetParams() {
4328            shape = null;
4329            if (getParent() == tabContainer && tabContainer != null) {
4330                tabContainer.remove(this);
4331            }
4332        }
4333
4334        public boolean isParamsSet() {
4335            return shape != null;
4336        }
4337
4338        public int getTabIndex() {
4339            return tabIndex;
4340        }
4341
4342        public int getCropline() {
4343            return cropline;
4344        }
4345
4346        public int getCroppedSideWidth() {
4347            return 3;
4348        }
4349
4350        private Color getBgColor() {
4351            Component parent = tabPane.getParent();
4352            if (parent != null) {
4353                Color bg = parent.getBackground();
4354                if (bg != null) {
4355                    return bg;
4356                }
4357            }
4358            return UIManager.getColor("control");
4359        }
4360
4361        protected void paintComponent(Graphics g) {
4362            super.paintComponent(g);
4363            if (isParamsSet() && g instanceof Graphics2D) {
4364                Graphics2D g2 = (Graphics2D) g;
4365                g2.clipRect(0, 0, getWidth(), getHeight());
4366                g2.setColor(getBgColor());
4367                g2.translate(cropx, cropy);
4368                g2.fill(shape);
4369                paintCroppedTabEdge(g);
4370                g2.translate(-cropx, -cropy);
4371            }
4372        }
4373    }
4374}
4375