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            // Clip the icon within iconRect bounds
1142            Shape oldClip = g.getClip();
1143            ((Graphics2D)g).clip(iconRect);
1144            icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
1145            g.setClip(oldClip);
1146        }
1147    }
1148
1149    /**
1150     * Paints text.
1151     * @param g the graphics
1152     * @param tabPlacement the tab placement
1153     * @param font the font
1154     * @param metrics the font metrics
1155     * @param tabIndex the tab index
1156     * @param title the title
1157     * @param textRect the text rectangle
1158     * @param isSelected selection status
1159     */
1160    protected void paintText(Graphics g, int tabPlacement,
1161                             Font font, FontMetrics metrics, int tabIndex,
1162                             String title, Rectangle textRect,
1163                             boolean isSelected) {
1164
1165        g.setFont(font);
1166
1167        View v = getTextViewForTab(tabIndex);
1168        if (v != null) {
1169            // html
1170            v.paint(g, textRect);
1171        } else {
1172            // plain text
1173            int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1174
1175            if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
1176                Color fg = tabPane.getForegroundAt(tabIndex);
1177                if (isSelected && (fg instanceof UIResource)) {
1178                    Color selectedFG = UIManager.getColor(
1179                                  "TabbedPane.selectedForeground");
1180                    if (selectedFG != null) {
1181                        fg = selectedFG;
1182                    }
1183                }
1184                g.setColor(fg);
1185                SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1186                             title, mnemIndex,
1187                             textRect.x, textRect.y + metrics.getAscent());
1188
1189            } else { // tab disabled
1190                g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
1191                SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1192                             title, mnemIndex,
1193                             textRect.x, textRect.y + metrics.getAscent());
1194                g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
1195                SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1196                             title, mnemIndex,
1197                             textRect.x - 1, textRect.y + metrics.getAscent() - 1);
1198
1199            }
1200        }
1201    }
1202
1203    /**
1204     * Returns the tab label shift x.
1205     * @param tabPlacement the tab placement
1206     * @param tabIndex the tab index
1207     * @param isSelected selection status
1208     * @return the tab label shift x
1209     */
1210    protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
1211        Rectangle tabRect = rects[tabIndex];
1212        String propKey = (isSelected ? "selectedLabelShift" : "labelShift");
1213        int nudge = DefaultLookup.getInt(
1214                tabPane, this, "TabbedPane." + propKey, 1);
1215
1216        switch (tabPlacement) {
1217            case LEFT:
1218                return nudge;
1219            case RIGHT:
1220                return -nudge;
1221            case BOTTOM:
1222            case TOP:
1223            default:
1224                return tabRect.width % 2;
1225        }
1226    }
1227
1228    /**
1229     * Returns the tab label shift y.
1230     * @param tabPlacement the tab placement
1231     * @param tabIndex the tab index
1232     * @param isSelected selection status
1233     * @return the tab label shift y
1234     */
1235    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
1236        Rectangle tabRect = rects[tabIndex];
1237        int nudge = (isSelected ? DefaultLookup.getInt(tabPane, this, "TabbedPane.selectedLabelShift", -1) :
1238                DefaultLookup.getInt(tabPane, this, "TabbedPane.labelShift", 1));
1239
1240        switch (tabPlacement) {
1241            case BOTTOM:
1242                return -nudge;
1243            case LEFT:
1244            case RIGHT:
1245                return tabRect.height % 2;
1246            case TOP:
1247            default:
1248                return nudge;
1249        }
1250    }
1251
1252    /**
1253     * Paints the focus indicator.
1254     * @param g the graphics
1255     * @param tabPlacement the tab placement
1256     * @param rects rectangles
1257     * @param tabIndex the tab index
1258     * @param iconRect the icon rectangle
1259     * @param textRect the text rectangle
1260     * @param isSelected selection status
1261     */
1262    protected void paintFocusIndicator(Graphics g, int tabPlacement,
1263                                       Rectangle[] rects, int tabIndex,
1264                                       Rectangle iconRect, Rectangle textRect,
1265                                       boolean isSelected) {
1266        Rectangle tabRect = rects[tabIndex];
1267        if (tabPane.hasFocus() && isSelected) {
1268            int x, y, w, h;
1269            g.setColor(focus);
1270            switch(tabPlacement) {
1271              case LEFT:
1272                  x = tabRect.x + 3;
1273                  y = tabRect.y + 3;
1274                  w = tabRect.width - 5;
1275                  h = tabRect.height - 6;
1276                  break;
1277              case RIGHT:
1278                  x = tabRect.x + 2;
1279                  y = tabRect.y + 3;
1280                  w = tabRect.width - 5;
1281                  h = tabRect.height - 6;
1282                  break;
1283              case BOTTOM:
1284                  x = tabRect.x + 3;
1285                  y = tabRect.y + 2;
1286                  w = tabRect.width - 6;
1287                  h = tabRect.height - 5;
1288                  break;
1289              case TOP:
1290              default:
1291                  x = tabRect.x + 3;
1292                  y = tabRect.y + 3;
1293                  w = tabRect.width - 6;
1294                  h = tabRect.height - 5;
1295            }
1296            BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
1297        }
1298    }
1299
1300    /**
1301      * this function draws the border around each tab
1302      * note that this function does now draw the background of the tab.
1303      * that is done elsewhere
1304      *
1305      * @param g             the graphics context in which to paint
1306      * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1307      * @param tabIndex      the index of the tab with respect to other tabs
1308      * @param x             the x coordinate of tab
1309      * @param y             the y coordinate of tab
1310      * @param w             the width of the tab
1311      * @param h             the height of the tab
1312      * @param isSelected    a {@code boolean} which determines whether or not
1313      * the tab is selected
1314      */
1315    protected void paintTabBorder(Graphics g, int tabPlacement,
1316                                  int tabIndex,
1317                                  int x, int y, int w, int h,
1318                                  boolean isSelected ) {
1319        g.setColor(lightHighlight);
1320
1321        switch (tabPlacement) {
1322          case LEFT:
1323              g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1324              g.drawLine(x, y+2, x, y+h-3); // left highlight
1325              g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1326              g.drawLine(x+2, y, x+w-1, y); // top highlight
1327
1328              g.setColor(shadow);
1329              g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow
1330
1331              g.setColor(darkShadow);
1332              g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow
1333              break;
1334          case RIGHT:
1335              g.drawLine(x, y, x+w-3, y); // top highlight
1336
1337              g.setColor(shadow);
1338              g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow
1339              g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow
1340
1341              g.setColor(darkShadow);
1342              g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow
1343              g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1344              g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow
1345              g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1346              break;
1347          case BOTTOM:
1348              g.drawLine(x, y, x, y+h-3); // left highlight
1349              g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1350
1351              g.setColor(shadow);
1352              g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow
1353              g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow
1354
1355              g.setColor(darkShadow);
1356              g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1357              g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1358              g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
1359              break;
1360          case TOP:
1361          default:
1362              g.drawLine(x, y+2, x, y+h-1); // left highlight
1363              g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1364              g.drawLine(x+2, y, x+w-3, y); // top highlight
1365
1366              g.setColor(shadow);
1367              g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow
1368
1369              g.setColor(darkShadow);
1370              g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow
1371              g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow
1372        }
1373    }
1374
1375    /**
1376     * Paints the tab background.
1377     * @param g             the graphics context in which to paint
1378     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1379     * @param tabIndex      the index of the tab with respect to other tabs
1380     * @param x             the x coordinate of tab
1381     * @param y             the y coordinate of tab
1382     * @param w             the width of the tab
1383     * @param h             the height of the tab
1384     * @param isSelected    a {@code boolean} which determines whether or not
1385     * the tab is selected
1386     */
1387    protected void paintTabBackground(Graphics g, int tabPlacement,
1388                                      int tabIndex,
1389                                      int x, int y, int w, int h,
1390                                      boolean isSelected ) {
1391        g.setColor(!isSelected || selectedColor == null?
1392                   tabPane.getBackgroundAt(tabIndex) : selectedColor);
1393        switch(tabPlacement) {
1394          case LEFT:
1395              g.fillRect(x+1, y+1, w-1, h-3);
1396              break;
1397          case RIGHT:
1398              g.fillRect(x, y+1, w-2, h-3);
1399              break;
1400          case BOTTOM:
1401              g.fillRect(x+1, y, w-3, h-1);
1402              break;
1403          case TOP:
1404          default:
1405              g.fillRect(x+1, y+1, w-3, h-1);
1406        }
1407    }
1408
1409    /**
1410     * Paints the content border.
1411     * @param g             the graphics context in which to paint
1412     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1413     * @param selectedIndex the tab index of the selected component
1414     */
1415    protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
1416        int width = tabPane.getWidth();
1417        int height = tabPane.getHeight();
1418        Insets insets = tabPane.getInsets();
1419        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1420
1421        int x = insets.left;
1422        int y = insets.top;
1423        int w = width - insets.right - insets.left;
1424        int h = height - insets.top - insets.bottom;
1425
1426        switch(tabPlacement) {
1427          case LEFT:
1428              x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1429              if (tabsOverlapBorder) {
1430                  x -= tabAreaInsets.right;
1431              }
1432              w -= (x - insets.left);
1433              break;
1434          case RIGHT:
1435              w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1436              if (tabsOverlapBorder) {
1437                  w += tabAreaInsets.left;
1438              }
1439              break;
1440          case BOTTOM:
1441              h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1442              if (tabsOverlapBorder) {
1443                  h += tabAreaInsets.top;
1444              }
1445              break;
1446          case TOP:
1447          default:
1448              y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1449              if (tabsOverlapBorder) {
1450                  y -= tabAreaInsets.bottom;
1451              }
1452              h -= (y - insets.top);
1453        }
1454
1455            if ( tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque()) ) {
1456            // Fill region behind content area
1457            Color color = UIManager.getColor("TabbedPane.contentAreaColor");
1458            if (color != null) {
1459                g.setColor(color);
1460            }
1461            else if ( selectedColor == null || selectedIndex == -1 ) {
1462                g.setColor(tabPane.getBackground());
1463            }
1464            else {
1465                g.setColor(selectedColor);
1466            }
1467            g.fillRect(x,y,w,h);
1468        }
1469
1470        paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1471        paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1472        paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1473        paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1474
1475    }
1476
1477    /**
1478     * Paints the content border top edge.
1479     * @param g             the graphics context in which to paint
1480     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1481     * @param selectedIndex the tab index of the selected component
1482     * @param x             the x coordinate of tab
1483     * @param y             the y coordinate of tab
1484     * @param w             the width of the tab
1485     * @param h             the height of the tab
1486     */
1487    protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
1488                                         int selectedIndex,
1489                                         int x, int y, int w, int h) {
1490        Rectangle selRect = selectedIndex < 0? null :
1491                               getTabBounds(selectedIndex, calcRect);
1492
1493        g.setColor(lightHighlight);
1494
1495        // Draw unbroken line if tabs are not on TOP, OR
1496        // selected tab is not in run adjacent to content, OR
1497        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1498        //
1499        if (tabPlacement != TOP || selectedIndex < 0 ||
1500            (selRect.y + selRect.height + 1 < y) ||
1501            (selRect.x < x || selRect.x > x + w)) {
1502            g.drawLine(x, y, x+w-2, y);
1503        } else {
1504            // Break line to show visual connection to selected tab
1505            g.drawLine(x, y, selRect.x - 1, y);
1506            if (selRect.x + selRect.width < x + w - 2) {
1507                g.drawLine(selRect.x + selRect.width, y,
1508                           x+w-2, y);
1509            } else {
1510                g.setColor(shadow);
1511                g.drawLine(x+w-2, y, x+w-2, y);
1512            }
1513        }
1514    }
1515
1516    /**
1517     * Paints the content border left edge.
1518     * @param g             the graphics context in which to paint
1519     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1520     * @param selectedIndex the tab index of the selected component
1521     * @param x             the x coordinate of tab
1522     * @param y             the y coordinate of tab
1523     * @param w             the width of the tab
1524     * @param h             the height of the tab
1525     */
1526    protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
1527                                               int selectedIndex,
1528                                               int x, int y, int w, int h) {
1529        Rectangle selRect = selectedIndex < 0? null :
1530                               getTabBounds(selectedIndex, calcRect);
1531
1532        g.setColor(lightHighlight);
1533
1534        // Draw unbroken line if tabs are not on LEFT, OR
1535        // selected tab is not in run adjacent to content, OR
1536        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1537        //
1538        if (tabPlacement != LEFT || selectedIndex < 0 ||
1539            (selRect.x + selRect.width + 1 < x) ||
1540            (selRect.y < y || selRect.y > y + h)) {
1541            g.drawLine(x, y, x, y+h-2);
1542        } else {
1543            // Break line to show visual connection to selected tab
1544            g.drawLine(x, y, x, selRect.y - 1);
1545            if (selRect.y + selRect.height < y + h - 2) {
1546                g.drawLine(x, selRect.y + selRect.height,
1547                           x, y+h-2);
1548            }
1549        }
1550    }
1551
1552    /**
1553     * Paints the content border bottom edge.
1554     * @param g             the graphics context in which to paint
1555     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1556     * @param selectedIndex the tab index of the selected component
1557     * @param x             the x coordinate of tab
1558     * @param y             the y coordinate of tab
1559     * @param w             the width of the tab
1560     * @param h             the height of the tab
1561     */
1562    protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
1563                                               int selectedIndex,
1564                                               int x, int y, int w, int h) {
1565        Rectangle selRect = selectedIndex < 0? null :
1566                               getTabBounds(selectedIndex, calcRect);
1567
1568        g.setColor(shadow);
1569
1570        // Draw unbroken line if tabs are not on BOTTOM, OR
1571        // selected tab is not in run adjacent to content, OR
1572        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1573        //
1574        if (tabPlacement != BOTTOM || selectedIndex < 0 ||
1575             (selRect.y - 1 > h) ||
1576             (selRect.x < x || selRect.x > x + w)) {
1577            g.drawLine(x+1, y+h-2, x+w-2, y+h-2);
1578            g.setColor(darkShadow);
1579            g.drawLine(x, y+h-1, x+w-1, y+h-1);
1580        } else {
1581            // Break line to show visual connection to selected tab
1582            g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2);
1583            g.setColor(darkShadow);
1584            g.drawLine(x, y+h-1, selRect.x - 1, y+h-1);
1585            if (selRect.x + selRect.width < x + w - 2) {
1586                g.setColor(shadow);
1587                g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
1588                g.setColor(darkShadow);
1589                g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1);
1590            }
1591        }
1592
1593    }
1594
1595    /**
1596     * Paints the content border right edge.
1597     * @param g             the graphics context in which to paint
1598     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1599     * @param selectedIndex the tab index of the selected component
1600     * @param x             the x coordinate of tab
1601     * @param y             the y coordinate of tab
1602     * @param w             the width of the tab
1603     * @param h             the height of the tab
1604     */
1605    protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
1606                                               int selectedIndex,
1607                                               int x, int y, int w, int h) {
1608        Rectangle selRect = selectedIndex < 0? null :
1609                               getTabBounds(selectedIndex, calcRect);
1610
1611        g.setColor(shadow);
1612
1613        // Draw unbroken line if tabs are not on RIGHT, OR
1614        // selected tab is not in run adjacent to content, OR
1615        // selected tab is not visible (SCROLL_TAB_LAYOUT)
1616        //
1617        if (tabPlacement != RIGHT || selectedIndex < 0 ||
1618             (selRect.x - 1 > w) ||
1619             (selRect.y < y || selRect.y > y + h)) {
1620            g.drawLine(x+w-2, y+1, x+w-2, y+h-3);
1621            g.setColor(darkShadow);
1622            g.drawLine(x+w-1, y, x+w-1, y+h-1);
1623        } else {
1624            // Break line to show visual connection to selected tab
1625            g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1);
1626            g.setColor(darkShadow);
1627            g.drawLine(x+w-1, y, x+w-1, selRect.y - 1);
1628
1629            if (selRect.y + selRect.height < y + h - 2) {
1630                g.setColor(shadow);
1631                g.drawLine(x+w-2, selRect.y + selRect.height,
1632                           x+w-2, y+h-2);
1633                g.setColor(darkShadow);
1634                g.drawLine(x+w-1, selRect.y + selRect.height,
1635                           x+w-1, y+h-2);
1636            }
1637        }
1638    }
1639
1640    private void ensureCurrentLayout() {
1641        if (!tabPane.isValid()) {
1642            tabPane.validate();
1643        }
1644        /* If tabPane doesn't have a peer yet, the validate() call will
1645         * silently fail.  We handle that by forcing a layout if tabPane
1646         * is still invalid.  See bug 4237677.
1647         */
1648        if (!tabPane.isValid()) {
1649            TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
1650            layout.calculateLayoutInfo();
1651        }
1652    }
1653
1654
1655// TabbedPaneUI methods
1656
1657    /**
1658     * Returns the bounds of the specified tab index.  The bounds are
1659     * with respect to the JTabbedPane's coordinate space.
1660     */
1661    public Rectangle getTabBounds(JTabbedPane pane, int i) {
1662        ensureCurrentLayout();
1663        Rectangle tabRect = new Rectangle();
1664        return getTabBounds(i, tabRect);
1665    }
1666
1667    public int getTabRunCount(JTabbedPane pane) {
1668        ensureCurrentLayout();
1669        return runCount;
1670    }
1671
1672    /**
1673     * Returns the tab index which intersects the specified point
1674     * in the JTabbedPane's coordinate space.
1675     */
1676    public int tabForCoordinate(JTabbedPane pane, int x, int y) {
1677        return tabForCoordinate(pane, x, y, true);
1678    }
1679
1680    private int tabForCoordinate(JTabbedPane pane, int x, int y,
1681                                 boolean validateIfNecessary) {
1682        if (validateIfNecessary) {
1683            ensureCurrentLayout();
1684        }
1685        if (isRunsDirty) {
1686            // We didn't recalculate the layout, runs and tabCount may not
1687            // line up, bail.
1688            return -1;
1689        }
1690        Point p = new Point(x, y);
1691
1692        if (scrollableTabLayoutEnabled()) {
1693            translatePointToTabPanel(x, y, p);
1694            Rectangle viewRect = tabScroller.viewport.getViewRect();
1695            if (!viewRect.contains(p)) {
1696                return -1;
1697            }
1698        }
1699        int tabCount = tabPane.getTabCount();
1700        for (int i = 0; i < tabCount; i++) {
1701            if (rects[i].contains(p.x, p.y)) {
1702                return i;
1703            }
1704        }
1705        return -1;
1706    }
1707
1708    /**
1709     * Returns the bounds of the specified tab in the coordinate space
1710     * of the JTabbedPane component.  This is required because the tab rects
1711     * are by default defined in the coordinate space of the component where
1712     * they are rendered, which could be the JTabbedPane
1713     * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
1714     * This method should be used whenever the tab rectangle must be relative
1715     * to the JTabbedPane itself and the result should be placed in a
1716     * designated Rectangle object (rather than instantiating and returning
1717     * a new Rectangle each time). The tab index parameter must be a valid
1718     * tabbed pane tab index (0 to tab count - 1, inclusive).  The destination
1719     * rectangle parameter must be a valid <code>Rectangle</code> instance.
1720     * The handling of invalid parameters is unspecified.
1721     *
1722     * @param tabIndex the index of the tab
1723     * @param dest the rectangle where the result should be placed
1724     * @return the resulting rectangle
1725     *
1726     * @since 1.4
1727     */
1728    protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
1729        dest.width = rects[tabIndex].width;
1730        dest.height = rects[tabIndex].height;
1731
1732        if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
1733            // Need to translate coordinates based on viewport location &
1734            // view position
1735            Point vpp = tabScroller.viewport.getLocation();
1736            Point viewp = tabScroller.viewport.getViewPosition();
1737            dest.x = rects[tabIndex].x + vpp.x - viewp.x;
1738            dest.y = rects[tabIndex].y + vpp.y - viewp.y;
1739
1740        } else { // WRAP_TAB_LAYOUT
1741            dest.x = rects[tabIndex].x;
1742            dest.y = rects[tabIndex].y;
1743        }
1744        return dest;
1745    }
1746
1747    /**
1748     * Returns the index of the tab closest to the passed in location, note
1749     * that the returned tab may not contain the location x,y.
1750     */
1751    private int getClosestTab(int x, int y) {
1752        int min = 0;
1753        int tabCount = Math.min(rects.length, tabPane.getTabCount());
1754        int max = tabCount;
1755        int tabPlacement = tabPane.getTabPlacement();
1756        boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
1757        int want = (useX) ? x : y;
1758
1759        while (min != max) {
1760            int current = (max + min) / 2;
1761            int minLoc;
1762            int maxLoc;
1763
1764            if (useX) {
1765                minLoc = rects[current].x;
1766                maxLoc = minLoc + rects[current].width;
1767            }
1768            else {
1769                minLoc = rects[current].y;
1770                maxLoc = minLoc + rects[current].height;
1771            }
1772            if (want < minLoc) {
1773                max = current;
1774                if (min == max) {
1775                    return Math.max(0, current - 1);
1776                }
1777            }
1778            else if (want >= maxLoc) {
1779                min = current;
1780                if (max - min <= 1) {
1781                    return Math.max(current + 1, tabCount - 1);
1782                }
1783            }
1784            else {
1785                return current;
1786            }
1787        }
1788        return min;
1789    }
1790
1791    /**
1792     * Returns a point which is translated from the specified point in the
1793     * JTabbedPane's coordinate space to the coordinate space of the
1794     * ScrollableTabPanel.  This is used for SCROLL_TAB_LAYOUT ONLY.
1795     */
1796    private Point translatePointToTabPanel(int srcx, int srcy, Point dest) {
1797        Point vpp = tabScroller.viewport.getLocation();
1798        Point viewp = tabScroller.viewport.getViewPosition();
1799        dest.x = srcx - vpp.x + viewp.x;
1800        dest.y = srcy - vpp.y + viewp.y;
1801        return dest;
1802    }
1803
1804// BasicTabbedPaneUI methods
1805
1806    /**
1807     * Returns the visible component.
1808     * @return the visible component
1809     */
1810    protected Component getVisibleComponent() {
1811        return visibleComponent;
1812    }
1813
1814    /**
1815     * Sets the visible component.
1816     * @param component the component
1817     */
1818    protected void setVisibleComponent(Component component) {
1819        if (visibleComponent != null
1820                && visibleComponent != component
1821                && visibleComponent.getParent() == tabPane
1822                && visibleComponent.isVisible()) {
1823
1824            visibleComponent.setVisible(false);
1825        }
1826        if (component != null && !component.isVisible()) {
1827            component.setVisible(true);
1828        }
1829        visibleComponent = component;
1830    }
1831
1832    /**
1833     * Assure the rectangles are created.
1834     * @param tabCount the tab count
1835     */
1836    protected void assureRectsCreated(int tabCount) {
1837        int rectArrayLen = rects.length;
1838        if (tabCount != rectArrayLen ) {
1839            Rectangle[] tempRectArray = new Rectangle[tabCount];
1840            System.arraycopy(rects, 0, tempRectArray, 0,
1841                             Math.min(rectArrayLen, tabCount));
1842            rects = tempRectArray;
1843            for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
1844                rects[rectIndex] = new Rectangle();
1845            }
1846        }
1847
1848    }
1849
1850    /**
1851     * Expands the tab runs array.
1852     */
1853    protected void expandTabRunsArray() {
1854        int rectLen = tabRuns.length;
1855        int[] newArray = new int[rectLen+10];
1856        System.arraycopy(tabRuns, 0, newArray, 0, runCount);
1857        tabRuns = newArray;
1858    }
1859
1860    /**
1861     * Returns the run for a tab.
1862     * @param tabCount the tab count
1863     * @param tabIndex the tab index.
1864     * @return the run for a tab
1865     */
1866    protected int getRunForTab(int tabCount, int tabIndex) {
1867        for (int i = 0; i < runCount; i++) {
1868            int first = tabRuns[i];
1869            int last = lastTabInRun(tabCount, i);
1870            if (tabIndex >= first && tabIndex <= last) {
1871                return i;
1872            }
1873        }
1874        return 0;
1875    }
1876
1877    /**
1878     * Returns the last tab in a run.
1879     * @param tabCount the tab count
1880     * @param run the run
1881     * @return the last tab in a run
1882     */
1883    protected int lastTabInRun(int tabCount, int run) {
1884        if (runCount == 1) {
1885            return tabCount - 1;
1886        }
1887        int nextRun = (run == runCount - 1? 0 : run + 1);
1888        if (tabRuns[nextRun] == 0) {
1889            return tabCount - 1;
1890        }
1891        return tabRuns[nextRun]-1;
1892    }
1893
1894    /**
1895     * Returns the tab run overlay.
1896     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1897     * @return the tab run overlay
1898     */
1899    protected int getTabRunOverlay(int tabPlacement) {
1900        return tabRunOverlay;
1901    }
1902
1903    /**
1904     * Returns the tab run indent.
1905     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1906     * @param run the tab run
1907     * @return the tab run indent
1908     */
1909    protected int getTabRunIndent(int tabPlacement, int run) {
1910        return 0;
1911    }
1912
1913    /**
1914     * Returns whether or not the tab run should be padded.
1915     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1916     * @param run the tab run
1917     * @return whether or not the tab run should be padded
1918     */
1919    protected boolean shouldPadTabRun(int tabPlacement, int run) {
1920        return runCount > 1;
1921    }
1922
1923    /**
1924     * Returns whether or not the tab run should be rotated.
1925     * @param tabPlacement the placement (left, right, bottom, top) of the tab
1926     * @return whether or not the tab run should be rotated
1927     */
1928    protected boolean shouldRotateTabRuns(int tabPlacement) {
1929        return true;
1930    }
1931
1932    /**
1933     * Returns the icon for a tab.
1934     * @param tabIndex the index of the tab
1935     * @return the icon for a tab
1936     */
1937    protected Icon getIconForTab(int tabIndex) {
1938        return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))?
1939                          tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex);
1940    }
1941
1942    /**
1943     * Returns the text View object required to render stylized text (HTML) for
1944     * the specified tab or null if no specialized text rendering is needed
1945     * for this tab. This is provided to support html rendering inside tabs.
1946     *
1947     * @param tabIndex the index of the tab
1948     * @return the text view to render the tab's text or null if no
1949     *         specialized rendering is required
1950     *
1951     * @since 1.4
1952     */
1953    protected View getTextViewForTab(int tabIndex) {
1954        if (htmlViews != null) {
1955            return htmlViews.elementAt(tabIndex);
1956        }
1957        return null;
1958    }
1959
1960    /**
1961     * Calculates the tab height.
1962     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1963     * @param tabIndex      the index of the tab with respect to other tabs
1964     * @param fontHeight    the font height
1965     * @return the tab height
1966     */
1967    protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
1968        int height = 0;
1969        Component c = tabPane.getTabComponentAt(tabIndex);
1970        if (c != null) {
1971            height = c.getPreferredSize().height;
1972        } else {
1973            View v = getTextViewForTab(tabIndex);
1974            if (v != null) {
1975                // html
1976                height += (int) v.getPreferredSpan(View.Y_AXIS);
1977            } else {
1978                // plain text
1979                height += fontHeight;
1980            }
1981            Icon icon = getIconForTab(tabIndex);
1982
1983            if (icon != null) {
1984                height = Math.max(height, icon.getIconHeight());
1985            }
1986        }
1987        Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1988        height += tabInsets.top + tabInsets.bottom + 2;
1989        return height;
1990    }
1991
1992    /**
1993     * Calculates the maximum tab height.
1994     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1995     * @return the maximum tab height
1996     */
1997    protected int calculateMaxTabHeight(int tabPlacement) {
1998        FontMetrics metrics = getFontMetrics();
1999        int tabCount = tabPane.getTabCount();
2000        int result = 0;
2001        int fontHeight = metrics.getHeight();
2002        for(int i = 0; i < tabCount; i++) {
2003            result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
2004        }
2005        return result;
2006    }
2007
2008    /**
2009     * Calculates the tab width.
2010     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2011     * @param tabIndex      the index of the tab with respect to other tabs
2012     * @param metrics       the font metrics
2013     * @return the tab width
2014     */
2015    protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
2016        Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
2017        int width = tabInsets.left + tabInsets.right + 3;
2018        Component tabComponent = tabPane.getTabComponentAt(tabIndex);
2019        if (tabComponent != null) {
2020            width += tabComponent.getPreferredSize().width;
2021        } else {
2022            Icon icon = getIconForTab(tabIndex);
2023            if (icon != null) {
2024                width += icon.getIconWidth() + textIconGap;
2025            }
2026            View v = getTextViewForTab(tabIndex);
2027            if (v != null) {
2028                // html
2029                width += (int) v.getPreferredSpan(View.X_AXIS);
2030            } else {
2031                // plain text
2032                String title = tabPane.getTitleAt(tabIndex);
2033                width += SwingUtilities2.stringWidth(tabPane, metrics, title);
2034            }
2035        }
2036        return width;
2037    }
2038
2039    /**
2040     * Calculates the maximum tab width.
2041     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2042     * @return the maximum tab width
2043     */
2044    protected int calculateMaxTabWidth(int tabPlacement) {
2045        FontMetrics metrics = getFontMetrics();
2046        int tabCount = tabPane.getTabCount();
2047        int result = 0;
2048        for(int i = 0; i < tabCount; i++) {
2049            result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
2050        }
2051        return result;
2052    }
2053
2054    /**
2055     * Calculates the tab area height.
2056     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2057     * @param horizRunCount horizontal run count
2058     * @param maxTabHeight maximum tab height
2059     * @return the tab area height
2060     */
2061    protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) {
2062        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2063        int tabRunOverlay = getTabRunOverlay(tabPlacement);
2064        return (horizRunCount > 0?
2065                horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay +
2066                tabAreaInsets.top + tabAreaInsets.bottom :
2067                0);
2068    }
2069
2070    /**
2071     * Calculates the tab area width.
2072     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2073     * @param vertRunCount vertical run count
2074     * @param maxTabWidth maximum tab width
2075     * @return the tab area width
2076     */
2077    protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) {
2078        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2079        int tabRunOverlay = getTabRunOverlay(tabPlacement);
2080        return (vertRunCount > 0?
2081                vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay +
2082                tabAreaInsets.left + tabAreaInsets.right :
2083                0);
2084    }
2085
2086    /**
2087     * Returns the tab insets.
2088     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2089     * @param tabIndex the tab index
2090     * @return the tab insets
2091     */
2092    protected Insets getTabInsets(int tabPlacement, int tabIndex) {
2093        return tabInsets;
2094    }
2095
2096    /**
2097     * Returns the selected tab pad insets.
2098     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2099     * @return the selected tab pad insets
2100     */
2101    protected Insets getSelectedTabPadInsets(int tabPlacement) {
2102        rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
2103        return currentPadInsets;
2104    }
2105
2106    /**
2107     * Returns the tab area insets.
2108     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2109     * @return the pad area insets
2110     */
2111    protected Insets getTabAreaInsets(int tabPlacement) {
2112        rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
2113        return currentTabAreaInsets;
2114    }
2115
2116    /**
2117     * Returns the content border insets.
2118     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2119     * @return the content border insets
2120     */
2121    protected Insets getContentBorderInsets(int tabPlacement) {
2122        return contentBorderInsets;
2123    }
2124
2125    /**
2126     * Returns the font metrics.
2127     * @return the font metrics
2128     */
2129    protected FontMetrics getFontMetrics() {
2130        Font font = tabPane.getFont();
2131        return tabPane.getFontMetrics(font);
2132    }
2133
2134
2135// Tab Navigation methods
2136
2137    /**
2138     * Navigate the selected tab.
2139     * @param direction the direction
2140     */
2141    protected void navigateSelectedTab(int direction) {
2142        int tabPlacement = tabPane.getTabPlacement();
2143        int current = DefaultLookup.getBoolean(tabPane, this,
2144                             "TabbedPane.selectionFollowsFocus", true) ?
2145                             tabPane.getSelectedIndex() : getFocusIndex();
2146        int tabCount = tabPane.getTabCount();
2147        boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
2148
2149        // If we have no tabs then don't navigate.
2150        if (tabCount <= 0) {
2151            return;
2152        }
2153
2154        int offset;
2155        switch(tabPlacement) {
2156          case LEFT:
2157          case RIGHT:
2158              switch(direction) {
2159                 case NEXT:
2160                     selectNextTab(current);
2161                     break;
2162                 case PREVIOUS:
2163                     selectPreviousTab(current);
2164                     break;
2165                case NORTH:
2166                    selectPreviousTabInRun(current);
2167                    break;
2168                case SOUTH:
2169                    selectNextTabInRun(current);
2170                    break;
2171                case WEST:
2172                    offset = getTabRunOffset(tabPlacement, tabCount, current, false);
2173                    selectAdjacentRunTab(tabPlacement, current, offset);
2174                    break;
2175                case EAST:
2176                    offset = getTabRunOffset(tabPlacement, tabCount, current, true);
2177                    selectAdjacentRunTab(tabPlacement, current, offset);
2178                    break;
2179                default:
2180              }
2181              break;
2182          case BOTTOM:
2183          case TOP:
2184          default:
2185              switch(direction) {
2186                case NEXT:
2187                    selectNextTab(current);
2188                    break;
2189                case PREVIOUS:
2190                    selectPreviousTab(current);
2191                    break;
2192                case NORTH:
2193                    offset = getTabRunOffset(tabPlacement, tabCount, current, false);
2194                    selectAdjacentRunTab(tabPlacement, current, offset);
2195                    break;
2196                case SOUTH:
2197                    offset = getTabRunOffset(tabPlacement, tabCount, current, true);
2198                    selectAdjacentRunTab(tabPlacement, current, offset);
2199                    break;
2200                case EAST:
2201                    if (leftToRight) {
2202                        selectNextTabInRun(current);
2203                    } else {
2204                        selectPreviousTabInRun(current);
2205                    }
2206                    break;
2207                case WEST:
2208                    if (leftToRight) {
2209                        selectPreviousTabInRun(current);
2210                    } else {
2211                        selectNextTabInRun(current);
2212                    }
2213                    break;
2214                default:
2215              }
2216        }
2217    }
2218
2219    /**
2220     * Select the next tab in the run.
2221     * @param current the current tab
2222     */
2223    protected void selectNextTabInRun(int current) {
2224        int tabCount = tabPane.getTabCount();
2225        int tabIndex = getNextTabIndexInRun(tabCount, current);
2226
2227        while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2228            tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
2229        }
2230        navigateTo(tabIndex);
2231    }
2232
2233    /**
2234     * Select the previous tab in the run.
2235     * @param current the current tab
2236     */
2237    protected void selectPreviousTabInRun(int current) {
2238        int tabCount = tabPane.getTabCount();
2239        int tabIndex = getPreviousTabIndexInRun(tabCount, current);
2240
2241        while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2242            tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
2243        }
2244        navigateTo(tabIndex);
2245    }
2246
2247    /**
2248     * Select the next tab.
2249     * @param current the current tab
2250     */
2251    protected void selectNextTab(int current) {
2252        int tabIndex = getNextTabIndex(current);
2253
2254        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2255            tabIndex = getNextTabIndex(tabIndex);
2256        }
2257        navigateTo(tabIndex);
2258    }
2259
2260    /**
2261     * Select the previous tab.
2262     * @param current the current tab
2263     */
2264    protected void selectPreviousTab(int current) {
2265        int tabIndex = getPreviousTabIndex(current);
2266
2267        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
2268            tabIndex = getPreviousTabIndex(tabIndex);
2269        }
2270        navigateTo(tabIndex);
2271    }
2272
2273    /**
2274     * Selects an adjacent run of tabs.
2275     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2276     * @param tabIndex      the index of the tab with respect to other tabs
2277     * @param offset        selection offset
2278     */
2279    protected void selectAdjacentRunTab(int tabPlacement,
2280                                        int tabIndex, int offset) {
2281        if ( runCount < 2 ) {
2282            return;
2283        }
2284        int newIndex;
2285        Rectangle r = rects[tabIndex];
2286        switch(tabPlacement) {
2287          case LEFT:
2288          case RIGHT:
2289              newIndex = tabForCoordinate(tabPane, r.x + r.width/2 + offset,
2290                                       r.y + r.height/2);
2291              break;
2292          case BOTTOM:
2293          case TOP:
2294          default:
2295              newIndex = tabForCoordinate(tabPane, r.x + r.width/2,
2296                                       r.y + r.height/2 + offset);
2297        }
2298        if (newIndex != -1) {
2299            while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
2300                newIndex = getNextTabIndex(newIndex);
2301            }
2302            navigateTo(newIndex);
2303        }
2304    }
2305
2306    private void navigateTo(int index) {
2307        if (DefaultLookup.getBoolean(tabPane, this,
2308                             "TabbedPane.selectionFollowsFocus", true)) {
2309            tabPane.setSelectedIndex(index);
2310        } else {
2311            // Just move focus (not selection)
2312            setFocusIndex(index, true);
2313        }
2314    }
2315
2316    void setFocusIndex(int index, boolean repaint) {
2317        if (repaint && !isRunsDirty) {
2318            repaintTab(focusIndex);
2319            focusIndex = index;
2320            repaintTab(focusIndex);
2321        }
2322        else {
2323            focusIndex = index;
2324        }
2325    }
2326
2327    /**
2328     * Repaints the specified tab.
2329     */
2330    private void repaintTab(int index) {
2331        // If we're not valid that means we will shortly be validated and
2332        // painted, which means we don't have to do anything here.
2333        if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) {
2334            tabPane.repaint(getTabBounds(tabPane, index));
2335        }
2336    }
2337
2338    /**
2339     * Makes sure the focusIndex is valid.
2340     */
2341    private void validateFocusIndex() {
2342        if (focusIndex >= tabPane.getTabCount()) {
2343            setFocusIndex(tabPane.getSelectedIndex(), false);
2344        }
2345    }
2346
2347    /**
2348     * Returns the index of the tab that has focus.
2349     *
2350     * @return index of tab that has focus
2351     * @since 1.5
2352     */
2353    protected int getFocusIndex() {
2354        return focusIndex;
2355    }
2356
2357    /**
2358     * Returns the tab run offset.
2359     * @param tabPlacement  the placement (left, right, bottom, top) of the tab
2360     * @param tabCount the tab count
2361     * @param tabIndex      the index of the tab with respect to other tabs
2362     * @param forward forward or not
2363     * @return the tab run offset
2364     */
2365    protected int getTabRunOffset(int tabPlacement, int tabCount,
2366                                  int tabIndex, boolean forward) {
2367        int run = getRunForTab(tabCount, tabIndex);
2368        int offset;
2369        switch(tabPlacement) {
2370          case LEFT: {
2371              if (run == 0) {
2372                  offset = (forward?
2373                            -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2374                            -maxTabWidth);
2375
2376              } else if (run == runCount - 1) {
2377                  offset = (forward?
2378                            maxTabWidth :
2379                            calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2380              } else {
2381                  offset = (forward? maxTabWidth : -maxTabWidth);
2382              }
2383              break;
2384          }
2385          case RIGHT: {
2386              if (run == 0) {
2387                  offset = (forward?
2388                            maxTabWidth :
2389                            calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2390              } else if (run == runCount - 1) {
2391                  offset = (forward?
2392                            -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2393                            -maxTabWidth);
2394              } else {
2395                  offset = (forward? maxTabWidth : -maxTabWidth);
2396              }
2397              break;
2398          }
2399          case BOTTOM: {
2400              if (run == 0) {
2401                  offset = (forward?
2402                            maxTabHeight :
2403                            calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2404              } else if (run == runCount - 1) {
2405                  offset = (forward?
2406                            -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2407                            -maxTabHeight);
2408              } else {
2409                  offset = (forward? maxTabHeight : -maxTabHeight);
2410              }
2411              break;
2412          }
2413          case TOP:
2414          default: {
2415              if (run == 0) {
2416                  offset = (forward?
2417                            -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2418                            -maxTabHeight);
2419              } else if (run == runCount - 1) {
2420                  offset = (forward?
2421                            maxTabHeight :
2422                            calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2423              } else {
2424                  offset = (forward? maxTabHeight : -maxTabHeight);
2425              }
2426          }
2427        }
2428        return offset;
2429    }
2430
2431    /**
2432     * Returns the previous tab index.
2433     * @param base the base
2434     * @return the previous tab index
2435     */
2436    protected int getPreviousTabIndex(int base) {
2437        int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1);
2438        return (tabIndex >= 0? tabIndex : 0);
2439    }
2440
2441    /**
2442     * Returns the next tab index.
2443     * @param base the base
2444     * @return the next tab index
2445     */
2446    protected int getNextTabIndex(int base) {
2447        return (base+1)%tabPane.getTabCount();
2448    }
2449
2450    /**
2451     * Returns the next tab index in the run.
2452     * @param tabCount the tab count
2453     * @param base the base
2454     * @return the next tab index in the run
2455     */
2456    protected int getNextTabIndexInRun(int tabCount, int base) {
2457        if (runCount < 2) {
2458            return getNextTabIndex(base);
2459        }
2460        int currentRun = getRunForTab(tabCount, base);
2461        int next = getNextTabIndex(base);
2462        if (next == tabRuns[getNextTabRun(currentRun)]) {
2463            return tabRuns[currentRun];
2464        }
2465        return next;
2466    }
2467
2468    /**
2469     * Returns the previous tab index in the run.
2470     * @param tabCount the tab count
2471     * @param base the base
2472     * @return the previous tab index in the run
2473     */
2474    protected int getPreviousTabIndexInRun(int tabCount, int base) {
2475        if (runCount < 2) {
2476            return getPreviousTabIndex(base);
2477        }
2478        int currentRun = getRunForTab(tabCount, base);
2479        if (base == tabRuns[currentRun]) {
2480            int previous = tabRuns[getNextTabRun(currentRun)]-1;
2481            return (previous != -1? previous : tabCount-1);
2482        }
2483        return getPreviousTabIndex(base);
2484    }
2485
2486    /**
2487     * Returns the previous tab run.
2488     * @param baseRun the base run
2489     * @return the previous tab run
2490     */
2491    protected int getPreviousTabRun(int baseRun) {
2492        int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1);
2493        return (runIndex >= 0? runIndex : 0);
2494    }
2495
2496    /**
2497     * Returns the next tab run.
2498     * @param baseRun the base run
2499     * @return the next tab run
2500     */
2501    protected int getNextTabRun(int baseRun) {
2502        return (baseRun+1)%runCount;
2503    }
2504
2505    /**
2506     * Rotates the insets.
2507     * @param topInsets the top insets
2508     * @param targetInsets the target insets
2509     * @param targetPlacement the target placement
2510     */
2511    protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) {
2512
2513        switch(targetPlacement) {
2514          case LEFT:
2515              targetInsets.top = topInsets.left;
2516              targetInsets.left = topInsets.top;
2517              targetInsets.bottom = topInsets.right;
2518              targetInsets.right = topInsets.bottom;
2519              break;
2520          case BOTTOM:
2521              targetInsets.top = topInsets.bottom;
2522              targetInsets.left = topInsets.left;
2523              targetInsets.bottom = topInsets.top;
2524              targetInsets.right = topInsets.right;
2525              break;
2526          case RIGHT:
2527              targetInsets.top = topInsets.left;
2528              targetInsets.left = topInsets.bottom;
2529              targetInsets.bottom = topInsets.right;
2530              targetInsets.right = topInsets.top;
2531              break;
2532          case TOP:
2533          default:
2534              targetInsets.top = topInsets.top;
2535              targetInsets.left = topInsets.left;
2536              targetInsets.bottom = topInsets.bottom;
2537              targetInsets.right = topInsets.right;
2538        }
2539    }
2540
2541    // REMIND(aim,7/29/98): This method should be made
2542    // protected in the next release where
2543    // API changes are allowed
2544    boolean requestFocusForVisibleComponent() {
2545        return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent());
2546    }
2547
2548    private static class Actions extends UIAction {
2549        static final String NEXT = "navigateNext";
2550        static final String PREVIOUS = "navigatePrevious";
2551        static final String RIGHT = "navigateRight";
2552        static final String LEFT = "navigateLeft";
2553        static final String UP = "navigateUp";
2554        static final String DOWN = "navigateDown";
2555        static final String PAGE_UP = "navigatePageUp";
2556        static final String PAGE_DOWN = "navigatePageDown";
2557        static final String REQUEST_FOCUS = "requestFocus";
2558        static final String REQUEST_FOCUS_FOR_VISIBLE =
2559                                    "requestFocusForVisibleComponent";
2560        static final String SET_SELECTED = "setSelectedIndex";
2561        static final String SELECT_FOCUSED = "selectTabWithFocus";
2562        static final String SCROLL_FORWARD = "scrollTabsForwardAction";
2563        static final String SCROLL_BACKWARD = "scrollTabsBackwardAction";
2564
2565        Actions(String key) {
2566            super(key);
2567        }
2568
2569        public void actionPerformed(ActionEvent e) {
2570            String key = getName();
2571            JTabbedPane pane = (JTabbedPane)e.getSource();
2572            BasicTabbedPaneUI ui = (BasicTabbedPaneUI)BasicLookAndFeel.
2573                       getUIOfType(pane.getUI(), BasicTabbedPaneUI.class);
2574
2575            if (ui == null) {
2576                return;
2577            }
2578            if (key == NEXT) {
2579                ui.navigateSelectedTab(SwingConstants.NEXT);
2580            }
2581            else if (key == PREVIOUS) {
2582                ui.navigateSelectedTab(SwingConstants.PREVIOUS);
2583            }
2584            else if (key == RIGHT) {
2585                ui.navigateSelectedTab(SwingConstants.EAST);
2586            }
2587            else if (key == LEFT) {
2588                ui.navigateSelectedTab(SwingConstants.WEST);
2589            }
2590            else if (key == UP) {
2591                ui.navigateSelectedTab(SwingConstants.NORTH);
2592            }
2593            else if (key == DOWN) {
2594                ui.navigateSelectedTab(SwingConstants.SOUTH);
2595            }
2596            else if (key == PAGE_UP) {
2597                int tabPlacement = pane.getTabPlacement();
2598                if (tabPlacement == TOP|| tabPlacement == BOTTOM) {
2599                    ui.navigateSelectedTab(SwingConstants.WEST);
2600                } else {
2601                    ui.navigateSelectedTab(SwingConstants.NORTH);
2602                }
2603            }
2604            else if (key == PAGE_DOWN) {
2605                int tabPlacement = pane.getTabPlacement();
2606                if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2607                    ui.navigateSelectedTab(SwingConstants.EAST);
2608                } else {
2609                    ui.navigateSelectedTab(SwingConstants.SOUTH);
2610                }
2611            }
2612            else if (key == REQUEST_FOCUS) {
2613                pane.requestFocus();
2614            }
2615            else if (key == REQUEST_FOCUS_FOR_VISIBLE) {
2616                ui.requestFocusForVisibleComponent();
2617            }
2618            else if (key == SET_SELECTED) {
2619                String command = e.getActionCommand();
2620
2621                if (command != null && command.length() > 0) {
2622                    int mnemonic = (int)e.getActionCommand().charAt(0);
2623                    if (mnemonic >= 'a' && mnemonic <='z') {
2624                        mnemonic  -= ('a' - 'A');
2625                    }
2626                    Integer index = ui.mnemonicToIndexMap.get(Integer.valueOf(mnemonic));
2627                    if (index != null && pane.isEnabledAt(index.intValue())) {
2628                        pane.setSelectedIndex(index.intValue());
2629                    }
2630                }
2631            }
2632            else if (key == SELECT_FOCUSED) {
2633                int focusIndex = ui.getFocusIndex();
2634                if (focusIndex != -1) {
2635                    pane.setSelectedIndex(focusIndex);
2636                }
2637            }
2638            else if (key == SCROLL_FORWARD) {
2639                if (ui.scrollableTabLayoutEnabled()) {
2640                    ui.tabScroller.scrollForward(pane.getTabPlacement());
2641                }
2642            }
2643            else if (key == SCROLL_BACKWARD) {
2644                if (ui.scrollableTabLayoutEnabled()) {
2645                    ui.tabScroller.scrollBackward(pane.getTabPlacement());
2646                }
2647            }
2648        }
2649    }
2650
2651    /**
2652     * This class should be treated as a &quot;protected&quot; inner class.
2653     * Instantiate it only within subclasses of BasicTabbedPaneUI.
2654     */
2655    public class TabbedPaneLayout implements LayoutManager {
2656
2657        public void addLayoutComponent(String name, Component comp) {}
2658
2659        public void removeLayoutComponent(Component comp) {}
2660
2661        public Dimension preferredLayoutSize(Container parent) {
2662            return calculateSize(false);
2663        }
2664
2665        public Dimension minimumLayoutSize(Container parent) {
2666            return calculateSize(true);
2667        }
2668
2669        /**
2670         * Returns the calculated size.
2671         * @param minimum use the minimum size or preferred size
2672         * @return the calculated size
2673         */
2674        protected Dimension calculateSize(boolean minimum) {
2675            int tabPlacement = tabPane.getTabPlacement();
2676            Insets insets = tabPane.getInsets();
2677            Insets contentInsets = getContentBorderInsets(tabPlacement);
2678            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2679
2680            Dimension zeroSize = new Dimension(0,0);
2681            int height = 0;
2682            int width = 0;
2683            int cWidth = 0;
2684            int cHeight = 0;
2685
2686            // Determine minimum size required to display largest
2687            // child in each dimension
2688            //
2689            for (int i = 0; i < tabPane.getTabCount(); i++) {
2690                Component component = tabPane.getComponentAt(i);
2691                if (component != null) {
2692                    Dimension size = minimum ? component.getMinimumSize() :
2693                                component.getPreferredSize();
2694
2695                    if (size != null) {
2696                        cHeight = Math.max(size.height, cHeight);
2697                        cWidth = Math.max(size.width, cWidth);
2698                    }
2699                }
2700            }
2701            // Add content border insets to minimum size
2702            width += cWidth;
2703            height += cHeight;
2704            int tabExtent;
2705
2706            // Calculate how much space the tabs will need, based on the
2707            // minimum size required to display largest child + content border
2708            //
2709            switch(tabPlacement) {
2710              case LEFT:
2711              case RIGHT:
2712                  height = Math.max(height, calculateMaxTabHeight(tabPlacement));
2713                  tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
2714                  width += tabExtent;
2715                  break;
2716              case TOP:
2717              case BOTTOM:
2718              default:
2719                  width = Math.max(width, calculateMaxTabWidth(tabPlacement));
2720                  tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
2721                  height += tabExtent;
2722            }
2723            return new Dimension(width + insets.left + insets.right + contentInsets.left + contentInsets.right,
2724                             height + insets.bottom + insets.top + contentInsets.top + contentInsets.bottom);
2725
2726        }
2727
2728        /**
2729         * Returns the preferred tab area height.
2730         * @param tabPlacement the tab placement
2731         * @param width the width
2732         * @return the preferred tab area height
2733         */
2734        protected int preferredTabAreaHeight(int tabPlacement, int width) {
2735            FontMetrics metrics = getFontMetrics();
2736            int tabCount = tabPane.getTabCount();
2737            int total = 0;
2738            if (tabCount > 0) {
2739                int rows = 1;
2740                int x = 0;
2741
2742                int maxTabHeight = calculateMaxTabHeight(tabPlacement);
2743
2744                for (int i = 0; i < tabCount; i++) {
2745                    int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
2746
2747                    if (x != 0 && x + tabWidth > width) {
2748                        rows++;
2749                        x = 0;
2750                    }
2751                    x += tabWidth;
2752                }
2753                total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
2754            }
2755            return total;
2756        }
2757
2758        /**
2759         * Returns the preferred tab area width.
2760         * @param tabPlacement the tab placement
2761         * @param height the height
2762         * @return the preferred tab area widty
2763         */
2764        protected int preferredTabAreaWidth(int tabPlacement, int height) {
2765            FontMetrics metrics = getFontMetrics();
2766            int tabCount = tabPane.getTabCount();
2767            int total = 0;
2768            if (tabCount > 0) {
2769                int columns = 1;
2770                int y = 0;
2771                int fontHeight = metrics.getHeight();
2772
2773                maxTabWidth = calculateMaxTabWidth(tabPlacement);
2774
2775                for (int i = 0; i < tabCount; i++) {
2776                    int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
2777
2778                    if (y != 0 && y + tabHeight > height) {
2779                        columns++;
2780                        y = 0;
2781                    }
2782                    y += tabHeight;
2783                }
2784                total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
2785            }
2786            return total;
2787        }
2788
2789        /** {@inheritDoc} */
2790        @SuppressWarnings("deprecation")
2791        public void layoutContainer(Container parent) {
2792            /* Some of the code in this method deals with changing the
2793            * visibility of components to hide and show the contents for the
2794            * selected tab. This is older code that has since been duplicated
2795            * in JTabbedPane.fireStateChanged(), so as to allow visibility
2796            * changes to happen sooner (see the note there). This code remains
2797            * for backward compatibility as there are some cases, such as
2798            * subclasses that don't fireStateChanged() where it may be used.
2799            * Any changes here need to be kept in synch with
2800            * JTabbedPane.fireStateChanged().
2801            */
2802
2803            setRolloverTab(-1);
2804
2805            int tabPlacement = tabPane.getTabPlacement();
2806            Insets insets = tabPane.getInsets();
2807            int selectedIndex = tabPane.getSelectedIndex();
2808            Component visibleComponent = getVisibleComponent();
2809
2810            calculateLayoutInfo();
2811
2812            Component selectedComponent = null;
2813            if (selectedIndex < 0) {
2814                if (visibleComponent != null) {
2815                    // The last tab was removed, so remove the component
2816                    setVisibleComponent(null);
2817                }
2818            } else {
2819                selectedComponent = tabPane.getComponentAt(selectedIndex);
2820            }
2821            int cx, cy, cw, ch;
2822            int totalTabWidth = 0;
2823            int totalTabHeight = 0;
2824            Insets contentInsets = getContentBorderInsets(tabPlacement);
2825
2826            boolean shouldChangeFocus = false;
2827
2828            // In order to allow programs to use a single component
2829            // as the display for multiple tabs, we will not change
2830            // the visible compnent if the currently selected tab
2831            // has a null component.  This is a bit dicey, as we don't
2832            // explicitly state we support this in the spec, but since
2833            // programs are now depending on this, we're making it work.
2834            //
2835            if(selectedComponent != null) {
2836                if(selectedComponent != visibleComponent &&
2837                        visibleComponent != null) {
2838                    if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
2839                        shouldChangeFocus = true;
2840                    }
2841                }
2842                setVisibleComponent(selectedComponent);
2843            }
2844
2845            Rectangle bounds = tabPane.getBounds();
2846            int numChildren = tabPane.getComponentCount();
2847
2848            if(numChildren > 0) {
2849
2850                switch(tabPlacement) {
2851                    case LEFT:
2852                        totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2853                        cx = insets.left + totalTabWidth + contentInsets.left;
2854                        cy = insets.top + contentInsets.top;
2855                        break;
2856                    case RIGHT:
2857                        totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2858                        cx = insets.left + contentInsets.left;
2859                        cy = insets.top + contentInsets.top;
2860                        break;
2861                    case BOTTOM:
2862                        totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2863                        cx = insets.left + contentInsets.left;
2864                        cy = insets.top + contentInsets.top;
2865                        break;
2866                    case TOP:
2867                    default:
2868                        totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2869                        cx = insets.left + contentInsets.left;
2870                        cy = insets.top + totalTabHeight + contentInsets.top;
2871                }
2872
2873                cw = bounds.width - totalTabWidth -
2874                        insets.left - insets.right -
2875                        contentInsets.left - contentInsets.right;
2876                ch = bounds.height - totalTabHeight -
2877                        insets.top - insets.bottom -
2878                        contentInsets.top - contentInsets.bottom;
2879
2880                for(int i = 0; i < numChildren; i++) {
2881                    Component child = tabPane.getComponent(i);
2882                    if(child == tabContainer) {
2883
2884                        int tabContainerWidth = totalTabWidth == 0 ? bounds.width :
2885                                totalTabWidth + insets.left + insets.right +
2886                                        contentInsets.left + contentInsets.right;
2887                        int tabContainerHeight = totalTabHeight == 0 ? bounds.height :
2888                                totalTabHeight + insets.top + insets.bottom +
2889                                        contentInsets.top + contentInsets.bottom;
2890
2891                        int tabContainerX = 0;
2892                        int tabContainerY = 0;
2893                        if(tabPlacement == BOTTOM) {
2894                            tabContainerY = bounds.height - tabContainerHeight;
2895                        } else if(tabPlacement == RIGHT) {
2896                            tabContainerX = bounds.width - tabContainerWidth;
2897                        }
2898                        child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
2899                    } else {
2900                        child.setBounds(cx, cy, cw, ch);
2901                    }
2902                }
2903            }
2904            layoutTabComponents();
2905            if(shouldChangeFocus) {
2906                if(!requestFocusForVisibleComponent()) {
2907                    tabPane.requestFocus();
2908                }
2909            }
2910        }
2911
2912        /**
2913         * Calculates the layout info.
2914         */
2915        public void calculateLayoutInfo() {
2916            int tabCount = tabPane.getTabCount();
2917            assureRectsCreated(tabCount);
2918            calculateTabRects(tabPane.getTabPlacement(), tabCount);
2919            isRunsDirty = false;
2920        }
2921
2922        private void layoutTabComponents() {
2923            if (tabContainer == null) {
2924                return;
2925            }
2926            Rectangle rect = new Rectangle();
2927            Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
2928            if (scrollableTabLayoutEnabled()) {
2929                translatePointToTabPanel(0, 0, delta);
2930            }
2931            for (int i = 0; i < tabPane.getTabCount(); i++) {
2932                Component c = tabPane.getTabComponentAt(i);
2933                if (c == null) {
2934                    continue;
2935                }
2936                getTabBounds(i, rect);
2937                Dimension preferredSize = c.getPreferredSize();
2938                Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
2939                int outerX = rect.x + insets.left + delta.x;
2940                int outerY = rect.y + insets.top + delta.y;
2941                int outerWidth = rect.width - insets.left - insets.right;
2942                int outerHeight = rect.height - insets.top - insets.bottom;
2943                //centralize component
2944                int x = outerX + (outerWidth - preferredSize.width) / 2;
2945                int y = outerY + (outerHeight - preferredSize.height) / 2;
2946                int tabPlacement = tabPane.getTabPlacement();
2947                boolean isSeleceted = i == tabPane.getSelectedIndex();
2948                c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted),
2949                            y + getTabLabelShiftY(tabPlacement, i, isSeleceted),
2950                        preferredSize.width, preferredSize.height);
2951            }
2952        }
2953
2954        /**
2955         * Calculate the tab rectangles.
2956         * @param tabPlacement the tab placement
2957         * @param tabCount the tab count
2958         */
2959        protected void calculateTabRects(int tabPlacement, int tabCount) {
2960            FontMetrics metrics = getFontMetrics();
2961            Dimension size = tabPane.getSize();
2962            Insets insets = tabPane.getInsets();
2963            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2964            int fontHeight = metrics.getHeight();
2965            int selectedIndex = tabPane.getSelectedIndex();
2966            int tabRunOverlay;
2967            int i, j;
2968            int x, y;
2969            int returnAt;
2970            boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2971            boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
2972
2973            //
2974            // Calculate bounds within which a tab run must fit
2975            //
2976            switch(tabPlacement) {
2977              case LEFT:
2978                  maxTabWidth = calculateMaxTabWidth(tabPlacement);
2979                  x = insets.left + tabAreaInsets.left;
2980                  y = insets.top + tabAreaInsets.top;
2981                  returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2982                  break;
2983              case RIGHT:
2984                  maxTabWidth = calculateMaxTabWidth(tabPlacement);
2985                  x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
2986                  y = insets.top + tabAreaInsets.top;
2987                  returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2988                  break;
2989              case BOTTOM:
2990                  maxTabHeight = calculateMaxTabHeight(tabPlacement);
2991                  x = insets.left + tabAreaInsets.left;
2992                  y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
2993                  returnAt = size.width - (insets.right + tabAreaInsets.right);
2994                  break;
2995              case TOP:
2996              default:
2997                  maxTabHeight = calculateMaxTabHeight(tabPlacement);
2998                  x = insets.left + tabAreaInsets.left;
2999                  y = insets.top + tabAreaInsets.top;
3000                  returnAt = size.width - (insets.right + tabAreaInsets.right);
3001                  break;
3002            }
3003
3004            tabRunOverlay = getTabRunOverlay(tabPlacement);
3005
3006            runCount = 0;
3007            selectedRun = -1;
3008
3009            if (tabCount == 0) {
3010                return;
3011            }
3012
3013            // Run through tabs and partition them into runs
3014            Rectangle rect;
3015            for (i = 0; i < tabCount; i++) {
3016                rect = rects[i];
3017
3018                if (!verticalTabRuns) {
3019                    // Tabs on TOP or BOTTOM....
3020                    if (i > 0) {
3021                        rect.x = rects[i-1].x + rects[i-1].width;
3022                    } else {
3023                        tabRuns[0] = 0;
3024                        runCount = 1;
3025                        maxTabWidth = 0;
3026                        rect.x = x;
3027                    }
3028                    rect.width = calculateTabWidth(tabPlacement, i, metrics);
3029                    maxTabWidth = Math.max(maxTabWidth, rect.width);
3030
3031                    // Never move a TAB down a run if it is in the first column.
3032                    // Even if there isn't enough room, moving it to a fresh
3033                    // line won't help.
3034                    if (rect.x != x && rect.x + rect.width > returnAt) {
3035                        if (runCount > tabRuns.length - 1) {
3036                            expandTabRunsArray();
3037                        }
3038                        tabRuns[runCount] = i;
3039                        runCount++;
3040                        rect.x = x;
3041                    }
3042                    // Initialize y position in case there's just one run
3043                    rect.y = y;
3044                    rect.height = maxTabHeight/* - 2*/;
3045
3046                } else {
3047                    // Tabs on LEFT or RIGHT...
3048                    if (i > 0) {
3049                        rect.y = rects[i-1].y + rects[i-1].height;
3050                    } else {
3051                        tabRuns[0] = 0;
3052                        runCount = 1;
3053                        maxTabHeight = 0;
3054                        rect.y = y;
3055                    }
3056                    rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3057                    maxTabHeight = Math.max(maxTabHeight, rect.height);
3058
3059                    // Never move a TAB over a run if it is in the first run.
3060                    // Even if there isn't enough room, moving it to a fresh
3061                    // column won't help.
3062                    if (rect.y != y && rect.y + rect.height > returnAt) {
3063                        if (runCount > tabRuns.length - 1) {
3064                            expandTabRunsArray();
3065                        }
3066                        tabRuns[runCount] = i;
3067                        runCount++;
3068                        rect.y = y;
3069                    }
3070                    // Initialize x position in case there's just one column
3071                    rect.x = x;
3072                    rect.width = maxTabWidth/* - 2*/;
3073
3074                }
3075                if (i == selectedIndex) {
3076                    selectedRun = runCount - 1;
3077                }
3078            }
3079
3080            if (runCount > 1) {
3081                // Re-distribute tabs in case last run has leftover space
3082                normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
3083
3084                selectedRun = getRunForTab(tabCount, selectedIndex);
3085
3086                // Rotate run array so that selected run is first
3087                if (shouldRotateTabRuns(tabPlacement)) {
3088                    rotateTabRuns(tabPlacement, selectedRun);
3089                }
3090            }
3091
3092            // Step through runs from back to front to calculate
3093            // tab y locations and to pad runs appropriately
3094            for (i = runCount - 1; i >= 0; i--) {
3095                int start = tabRuns[i];
3096                int next = tabRuns[i == (runCount - 1)? 0 : i + 1];
3097                int end = (next != 0? next - 1 : tabCount - 1);
3098                if (!verticalTabRuns) {
3099                    for (j = start; j <= end; j++) {
3100                        rect = rects[j];
3101                        rect.y = y;
3102                        rect.x += getTabRunIndent(tabPlacement, i);
3103                    }
3104                    if (shouldPadTabRun(tabPlacement, i)) {
3105                        padTabRun(tabPlacement, start, end, returnAt);
3106                    }
3107                    if (tabPlacement == BOTTOM) {
3108                        y -= (maxTabHeight - tabRunOverlay);
3109                    } else {
3110                        y += (maxTabHeight - tabRunOverlay);
3111                    }
3112                } else {
3113                    for (j = start; j <= end; j++) {
3114                        rect = rects[j];
3115                        rect.x = x;
3116                        rect.y += getTabRunIndent(tabPlacement, i);
3117                    }
3118                    if (shouldPadTabRun(tabPlacement, i)) {
3119                        padTabRun(tabPlacement, start, end, returnAt);
3120                    }
3121                    if (tabPlacement == RIGHT) {
3122                        x -= (maxTabWidth - tabRunOverlay);
3123                    } else {
3124                        x += (maxTabWidth - tabRunOverlay);
3125                    }
3126                }
3127            }
3128
3129            // Pad the selected tab so that it appears raised in front
3130            padSelectedTab(tabPlacement, selectedIndex);
3131
3132            // if right to left and tab placement on the top or
3133            // the bottom, flip x positions and adjust by widths
3134            if (!leftToRight && !verticalTabRuns) {
3135                int rightMargin = size.width
3136                                  - (insets.right + tabAreaInsets.right);
3137                for (i = 0; i < tabCount; i++) {
3138                    rects[i].x = rightMargin - rects[i].x - rects[i].width;
3139                }
3140            }
3141        }
3142
3143
3144        /**
3145         * Rotates the run-index array so that the selected run is run[0].
3146         * @param tabPlacement the tab placement
3147         * @param selectedRun the selected run
3148         */
3149        protected void rotateTabRuns(int tabPlacement, int selectedRun) {
3150            for (int i = 0; i < selectedRun; i++) {
3151                int save = tabRuns[0];
3152                for (int j = 1; j < runCount; j++) {
3153                    tabRuns[j - 1] = tabRuns[j];
3154                }
3155                tabRuns[runCount-1] = save;
3156            }
3157        }
3158
3159        /**
3160         * Normalizes the tab runs.
3161         * @param tabPlacement the tab placement
3162         * @param tabCount the tab count
3163         * @param start the start
3164         * @param max the max
3165         */
3166        protected void normalizeTabRuns(int tabPlacement, int tabCount,
3167                                     int start, int max) {
3168            boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
3169            int run = runCount - 1;
3170            boolean keepAdjusting = true;
3171            double weight = 1.25;
3172
3173            // At this point the tab runs are packed to fit as many
3174            // tabs as possible, which can leave the last run with a lot
3175            // of extra space (resulting in very fat tabs on the last run).
3176            // So we'll attempt to distribute this extra space more evenly
3177            // across the runs in order to make the runs look more consistent.
3178            //
3179            // Starting with the last run, determine whether the last tab in
3180            // the previous run would fit (generously) in this run; if so,
3181            // move tab to current run and shift tabs accordingly.  Cycle
3182            // through remaining runs using the same algorithm.
3183            //
3184            while (keepAdjusting) {
3185                int last = lastTabInRun(tabCount, run);
3186                int prevLast = lastTabInRun(tabCount, run-1);
3187                int end;
3188                int prevLastLen;
3189
3190                if (!verticalTabRuns) {
3191                    end = rects[last].x + rects[last].width;
3192                    prevLastLen = (int)(maxTabWidth*weight);
3193                } else {
3194                    end = rects[last].y + rects[last].height;
3195                    prevLastLen = (int)(maxTabHeight*weight*2);
3196                }
3197
3198                // Check if the run has enough extra space to fit the last tab
3199                // from the previous row...
3200                if (max - end > prevLastLen) {
3201
3202                    // Insert tab from previous row and shift rest over
3203                    tabRuns[run] = prevLast;
3204                    if (!verticalTabRuns) {
3205                        rects[prevLast].x = start;
3206                    } else {
3207                        rects[prevLast].y = start;
3208                    }
3209                    for (int i = prevLast+1; i <= last; i++) {
3210                        if (!verticalTabRuns) {
3211                            rects[i].x = rects[i-1].x + rects[i-1].width;
3212                        } else {
3213                            rects[i].y = rects[i-1].y + rects[i-1].height;
3214                        }
3215                    }
3216
3217                } else if (run == runCount - 1) {
3218                    // no more room left in last run, so we're done!
3219                    keepAdjusting = false;
3220                }
3221                if (run - 1 > 0) {
3222                    // check previous run next...
3223                    run -= 1;
3224                } else {
3225                    // check last run again...but require a higher ratio
3226                    // of extraspace-to-tabsize because we don't want to
3227                    // end up with too many tabs on the last run!
3228                    run = runCount - 1;
3229                    weight += .25;
3230                }
3231            }
3232        }
3233
3234        /**
3235         * Pads the tab run.
3236         * @param tabPlacement the tab placement
3237         * @param start the start
3238         * @param end the end
3239         * @param max the max
3240         */
3241        protected void padTabRun(int tabPlacement, int start, int end, int max) {
3242            Rectangle lastRect = rects[end];
3243            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3244                int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
3245                int deltaWidth = max - (lastRect.x + lastRect.width);
3246                float factor = (float)deltaWidth / (float)runWidth;
3247
3248                for (int j = start; j <= end; j++) {
3249                    Rectangle pastRect = rects[j];
3250                    if (j > start) {
3251                        pastRect.x = rects[j-1].x + rects[j-1].width;
3252                    }
3253                    pastRect.width += Math.round((float)pastRect.width * factor);
3254                }
3255                lastRect.width = max - lastRect.x;
3256            } else {
3257                int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
3258                int deltaHeight = max - (lastRect.y + lastRect.height);
3259                float factor = (float)deltaHeight / (float)runHeight;
3260
3261                for (int j = start; j <= end; j++) {
3262                    Rectangle pastRect = rects[j];
3263                    if (j > start) {
3264                        pastRect.y = rects[j-1].y + rects[j-1].height;
3265                    }
3266                    pastRect.height += Math.round((float)pastRect.height * factor);
3267                }
3268                lastRect.height = max - lastRect.y;
3269            }
3270        }
3271
3272        /**
3273         * Pads selected tab.
3274         * @param tabPlacement the tab placement
3275         * @param selectedIndex the selected index
3276         */
3277        protected void padSelectedTab(int tabPlacement, int selectedIndex) {
3278
3279            if (selectedIndex >= 0) {
3280                Rectangle selRect = rects[selectedIndex];
3281                Insets padInsets = getSelectedTabPadInsets(tabPlacement);
3282                selRect.x -= padInsets.left;
3283                selRect.width += (padInsets.left + padInsets.right);
3284                selRect.y -= padInsets.top;
3285                selRect.height += (padInsets.top + padInsets.bottom);
3286
3287                if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
3288                    // do not expand selected tab more then necessary
3289                    Dimension size = tabPane.getSize();
3290                    Insets insets = tabPane.getInsets();
3291
3292                    if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) {
3293                        int top = insets.top - selRect.y;
3294                        if (top > 0) {
3295                            selRect.y += top;
3296                            selRect.height -= top;
3297                        }
3298                        int bottom = (selRect.y + selRect.height) + insets.bottom - size.height;
3299                        if (bottom > 0) {
3300                            selRect.height -= bottom;
3301                        }
3302                    } else {
3303                        int left = insets.left - selRect.x;
3304                        if (left > 0) {
3305                            selRect.x += left;
3306                            selRect.width -= left;
3307                        }
3308                        int right = (selRect.x + selRect.width) + insets.right - size.width;
3309                        if (right > 0) {
3310                            selRect.width -= right;
3311                        }
3312                    }
3313                }
3314            }
3315        }
3316    }
3317
3318    private class TabbedPaneScrollLayout extends TabbedPaneLayout {
3319
3320        protected int preferredTabAreaHeight(int tabPlacement, int width) {
3321            return calculateMaxTabHeight(tabPlacement);
3322        }
3323
3324        protected int preferredTabAreaWidth(int tabPlacement, int height) {
3325            return calculateMaxTabWidth(tabPlacement);
3326        }
3327
3328        @SuppressWarnings("deprecation")
3329        public void layoutContainer(Container parent) {
3330            /* Some of the code in this method deals with changing the
3331             * visibility of components to hide and show the contents for the
3332             * selected tab. This is older code that has since been duplicated
3333             * in JTabbedPane.fireStateChanged(), so as to allow visibility
3334             * changes to happen sooner (see the note there). This code remains
3335             * for backward compatibility as there are some cases, such as
3336             * subclasses that don't fireStateChanged() where it may be used.
3337             * Any changes here need to be kept in synch with
3338             * JTabbedPane.fireStateChanged().
3339             */
3340
3341            setRolloverTab(-1);
3342
3343            int tabPlacement = tabPane.getTabPlacement();
3344            int tabCount = tabPane.getTabCount();
3345            Insets insets = tabPane.getInsets();
3346            int selectedIndex = tabPane.getSelectedIndex();
3347            Component visibleComponent = getVisibleComponent();
3348
3349            calculateLayoutInfo();
3350
3351            Component selectedComponent = null;
3352            if (selectedIndex < 0) {
3353                if (visibleComponent != null) {
3354                    // The last tab was removed, so remove the component
3355                    setVisibleComponent(null);
3356                }
3357            } else {
3358                selectedComponent = tabPane.getComponentAt(selectedIndex);
3359            }
3360
3361            if (tabPane.getTabCount() == 0) {
3362                tabScroller.croppedEdge.resetParams();
3363                tabScroller.scrollForwardButton.setVisible(false);
3364                tabScroller.scrollBackwardButton.setVisible(false);
3365                return;
3366            }
3367
3368            boolean shouldChangeFocus = false;
3369
3370            // In order to allow programs to use a single component
3371            // as the display for multiple tabs, we will not change
3372            // the visible compnent if the currently selected tab
3373            // has a null component.  This is a bit dicey, as we don't
3374            // explicitly state we support this in the spec, but since
3375            // programs are now depending on this, we're making it work.
3376            //
3377            if(selectedComponent != null) {
3378                if(selectedComponent != visibleComponent &&
3379                        visibleComponent != null) {
3380                    if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
3381                        shouldChangeFocus = true;
3382                    }
3383                }
3384                setVisibleComponent(selectedComponent);
3385            }
3386            int tx, ty, tw, th; // tab area bounds
3387            int cx, cy, cw, ch; // content area bounds
3388            Insets contentInsets = getContentBorderInsets(tabPlacement);
3389            Rectangle bounds = tabPane.getBounds();
3390            int numChildren = tabPane.getComponentCount();
3391
3392            if(numChildren > 0) {
3393                switch(tabPlacement) {
3394                    case LEFT:
3395                        // calculate tab area bounds
3396                        tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
3397                        th = bounds.height - insets.top - insets.bottom;
3398                        tx = insets.left;
3399                        ty = insets.top;
3400
3401                        // calculate content area bounds
3402                        cx = tx + tw + contentInsets.left;
3403                        cy = ty + contentInsets.top;
3404                        cw = bounds.width - insets.left - insets.right - tw -
3405                                contentInsets.left - contentInsets.right;
3406                        ch = bounds.height - insets.top - insets.bottom -
3407                                contentInsets.top - contentInsets.bottom;
3408                        break;
3409                    case RIGHT:
3410                        // calculate tab area bounds
3411                        tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
3412                        th = bounds.height - insets.top - insets.bottom;
3413                        tx = bounds.width - insets.right - tw;
3414                        ty = insets.top;
3415
3416                        // calculate content area bounds
3417                        cx = insets.left + contentInsets.left;
3418                        cy = insets.top + contentInsets.top;
3419                        cw = bounds.width - insets.left - insets.right - tw -
3420                                contentInsets.left - contentInsets.right;
3421                        ch = bounds.height - insets.top - insets.bottom -
3422                                contentInsets.top - contentInsets.bottom;
3423                        break;
3424                    case BOTTOM:
3425                        // calculate tab area bounds
3426                        tw = bounds.width - insets.left - insets.right;
3427                        th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
3428                        tx = insets.left;
3429                        ty = bounds.height - insets.bottom - th;
3430
3431                        // calculate content area bounds
3432                        cx = insets.left + contentInsets.left;
3433                        cy = insets.top + contentInsets.top;
3434                        cw = bounds.width - insets.left - insets.right -
3435                                contentInsets.left - contentInsets.right;
3436                        ch = bounds.height - insets.top - insets.bottom - th -
3437                                contentInsets.top - contentInsets.bottom;
3438                        break;
3439                    case TOP:
3440                    default:
3441                        // calculate tab area bounds
3442                        tw = bounds.width - insets.left - insets.right;
3443                        th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
3444                        tx = insets.left;
3445                        ty = insets.top;
3446
3447                        // calculate content area bounds
3448                        cx = tx + contentInsets.left;
3449                        cy = ty + th + contentInsets.top;
3450                        cw = bounds.width - insets.left - insets.right -
3451                                contentInsets.left - contentInsets.right;
3452                        ch = bounds.height - insets.top - insets.bottom - th -
3453                                contentInsets.top - contentInsets.bottom;
3454                }
3455
3456                for(int i = 0; i < numChildren; i++) {
3457                    Component child = tabPane.getComponent(i);
3458
3459                    if(tabScroller != null && child == tabScroller.viewport) {
3460                        JViewport viewport = (JViewport) child;
3461                        Rectangle viewRect = viewport.getViewRect();
3462                        int vw = tw;
3463                        int vh = th;
3464                        Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
3465                        switch(tabPlacement) {
3466                            case LEFT:
3467                            case RIGHT:
3468                                int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3469                                if(totalTabHeight > th) {
3470                                    // Allow space for scrollbuttons
3471                                    vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0;
3472                                    if(totalTabHeight - viewRect.y <= vh) {
3473                                        // Scrolled to the end, so ensure the viewport size is
3474                                        // such that the scroll offset aligns with a tab
3475                                        vh = totalTabHeight - viewRect.y;
3476                                    }
3477                                }
3478                                break;
3479                            case BOTTOM:
3480                            case TOP:
3481                            default:
3482                                int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3483                                if(totalTabWidth > tw) {
3484                                    // Need to allow space for scrollbuttons
3485                                    vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0;
3486                                    if(totalTabWidth - viewRect.x <= vw) {
3487                                        // Scrolled to the end, so ensure the viewport size is
3488                                        // such that the scroll offset aligns with a tab
3489                                        vw = totalTabWidth - viewRect.x;
3490                                    }
3491                                }
3492                        }
3493                        child.setBounds(tx, ty, vw, vh);
3494
3495                    } else if(tabScroller != null &&
3496                            (child == tabScroller.scrollForwardButton ||
3497                            child == tabScroller.scrollBackwardButton)) {
3498                        Component scrollbutton = child;
3499                        Dimension bsize = scrollbutton.getPreferredSize();
3500                        int bx = 0;
3501                        int by = 0;
3502                        int bw = bsize.width;
3503                        int bh = bsize.height;
3504                        boolean visible = false;
3505
3506                        switch(tabPlacement) {
3507                            case LEFT:
3508                            case RIGHT:
3509                                int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3510                                if(totalTabHeight > th) {
3511                                    visible = true;
3512                                    bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx);
3513                                    by = (child == tabScroller.scrollForwardButton) ?
3514                                            bounds.height - insets.bottom - bsize.height :
3515                                            bounds.height - insets.bottom - 2 * bsize.height;
3516                                }
3517                                break;
3518
3519                            case BOTTOM:
3520                            case TOP:
3521                            default:
3522                                int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3523
3524                                if(totalTabWidth > tw) {
3525                                    visible = true;
3526                                    bx = (child == tabScroller.scrollForwardButton) ?
3527                                            bounds.width - insets.left - bsize.width :
3528                                            bounds.width - insets.left - 2 * bsize.width;
3529                                    by = (tabPlacement == TOP ? ty + th - bsize.height : ty);
3530                                }
3531                        }
3532                        child.setVisible(visible);
3533                        if(visible) {
3534                            child.setBounds(bx, by, bw, bh);
3535                        }
3536
3537                    } else {
3538                        // All content children...
3539                        child.setBounds(cx, cy, cw, ch);
3540                    }
3541                }
3542                super.layoutTabComponents();
3543                layoutCroppedEdge();
3544                if(shouldChangeFocus) {
3545                    if(!requestFocusForVisibleComponent()) {
3546                        tabPane.requestFocus();
3547                    }
3548                }
3549            }
3550        }
3551
3552        private void layoutCroppedEdge() {
3553            tabScroller.croppedEdge.resetParams();
3554            Rectangle viewRect = tabScroller.viewport.getViewRect();
3555            int cropline;
3556            for (int i = 0; i < rects.length; i++) {
3557                Rectangle tabRect = rects[i];
3558                switch (tabPane.getTabPlacement()) {
3559                    case LEFT:
3560                    case RIGHT:
3561                        cropline = viewRect.y + viewRect.height;
3562                        if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
3563                            tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1,
3564                                    -currentTabAreaInsets.left,  0);
3565                        }
3566                        break;
3567                    case TOP:
3568                    case BOTTOM:
3569                    default:
3570                        cropline = viewRect.x + viewRect.width;
3571                        if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) {
3572                            tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1,
3573                                    0, -currentTabAreaInsets.top);
3574                        }
3575                }
3576            }
3577        }
3578
3579        protected void calculateTabRects(int tabPlacement, int tabCount) {
3580            FontMetrics metrics = getFontMetrics();
3581            Dimension size = tabPane.getSize();
3582            Insets insets = tabPane.getInsets();
3583            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
3584            int fontHeight = metrics.getHeight();
3585            int selectedIndex = tabPane.getSelectedIndex();
3586            int i;
3587            boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
3588            boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
3589            int x = tabAreaInsets.left;
3590            int y = tabAreaInsets.top;
3591            int totalWidth = 0;
3592            int totalHeight = 0;
3593
3594            //
3595            // Calculate bounds within which a tab run must fit
3596            //
3597            switch(tabPlacement) {
3598              case LEFT:
3599              case RIGHT:
3600                  maxTabWidth = calculateMaxTabWidth(tabPlacement);
3601                  break;
3602              case BOTTOM:
3603              case TOP:
3604              default:
3605                  maxTabHeight = calculateMaxTabHeight(tabPlacement);
3606            }
3607
3608            runCount = 0;
3609            selectedRun = -1;
3610
3611            if (tabCount == 0) {
3612                return;
3613            }
3614
3615            selectedRun = 0;
3616            runCount = 1;
3617
3618            // Run through tabs and lay them out in a single run
3619            Rectangle rect;
3620            for (i = 0; i < tabCount; i++) {
3621                rect = rects[i];
3622
3623                if (!verticalTabRuns) {
3624                    // Tabs on TOP or BOTTOM....
3625                    if (i > 0) {
3626                        rect.x = rects[i-1].x + rects[i-1].width;
3627                    } else {
3628                        tabRuns[0] = 0;
3629                        maxTabWidth = 0;
3630                        totalHeight += maxTabHeight;
3631                        rect.x = x;
3632                    }
3633                    rect.width = calculateTabWidth(tabPlacement, i, metrics);
3634                    totalWidth = rect.x + rect.width;
3635                    maxTabWidth = Math.max(maxTabWidth, rect.width);
3636
3637                    rect.y = y;
3638                    rect.height = maxTabHeight/* - 2*/;
3639
3640                } else {
3641                    // Tabs on LEFT or RIGHT...
3642                    if (i > 0) {
3643                        rect.y = rects[i-1].y + rects[i-1].height;
3644                    } else {
3645                        tabRuns[0] = 0;
3646                        maxTabHeight = 0;
3647                        totalWidth = maxTabWidth;
3648                        rect.y = y;
3649                    }
3650                    rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3651                    totalHeight = rect.y + rect.height;
3652                    maxTabHeight = Math.max(maxTabHeight, rect.height);
3653
3654                    rect.x = x;
3655                    rect.width = maxTabWidth/* - 2*/;
3656
3657                }
3658            }
3659
3660            if (tabsOverlapBorder) {
3661                // Pad the selected tab so that it appears raised in front
3662                padSelectedTab(tabPlacement, selectedIndex);
3663            }
3664
3665            // if right to left and tab placement on the top or
3666            // the bottom, flip x positions and adjust by widths
3667            if (!leftToRight && !verticalTabRuns) {
3668                int rightMargin = size.width
3669                                  - (insets.right + tabAreaInsets.right);
3670                for (i = 0; i < tabCount; i++) {
3671                    rects[i].x = rightMargin - rects[i].x - rects[i].width;
3672                }
3673            }
3674            tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
3675            tabScroller.tabPanel.invalidate();
3676        }
3677    }
3678
3679    private class ScrollableTabSupport implements ActionListener,
3680                            ChangeListener {
3681        public ScrollableTabViewport viewport;
3682        public ScrollableTabPanel tabPanel;
3683        public JButton scrollForwardButton;
3684        public JButton scrollBackwardButton;
3685        public CroppedEdge croppedEdge;
3686        public int leadingTabIndex;
3687
3688        private Point tabViewPosition = new Point(0,0);
3689
3690        ScrollableTabSupport(int tabPlacement) {
3691            viewport = new ScrollableTabViewport();
3692            tabPanel = new ScrollableTabPanel();
3693            viewport.setView(tabPanel);
3694            viewport.addChangeListener(this);
3695            croppedEdge = new CroppedEdge();
3696            createButtons();
3697        }
3698
3699        /**
3700         * Recreates the scroll buttons and adds them to the TabbedPane.
3701         */
3702        void createButtons() {
3703            if (scrollForwardButton != null) {
3704                tabPane.remove(scrollForwardButton);
3705                scrollForwardButton.removeActionListener(this);
3706                tabPane.remove(scrollBackwardButton);
3707                scrollBackwardButton.removeActionListener(this);
3708            }
3709            int tabPlacement = tabPane.getTabPlacement();
3710            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3711                scrollForwardButton = createScrollButton(EAST);
3712                scrollBackwardButton = createScrollButton(WEST);
3713
3714            } else { // tabPlacement = LEFT || RIGHT
3715                scrollForwardButton = createScrollButton(SOUTH);
3716                scrollBackwardButton = createScrollButton(NORTH);
3717            }
3718            scrollForwardButton.addActionListener(this);
3719            scrollBackwardButton.addActionListener(this);
3720            tabPane.add(scrollForwardButton);
3721            tabPane.add(scrollBackwardButton);
3722        }
3723
3724        public void scrollForward(int tabPlacement) {
3725            Dimension viewSize = viewport.getViewSize();
3726            Rectangle viewRect = viewport.getViewRect();
3727
3728            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3729                if (viewRect.width >= viewSize.width - viewRect.x) {
3730                    return; // no room left to scroll
3731                }
3732            } else { // tabPlacement == LEFT || tabPlacement == RIGHT
3733                if (viewRect.height >= viewSize.height - viewRect.y) {
3734                    return;
3735                }
3736            }
3737            setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
3738        }
3739
3740        public void scrollBackward(int tabPlacement) {
3741            if (leadingTabIndex == 0) {
3742                return; // no room left to scroll
3743            }
3744            setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
3745        }
3746
3747        public void setLeadingTabIndex(int tabPlacement, int index) {
3748            leadingTabIndex = index;
3749            Dimension viewSize = viewport.getViewSize();
3750            Rectangle viewRect = viewport.getViewRect();
3751
3752            switch(tabPlacement) {
3753              case TOP:
3754              case BOTTOM:
3755                tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x;
3756
3757                if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
3758                    // We've scrolled to the end, so adjust the viewport size
3759                    // to ensure the view position remains aligned on a tab boundary
3760                    Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x,
3761                                                         viewRect.height);
3762                    viewport.setExtentSize(extentSize);
3763                }
3764                break;
3765              case LEFT:
3766              case RIGHT:
3767                tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
3768
3769                if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
3770                // We've scrolled to the end, so adjust the viewport size
3771                // to ensure the view position remains aligned on a tab boundary
3772                     Dimension extentSize = new Dimension(viewRect.width,
3773                                                          viewSize.height - tabViewPosition.y);
3774                     viewport.setExtentSize(extentSize);
3775                }
3776            }
3777            viewport.setViewPosition(tabViewPosition);
3778        }
3779
3780        public void stateChanged(ChangeEvent e) {
3781            updateView();
3782        }
3783
3784        private void updateView() {
3785            int tabPlacement = tabPane.getTabPlacement();
3786            int tabCount = tabPane.getTabCount();
3787            assureRectsCreated(tabCount);
3788            Rectangle vpRect = viewport.getBounds();
3789            Dimension viewSize = viewport.getViewSize();
3790            Rectangle viewRect = viewport.getViewRect();
3791
3792            leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
3793
3794            // If the tab isn't right aligned, adjust it.
3795            if (leadingTabIndex + 1 < tabCount) {
3796                switch (tabPlacement) {
3797                case TOP:
3798                case BOTTOM:
3799                    if (rects[leadingTabIndex].x < viewRect.x) {
3800                        leadingTabIndex++;
3801                    }
3802                    break;
3803                case LEFT:
3804                case RIGHT:
3805                    if (rects[leadingTabIndex].y < viewRect.y) {
3806                        leadingTabIndex++;
3807                    }
3808                    break;
3809                }
3810            }
3811            Insets contentInsets = getContentBorderInsets(tabPlacement);
3812            switch(tabPlacement) {
3813              case LEFT:
3814                  tabPane.repaint(vpRect.x+vpRect.width, vpRect.y,
3815                                  contentInsets.left, vpRect.height);
3816                  scrollBackwardButton.setEnabled(
3817                          viewRect.y > 0 && leadingTabIndex > 0);
3818                  scrollForwardButton.setEnabled(
3819                          leadingTabIndex < tabCount-1 &&
3820                          viewSize.height-viewRect.y > viewRect.height);
3821                  break;
3822              case RIGHT:
3823                  tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y,
3824                                  contentInsets.right, vpRect.height);
3825                  scrollBackwardButton.setEnabled(
3826                          viewRect.y > 0 && leadingTabIndex > 0);
3827                  scrollForwardButton.setEnabled(
3828                          leadingTabIndex < tabCount-1 &&
3829                          viewSize.height-viewRect.y > viewRect.height);
3830                  break;
3831              case BOTTOM:
3832                  tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom,
3833                                  vpRect.width, contentInsets.bottom);
3834                  scrollBackwardButton.setEnabled(
3835                          viewRect.x > 0 && leadingTabIndex > 0);
3836                  scrollForwardButton.setEnabled(
3837                          leadingTabIndex < tabCount-1 &&
3838                          viewSize.width-viewRect.x > viewRect.width);
3839                  break;
3840              case TOP:
3841              default:
3842                  tabPane.repaint(vpRect.x, vpRect.y+vpRect.height,
3843                                  vpRect.width, contentInsets.top);
3844                  scrollBackwardButton.setEnabled(
3845                          viewRect.x > 0 && leadingTabIndex > 0);
3846                  scrollForwardButton.setEnabled(
3847                          leadingTabIndex < tabCount-1 &&
3848                          viewSize.width-viewRect.x > viewRect.width);
3849            }
3850        }
3851
3852        /**
3853         * ActionListener for the scroll buttons.
3854         */
3855        public void actionPerformed(ActionEvent e) {
3856            ActionMap map = tabPane.getActionMap();
3857
3858            if (map != null) {
3859                String actionKey;
3860
3861                if (e.getSource() == scrollForwardButton) {
3862                    actionKey = "scrollTabsForwardAction";
3863                }
3864                else {
3865                    actionKey = "scrollTabsBackwardAction";
3866                }
3867                Action action = map.get(actionKey);
3868
3869                if (action != null && action.isEnabled()) {
3870                    action.actionPerformed(new ActionEvent(tabPane,
3871                        ActionEvent.ACTION_PERFORMED, null, e.getWhen(),
3872                        e.getModifiers()));
3873                }
3874            }
3875        }
3876
3877        public String toString() {
3878            return "viewport.viewSize=" + viewport.getViewSize() + "\n" +
3879                              "viewport.viewRectangle="+viewport.getViewRect()+"\n"+
3880                              "leadingTabIndex="+leadingTabIndex+"\n"+
3881                              "tabViewPosition=" + tabViewPosition;
3882        }
3883
3884    }
3885
3886    @SuppressWarnings("serial") // Superclass is not serializable across versions
3887    private class ScrollableTabViewport extends JViewport implements UIResource {
3888        public ScrollableTabViewport() {
3889            super();
3890            setName("TabbedPane.scrollableViewport");
3891            setScrollMode(SIMPLE_SCROLL_MODE);
3892            setOpaque(tabPane.isOpaque());
3893            Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3894            if (bgColor == null) {
3895                bgColor = tabPane.getBackground();
3896            }
3897            setBackground(bgColor);
3898        }
3899    }
3900
3901    @SuppressWarnings("serial") // Superclass is not serializable across versions
3902    private class ScrollableTabPanel extends JPanel implements UIResource {
3903        public ScrollableTabPanel() {
3904            super(null);
3905            setOpaque(tabPane.isOpaque());
3906            Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3907            if (bgColor == null) {
3908                bgColor = tabPane.getBackground();
3909            }
3910            setBackground(bgColor);
3911        }
3912        public void paintComponent(Graphics g) {
3913            super.paintComponent(g);
3914            BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(),
3915                                                tabPane.getSelectedIndex());
3916            if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
3917                Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
3918                g.translate(croppedRect.x, croppedRect.y);
3919                tabScroller.croppedEdge.paintComponent(g);
3920                g.translate(-croppedRect.x, -croppedRect.y);
3921            }
3922        }
3923
3924        public void doLayout() {
3925            if (getComponentCount() > 0) {
3926                Component child = getComponent(0);
3927                child.setBounds(0, 0, getWidth(), getHeight());
3928            }
3929        }
3930    }
3931
3932    @SuppressWarnings("serial") // Superclass is not serializable across versions
3933    private class ScrollableTabButton extends BasicArrowButton implements UIResource,
3934                                                                            SwingConstants {
3935        public ScrollableTabButton(int direction) {
3936            super(direction,
3937                  UIManager.getColor("TabbedPane.selected"),
3938                  UIManager.getColor("TabbedPane.shadow"),
3939                  UIManager.getColor("TabbedPane.darkShadow"),
3940                  UIManager.getColor("TabbedPane.highlight"));
3941        }
3942    }
3943
3944
3945// Controller: event listeners
3946
3947    private class Handler implements ChangeListener, ContainerListener,
3948                  FocusListener, MouseListener, MouseMotionListener,
3949                  PropertyChangeListener {
3950        //
3951        // PropertyChangeListener
3952        //
3953        public void propertyChange(PropertyChangeEvent e) {
3954            JTabbedPane pane = (JTabbedPane)e.getSource();
3955            String name = e.getPropertyName();
3956            boolean isScrollLayout = scrollableTabLayoutEnabled();
3957            if (name == "mnemonicAt") {
3958                updateMnemonics();
3959                pane.repaint();
3960            }
3961            else if (name == "displayedMnemonicIndexAt") {
3962                pane.repaint();
3963            }
3964            else if (name =="indexForTitle") {
3965                calculatedBaseline = false;
3966                Integer index = (Integer) e.getNewValue();
3967                updateHtmlViews(index, false);
3968            } else if (name == "tabLayoutPolicy") {
3969                BasicTabbedPaneUI.this.uninstallUI(pane);
3970                BasicTabbedPaneUI.this.installUI(pane);
3971                calculatedBaseline = false;
3972            } else if (name == "tabPlacement") {
3973                if (scrollableTabLayoutEnabled()) {
3974                    tabScroller.createButtons();
3975                }
3976                calculatedBaseline = false;
3977            } else if (name == "opaque" && isScrollLayout) {
3978                boolean newVal = ((Boolean)e.getNewValue()).booleanValue();
3979                tabScroller.tabPanel.setOpaque(newVal);
3980                tabScroller.viewport.setOpaque(newVal);
3981            } else if (name == "background" && isScrollLayout) {
3982                Color newVal = (Color)e.getNewValue();
3983                tabScroller.tabPanel.setBackground(newVal);
3984                tabScroller.viewport.setBackground(newVal);
3985                Color newColor = selectedColor == null ? newVal : selectedColor;
3986                tabScroller.scrollForwardButton.setBackground(newColor);
3987                tabScroller.scrollBackwardButton.setBackground(newColor);
3988            } else if (name == "indexForTabComponent") {
3989                if (tabContainer != null) {
3990                    tabContainer.removeUnusedTabComponents();
3991                }
3992                Component c = tabPane.getTabComponentAt(
3993                        (Integer)e.getNewValue());
3994                if (c != null) {
3995                    if (tabContainer == null) {
3996                        installTabContainer();
3997                    } else {
3998                        tabContainer.add(c);
3999                    }
4000                }
4001                tabPane.revalidate();
4002                tabPane.repaint();
4003                calculatedBaseline = false;
4004            } else if (name == "indexForNullComponent") {
4005                isRunsDirty = true;
4006                updateHtmlViews((Integer)e.getNewValue(), true);
4007            } else if (name == "font") {
4008                calculatedBaseline = false;
4009            }
4010        }
4011
4012        private void updateHtmlViews(int index, boolean inserted) {
4013            String title = tabPane.getTitleAt(index);
4014            boolean isHTML = BasicHTML.isHTMLString(title);
4015            if (isHTML) {
4016                if (htmlViews==null) {    // Initialize vector
4017                    htmlViews = createHTMLVector();
4018                } else {                  // Vector already exists
4019                    View v = BasicHTML.createHTMLView(tabPane, title);
4020                    setHtmlView(v, inserted, index);
4021                }
4022            } else {                             // Not HTML
4023                if (htmlViews!=null) {           // Add placeholder
4024                    setHtmlView(null, inserted, index);
4025                }                                // else nada!
4026            }
4027            updateMnemonics();
4028        }
4029
4030        private void setHtmlView(View v, boolean inserted, int index) {
4031            if (inserted || index >= htmlViews.size()) {
4032                htmlViews.insertElementAt(v, index);
4033            } else {
4034                htmlViews.setElementAt(v, index);
4035            }
4036        }
4037
4038        //
4039        // ChangeListener
4040        //
4041        public void stateChanged(ChangeEvent e) {
4042            JTabbedPane tabPane = (JTabbedPane)e.getSource();
4043            tabPane.revalidate();
4044            tabPane.repaint();
4045
4046            setFocusIndex(tabPane.getSelectedIndex(), false);
4047
4048            if (scrollableTabLayoutEnabled()) {
4049                ensureCurrentLayout();
4050                int index = tabPane.getSelectedIndex();
4051                if (index < rects.length && index != -1) {
4052                    tabScroller.tabPanel.scrollRectToVisible(
4053                            (Rectangle)rects[index].clone());
4054                }
4055            }
4056        }
4057
4058        //
4059        // MouseListener
4060        //
4061        public void mouseClicked(MouseEvent e) {
4062        }
4063
4064        public void mouseReleased(MouseEvent e) {
4065        }
4066
4067        public void mouseEntered(MouseEvent e) {
4068            setRolloverTab(e.getX(), e.getY());
4069        }
4070
4071        public void mouseExited(MouseEvent e) {
4072            setRolloverTab(-1);
4073        }
4074
4075        public void mousePressed(MouseEvent e) {
4076            if (!tabPane.isEnabled()) {
4077                return;
4078            }
4079            int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
4080            if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
4081                if (tabIndex != tabPane.getSelectedIndex()) {
4082                    // Clicking on unselected tab, change selection, do NOT
4083                    // request focus.
4084                    // This will trigger the focusIndex to change by way
4085                    // of stateChanged.
4086                    tabPane.setSelectedIndex(tabIndex);
4087                }
4088                else if (tabPane.isRequestFocusEnabled()) {
4089                    // Clicking on selected tab, try and give the tabbedpane
4090                    // focus.  Repaint will occur in focusGained.
4091                    tabPane.requestFocus();
4092                }
4093            }
4094        }
4095
4096        //
4097        // MouseMotionListener
4098        //
4099        public void mouseDragged(MouseEvent e) {
4100        }
4101
4102        public void mouseMoved(MouseEvent e) {
4103            setRolloverTab(e.getX(), e.getY());
4104        }
4105
4106        //
4107        // FocusListener
4108        //
4109        public void focusGained(FocusEvent e) {
4110           setFocusIndex(tabPane.getSelectedIndex(), true);
4111        }
4112        public void focusLost(FocusEvent e) {
4113           repaintTab(focusIndex);
4114        }
4115
4116
4117        //
4118        // ContainerListener
4119        //
4120    /* GES 2/3/99:
4121       The container listener code was added to support HTML
4122       rendering of tab titles.
4123
4124       Ideally, we would be able to listen for property changes
4125       when a tab is added or its text modified.  At the moment
4126       there are no such events because the Beans spec doesn't
4127       allow 'indexed' property changes (i.e. tab 2's text changed
4128       from A to B).
4129
4130       In order to get around this, we listen for tabs to be added
4131       or removed by listening for the container events.  we then
4132       queue up a runnable (so the component has a chance to complete
4133       the add) which checks the tab title of the new component to see
4134       if it requires HTML rendering.
4135
4136       The Views (one per tab title requiring HTML rendering) are
4137       stored in the htmlViews Vector, which is only allocated after
4138       the first time we run into an HTML tab.  Note that this vector
4139       is kept in step with the number of pages, and nulls are added
4140       for those pages whose tab title do not require HTML rendering.
4141
4142       This makes it easy for the paint and layout code to tell
4143       whether to invoke the HTML engine without having to check
4144       the string during time-sensitive operations.
4145
4146       When we have added a way to listen for tab additions and
4147       changes to tab text, this code should be removed and
4148       replaced by something which uses that.  */
4149
4150        public void componentAdded(ContainerEvent e) {
4151            JTabbedPane tp = (JTabbedPane)e.getContainer();
4152            Component child = e.getChild();
4153            if (child instanceof UIResource) {
4154                return;
4155            }
4156            isRunsDirty = true;
4157            updateHtmlViews(tp.indexOfComponent(child), true);
4158        }
4159        public void componentRemoved(ContainerEvent e) {
4160            JTabbedPane tp = (JTabbedPane)e.getContainer();
4161            Component child = e.getChild();
4162            if (child instanceof UIResource) {
4163                return;
4164            }
4165
4166            // NOTE 4/15/2002 (joutwate):
4167            // This fix is implemented using client properties since there is
4168            // currently no IndexPropertyChangeEvent.  Once
4169            // IndexPropertyChangeEvents have been added this code should be
4170            // modified to use it.
4171            Integer indexObj =
4172                (Integer)tp.getClientProperty("__index_to_remove__");
4173            if (indexObj != null) {
4174                int index = indexObj.intValue();
4175                if (htmlViews != null && htmlViews.size() > index) {
4176                    htmlViews.removeElementAt(index);
4177                }
4178                tp.putClientProperty("__index_to_remove__", null);
4179            }
4180            isRunsDirty = true;
4181            updateMnemonics();
4182
4183            validateFocusIndex();
4184        }
4185    }
4186
4187    /**
4188     * This class should be treated as a &quot;protected&quot; inner class.
4189     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4190     */
4191    public class PropertyChangeHandler implements PropertyChangeListener {
4192        // NOTE: This class exists only for backward compatibility. All
4193        // its functionality has been moved into Handler. If you need to add
4194        // new functionality add it to the Handler, but make sure this
4195        // class calls into the Handler.
4196        public void propertyChange(PropertyChangeEvent e) {
4197            getHandler().propertyChange(e);
4198        }
4199    }
4200
4201    /**
4202     * This class should be treated as a &quot;protected&quot; inner class.
4203     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4204     */
4205    public class TabSelectionHandler implements ChangeListener {
4206        // NOTE: This class exists only for backward compatibility. All
4207        // its functionality has been moved into Handler. If you need to add
4208        // new functionality add it to the Handler, but make sure this
4209        // class calls into the Handler.
4210        public void stateChanged(ChangeEvent e) {
4211            getHandler().stateChanged(e);
4212        }
4213    }
4214
4215    /**
4216     * This class should be treated as a &quot;protected&quot; inner class.
4217     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4218     */
4219    public class MouseHandler extends MouseAdapter {
4220        // NOTE: This class exists only for backward compatibility. All
4221        // its functionality has been moved into Handler. If you need to add
4222        // new functionality add it to the Handler, but make sure this
4223        // class calls into the Handler.
4224        public void mousePressed(MouseEvent e) {
4225            getHandler().mousePressed(e);
4226        }
4227    }
4228
4229    /**
4230     * This class should be treated as a &quot;protected&quot; inner class.
4231     * Instantiate it only within subclasses of BasicTabbedPaneUI.
4232     */
4233    public class FocusHandler extends FocusAdapter {
4234        // NOTE: This class exists only for backward compatibility. All
4235        // its functionality has been moved into Handler. If you need to add
4236        // new functionality add it to the Handler, but make sure this
4237        // class calls into the Handler.
4238        public void focusGained(FocusEvent e) {
4239            getHandler().focusGained(e);
4240        }
4241        public void focusLost(FocusEvent e) {
4242            getHandler().focusLost(e);
4243        }
4244    }
4245
4246    private Vector<View> createHTMLVector() {
4247        Vector<View> htmlViews = new Vector<View>();
4248        int count = tabPane.getTabCount();
4249        if (count>0) {
4250            for (int i=0 ; i<count; i++) {
4251                String title = tabPane.getTitleAt(i);
4252                if (BasicHTML.isHTMLString(title)) {
4253                    htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
4254                } else {
4255                    htmlViews.addElement(null);
4256                }
4257            }
4258        }
4259        return htmlViews;
4260    }
4261
4262    @SuppressWarnings("serial") // Superclass is not serializable across versions
4263    private class TabContainer extends JPanel implements UIResource {
4264        private boolean notifyTabbedPane = true;
4265
4266        public TabContainer() {
4267            super(null);
4268            setOpaque(false);
4269        }
4270
4271        public void remove(Component comp) {
4272            int index = tabPane.indexOfTabComponent(comp);
4273            super.remove(comp);
4274            if (notifyTabbedPane && index != -1) {
4275                tabPane.setTabComponentAt(index, null);
4276            }
4277        }
4278
4279        private void removeUnusedTabComponents() {
4280            for (Component c : getComponents()) {
4281                if (!(c instanceof UIResource)) {
4282                    int index = tabPane.indexOfTabComponent(c);
4283                    if (index == -1) {
4284                        super.remove(c);
4285                    }
4286                }
4287            }
4288        }
4289
4290        public boolean isOptimizedDrawingEnabled() {
4291            return tabScroller != null && !tabScroller.croppedEdge.isParamsSet();
4292        }
4293
4294        public void doLayout() {
4295            // We layout tabComponents in JTabbedPane's layout manager
4296            // and use this method as a hook for repainting tabs
4297            // to update tabs area e.g. when the size of tabComponent was changed
4298            if (scrollableTabLayoutEnabled()) {
4299                tabScroller.tabPanel.repaint();
4300                tabScroller.updateView();
4301            } else {
4302                tabPane.repaint(getBounds());
4303            }
4304        }
4305    }
4306
4307    @SuppressWarnings("serial") // Superclass is not serializable across versions
4308    private class CroppedEdge extends JPanel implements UIResource {
4309        private Shape shape;
4310        private int tabIndex;
4311        private int cropline;
4312        private int cropx, cropy;
4313
4314        public CroppedEdge() {
4315            setOpaque(false);
4316        }
4317
4318        public void setParams(int tabIndex, int cropline, int cropx, int cropy) {
4319            this.tabIndex = tabIndex;
4320            this.cropline = cropline;
4321            this.cropx = cropx;
4322            this.cropy = cropy;
4323            Rectangle tabRect = rects[tabIndex];
4324            setBounds(tabRect);
4325            shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline);
4326            if (getParent() == null && tabContainer != null) {
4327                tabContainer.add(this, 0);
4328            }
4329        }
4330
4331        public void resetParams() {
4332            shape = null;
4333            if (getParent() == tabContainer && tabContainer != null) {
4334                tabContainer.remove(this);
4335            }
4336        }
4337
4338        public boolean isParamsSet() {
4339            return shape != null;
4340        }
4341
4342        public int getTabIndex() {
4343            return tabIndex;
4344        }
4345
4346        public int getCropline() {
4347            return cropline;
4348        }
4349
4350        public int getCroppedSideWidth() {
4351            return 3;
4352        }
4353
4354        private Color getBgColor() {
4355            Component parent = tabPane.getParent();
4356            if (parent != null) {
4357                Color bg = parent.getBackground();
4358                if (bg != null) {
4359                    return bg;
4360                }
4361            }
4362            return UIManager.getColor("control");
4363        }
4364
4365        protected void paintComponent(Graphics g) {
4366            super.paintComponent(g);
4367            if (isParamsSet() && g instanceof Graphics2D) {
4368                Graphics2D g2 = (Graphics2D) g;
4369                g2.clipRect(0, 0, getWidth(), getHeight());
4370                g2.setColor(getBgColor());
4371                g2.translate(cropx, cropy);
4372                g2.fill(shape);
4373                paintCroppedTabEdge(g);
4374                g2.translate(-cropx, -cropy);
4375            }
4376        }
4377    }
4378}
4379