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