LoboEvolution/LoboEvolution

View on GitHub
LoboJTattoo/src/main/java/com/jtattoo/plaf/BaseTabbedPaneUI.java

Summary

Maintainability
F
1 mo
Test Coverage
/*
 * MIT License
 *
 * Copyright (c) 2014 - 2024 LoboEvolution
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Contact info: ivan.difrancesco@yahoo.it
 */

package com.jtattoo.plaf;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.*;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTabbedPane;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentInputMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.text.View;

/**
 * This class is a modified copy of the javax.swing.plaf.basic.BasicTabbedPaneUI
 * A Basic L&F implementation of TabbedPaneUI.
 *
 * @version 1.87 06/08/99
 * Author Amy Fowler
 * Author Philip Milne
 * Author Steve Wilson
 * Author Tom Santos
 * Author Dave Moore
 * Author Michael Hagen
 */
@Slf4j
public class BaseTabbedPaneUI extends TabbedPaneUI implements SwingConstants {

    @EqualsAndHashCode(callSuper = true)
    @Data
    public class ArrowButton extends JButton implements SwingConstants {

        /**
         *
         */
        private static final long serialVersionUID = 1L;
        protected int direction;

        public ArrowButton(final int direction) {
            super();
            this.direction = direction;
            setRequestFocusEnabled(false);
            if (simpleButtonBorder) {
                final Color cLo = getLoBorderColor(0);
                final Color cHi = AbstractLookAndFeel.getTheme().getControlHighlight();
                setBorder(BorderFactory.createEtchedBorder(cHi, cLo));
            }
        }

        @Override
        public Dimension getMaximumSize() {
            return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
        }

        @Override
        public Dimension getMinimumSize() {
            return new Dimension(5, 5);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(17, 17);
        }

        @Override
        public void paint(final Graphics g) {
            super.paint(g);
            // Draw the arrow
            final int w = getSize().width;
            final int h = getSize().height;
            int size = Math.min((h - 4) / 3, (w - 4) / 3);
            size = Math.max(size, 2);
            paintTriangle(g, (w - size) / 2 + 1, (h - size) / 2 + 1, size);
        }

        public void paintTriangle(final Graphics g, final int x, final int y, final int sizeTriangle) {
            final Color oldColor = g.getColor();
            final int mid;
            int i;
            int j;
            final int size = Math.max(sizeTriangle, 2);
            mid = size / 2 - 1;

            final Color enabledColor = AbstractLookAndFeel.getTheme().getButtonForegroundColor();
            final Color disabledColor = AbstractLookAndFeel.getTheme().getDisabledForegroundColor();

            g.translate(x, y);
            if (isEnabled()) {
                g.setColor(enabledColor);
            } else {
                g.setColor(disabledColor);
            }

            switch (direction) {
            case NORTH:
                for (i = 0; i < size; i++) {
                    g.drawLine(mid - i, i, mid + i, i);
                }
                break;
            case SOUTH:
                j = 0;
                for (i = size - 1; i >= 0; i--) {
                    g.drawLine(mid - i, j, mid + i, j);
                    j++;
                }
                break;
            case WEST:
                for (i = 0; i < size; i++) {
                    g.drawLine(i, mid - i, i, mid + i);
                }
                break;
            case EAST:
                j = 0;
                for (i = size - 1; i >= 0; i--) {
                    g.drawLine(j, mid - i, j, mid + i);
                    j++;
                }
                break;
            default:
                break;
            }
            g.translate(-x, -y);
            g.setColor(oldColor);
        }

        public void setDirection(final int dir) {
            direction = dir;
        }
    }

    /*
     * GES 2/3/99: The container listener code was added to support HTML rendering
     * of tab titles.
     * 
     * Ideally, we would be able to listen for property changes when a tab is added
     * or its text modified. At the moment there are no such events because the
     * Beans spec doesn't allow 'indexed' property changes (i.e. tab 2's text
     * changed from A to B).
     * 
     * In order to get around this, we listen for tabs to be added or removed by
     * listening for the container events. we then queue up a runnable (so the
     * component has a chance to complete the add) which checks the tab title of the
     * new component to see if it requires HTML rendering.
     * 
     * The Views (one per tab title requiring HTML rendering) are stored in the
     * htmlViews list, which is only allocated after the first time we run into an
     * HTML tab. Note that this list is kept in step with the number of pages, and
     * nulls are added for those pages whose tab title do not require HTML
     * rendering.
     * 
     * This makes it easy for the paint and layout code to tell whether to invoke
     * the HTML engine without having to check the string during time-sensitive
     * operations.
     * 
     * When we have added a way to listen for tab additions and changes to tab text,
     * this code should be removed and replaced by something which uses that.
     */
    private final class ContainerHandler implements ContainerListener {

        @Override
        public void componentAdded(final ContainerEvent e) {
            final JTabbedPane tp = (JTabbedPane) e.getContainer();
            final TabbedPaneLayout layout = (TabbedPaneLayout) tp.getLayout();
            layout.layoutContainer(tp);

            final Component child = e.getChild();
            if (child instanceof UIResource) {
                return;
            }
            final int index = tp.indexOfComponent(child);
            final String title = tp.getTitleAt(index);
            final boolean isHTML = BasicHTML.isHTMLString(title);
            if (isHTML) {
                if (htmlViews == null) {
                    // Initialize vector
                    htmlViews = createHTMLViewList();
                } else {
                    // Vector already exists
                    final View v = BasicHTML.createHTMLView(tp, title);
                    htmlViews.add(index, v);
                }
            } else {
                // Not HTML
                if (htmlViews != null) {
                    // Add placeholder
                    htmlViews.add(index, null);
                } // else nada!
            }
        }

        @Override
        public void componentRemoved(final ContainerEvent e) {
            final JTabbedPane tp = (JTabbedPane) e.getContainer();
            final Component child = e.getChild();
            if (child instanceof UIResource) {
                return;
            }

            // NOTE 4/15/2002 (joutwate):
            // This fix is implemented using client properties since there is
            // currently no IndexPropertyChangeEvent. Once
            // IndexPropertyChangeEvents have been added this code should be
            // modified to use it.
            final Integer indexObj = (Integer) tp.getClientProperty("__index_to_remove__");
            if (indexObj != null) {
                if (htmlViews != null && htmlViews.size() >= indexObj) {
                    htmlViews.remove(indexObj);
                }
            }
        }
    }

    private final static class DownAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            ui.navigateSelectedTab(SOUTH);
        }
    }

    /**
     * This inner class is marked &quot;public&quot; due to a compiler bug. This
     * class should be treated as a &quot;protected&quot; inner class. Instantiate
     * it only within subclasses of BaseTabbedPaneUI.
     */
    public class FocusHandler extends FocusAdapter {

        @Override
        public void focusGained(final FocusEvent e) {
            final JTabbedPane tabPane = (JTabbedPane) e.getSource();
            final int tabCount = tabPane.getTabCount();
            final int selectedIndex = tabPane.getSelectedIndex();
            if (selectedIndex != -1 && tabCount > 0 && tabCount == rects.length) {
                tabPane.repaint(getTabBounds(tabPane, selectedIndex));
            }
        }

        @Override
        public void focusLost(final FocusEvent e) {
            final JTabbedPane tabPane = (JTabbedPane) e.getSource();
            final int tabCount = tabPane.getTabCount();
            final int selectedIndex = tabPane.getSelectedIndex();
            if (selectedIndex != -1 && tabCount > 0 && tabCount == rects.length) {
                tabPane.repaint(getTabBounds(tabPane, selectedIndex));
            }
        }
    }

    private final static class LeftAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            ui.navigateSelectedTab(WEST);
        }
    }

    /**
     * This inner class is marked &quot;public&quot; due to a compiler bug. This
     * class should be treated as a &quot;protected&quot; inner class. Instantiate
     * it only within subclasses of BaseTabbedPaneUI.
     */
    public class MouseHandler extends MouseAdapter {

        @Override
        public void mouseClicked(final MouseEvent e) {
            if (scrollableTabLayoutEnabled()) {
                final MouseListener[] ml = tabPane.getMouseListeners();
                for (final MouseListener ml1 : ml) {
                    ml1.mouseClicked(e);
                }
            }
        }

        @Override
        public void mouseEntered(final MouseEvent e) {
            if (scrollableTabLayoutEnabled()) {
                for (final MouseListener ml : tabPane.getMouseListeners()) {
                    ml.mouseEntered(e);
                }
            }
        }

        @Override
        public void mouseExited(final MouseEvent e) {
            if (scrollableTabLayoutEnabled()) {
                for (final MouseListener ml : tabPane.getMouseListeners()) {
                    ml.mouseExited(e);
                }
            }
            rolloverIndex = -1;
            if (rolloverIndex != oldRolloverIndex) {
                if (oldRolloverIndex >= 0 && oldRolloverIndex < tabPane.getTabCount()) {
                    tabPane.repaint(getTabBounds(tabPane, oldRolloverIndex));
                }
                if (rolloverIndex >= 0 && rolloverIndex < tabPane.getTabCount()) {
                    tabPane.repaint(getTabBounds(tabPane, rolloverIndex));
                }
                oldRolloverIndex = rolloverIndex;
            }
        }

        @Override
        public void mousePressed(final MouseEvent e) {
            if (scrollableTabLayoutEnabled()) {
                for (final MouseListener ml : tabPane.getMouseListeners()) {
                    ml.mousePressed(e);
                }
            }
            if (!tabPane.isEnabled()) {
                return;
            }
            final int tabIndex = getTabAtLocation(e.getX(), e.getY());
            if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
                if (tabIndex == tabPane.getSelectedIndex()) {
                    if (tabPane.isRequestFocusEnabled()) {
                        tabPane.requestFocus();
                        tabPane.repaint(getTabBounds(tabPane, tabIndex));
                    }
                } else {
                    tabPane.setSelectedIndex(tabIndex);
                }
            }
        }

        @Override
        public void mouseReleased(final MouseEvent e) {
            if (scrollableTabLayoutEnabled()) {
                for (final MouseListener ml : tabPane.getMouseListeners()) {
                    ml.mouseReleased(e);
                }
            }
        }
    }

    /**
     * This inner class is marked &quot;public&quot; due to a compiler bug. This
     * class should be treated as a &quot;protected&quot; inner class. Instantiate
     * it only within subclasses of BaseTabbedPaneUI.
     */
    public class MouseMotionHandler extends MouseMotionAdapter {

        @Override
        public void mouseDragged(final MouseEvent e) {
            if (scrollableTabLayoutEnabled()) {
                for (final MouseMotionListener mml : tabPane.getMouseMotionListeners()) {
                    mml.mouseDragged(e);
                }
            }
        }

        @Override
        public void mouseMoved(final MouseEvent e) {
            if (scrollableTabLayoutEnabled()) {
                for (final MouseMotionListener mml : tabPane.getMouseMotionListeners()) {
                    mml.mouseMoved(e);
                }
            }
            rolloverIndex = getTabAtLocation(e.getX(), e.getY());
            if (rolloverIndex != oldRolloverIndex) {
                if (oldRolloverIndex >= 0 && oldRolloverIndex < tabPane.getTabCount()) {
                    tabPane.repaint(getTabBounds(tabPane, oldRolloverIndex));
                }
                if (rolloverIndex >= 0 && rolloverIndex < tabPane.getTabCount()) {
                    tabPane.repaint(getTabBounds(tabPane, rolloverIndex));
                }
                oldRolloverIndex = rolloverIndex;
            }
        }
    }

    public class MyTabComponentListener implements PropertyChangeListener {

        @Override
        public void propertyChange(final PropertyChangeEvent evt) {
            if ("font".equals(evt.getPropertyName()) || "text".equals(evt.getPropertyName())) {
                tabPane.revalidate();
                tabPane.repaint();
            }
        }
    }

    private final static class NextAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            ui.navigateSelectedTab(NEXT);
        }
    }

    private final static class PageDownAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            final int tabPlacement = pane.getTabPlacement();
            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                ui.navigateSelectedTab(EAST);
            } else {
                ui.navigateSelectedTab(SOUTH);
            }
        }
    }

    private final static class PageUpAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            final int tabPlacement = pane.getTabPlacement();
            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                ui.navigateSelectedTab(WEST);
            } else {
                ui.navigateSelectedTab(NORTH);
            }
        }
    }

    private final static class PreviousAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            ui.navigateSelectedTab(PREVIOUS);
        }
    }

    /**
     * This inner class is marked &quot;public&quot; due to a compiler bug. This
     * class should be treated as a &quot;protected&quot; inner class. Instantiate
     * it only within subclasses of BaseTabbedPaneUI.
     */
    public class PropertyChangeHandler implements PropertyChangeListener {

        @Override
        public void propertyChange(final PropertyChangeEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final String name = e.getPropertyName();
            final boolean isScrollLayout = scrollableTabLayoutEnabled();
            if ("mnemonicAt".equals(name)) {
                updateMnemonics();
                pane.repaint();
            } else if ("displayedMnemonicIndexAt".equals(name)) {
                pane.repaint();
            } else if ("indexForTitle".equals(name)) {
                final int index = (Integer) e.getNewValue();
                final String title = tabPane.getTitleAt(index);
                if (BasicHTML.isHTMLString(title)) {
                    if (htmlViews == null) { // Initialize vector
                        htmlViews = createHTMLViewList();
                    } else { // Vector already exists
                        final View v = BasicHTML.createHTMLView(tabPane, title);
                        htmlViews.set(index, v);
                    }
                } else {
                    if (htmlViews != null && htmlViews.get(index) != null) {
                        htmlViews.set(index, null);
                    }
                }
                updateMnemonics();
            } else if ("tabLayoutPolicy".equals(name)) {
                BaseTabbedPaneUI.this.uninstallUI(pane);
                BaseTabbedPaneUI.this.installUI(pane);
            } else if ("background".equals(name) && isScrollLayout) {
                final Color newVal = (Color) e.getNewValue();
                tabScroller.tabPanel.setBackground(newVal);
                tabScroller.viewport.setBackground(newVal);
                final Color newColor = selectedColor == null ? newVal : selectedColor;
                tabScroller.scrollForwardButton.setBackground(newColor);
                tabScroller.scrollBackwardButton.setBackground(newColor);
            } else if ("indexForTabComponent".equals(name)) {
                if (tabContainer != null) {
                    tabContainer.removeUnusedTabComponents();
                }
                try {
                    final Component tabComponent = getTabComponentAt((Integer) e.getNewValue());
                    if (tabComponent != null) {
                        if (tabContainer == null) {
                            installTabContainer();
                        } else {
                            addMyPropertyChangeListeners(tabComponent);
                            tabContainer.add(tabComponent);
                        }
                    }
                } catch (final Exception ex) {
                    log.info(ex.getMessage());
                }
                tabPane.revalidate();
                tabPane.repaint();
            } else if ("componentOrientation".equals(name)) {
                pane.revalidate();
                pane.repaint();
            } else if ("tabAreaBackground".equals(name)) {
                pane.revalidate();
                pane.repaint();
            }
        }
    }

    private final static class RequestFocusAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            pane.requestFocus();
        }
    }

    private final static class RequestFocusForVisibleAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            ui.requestFocusForVisibleComponent();
        }
    }

    private final static class RightAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            ui.navigateSelectedTab(EAST);
        }
    }

    private final class ScrollablePopupMenuTabButton extends ArrowButton implements UIResource, SwingConstants {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        public ScrollablePopupMenuTabButton() {
            super(SOUTH);
        }
    }

    private final class ScrollableTabButton extends ArrowButton implements UIResource, SwingConstants {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        public ScrollableTabButton(final int direction) {
            super(direction);
        }

        public boolean scrollsForward() {
            return direction == EAST || direction == SOUTH;
        }
    }

    private final class ScrollableTabPanel extends JPanel implements UIResource {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        public ScrollableTabPanel() {
            setLayout(null);
            setOpaque(false);
        }

        @Override
        public void doLayout() {
            if (getComponentCount() > 0) {
                final Component child = getComponent(0);
                child.setBounds(0, 0, getWidth(), getHeight());
            }
        }

        @Override
        public void paintComponent(final Graphics g) {
            super.paintComponent(g);
            paintScrollContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex(), 0, 0, getWidth(),
                    getHeight());
            paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
        }
    }

    private final class ScrollableTabSupport implements ChangeListener {

        public final ScrollableTabViewport viewport;
        public final ScrollableTabPanel tabPanel;
        public final ScrollableTabButton scrollForwardButton;
        public final ScrollableTabButton scrollBackwardButton;
        public final ScrollablePopupMenuTabButton popupMenuButton;
        public int leadingTabIndex;
        private final Point tabViewPosition = new Point(0, 0);

        ScrollableTabSupport(final int tabPlacement) {
            viewport = new ScrollableTabViewport();
            tabPanel = new ScrollableTabPanel();

            viewport.setView(tabPanel);
            viewport.addChangeListener(this);

            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                scrollForwardButton = new ScrollableTabButton(EAST);
                scrollBackwardButton = new ScrollableTabButton(WEST);

            } else { // tabPlacement = LEFT || RIGHT
                scrollForwardButton = new ScrollableTabButton(SOUTH);
                scrollBackwardButton = new ScrollableTabButton(NORTH);
            }
            popupMenuButton = new ScrollablePopupMenuTabButton();
        }

        public void scrollBackward(final int tabPlacement) {
            if (leadingTabIndex == 0) {
                return; // no room left to scroll
            }
            setLeadingTabIndex(tabPlacement, leadingTabIndex - 1);
            if (tabPane != null) {
                tabPane.doLayout();
            }
        }

        public void scrollForward(final int tabPlacement) {
            final Dimension viewSize = viewport.getViewSize();
            final Rectangle viewRect = viewport.getViewRect();

            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                if (viewRect.width >= viewSize.width - viewRect.x) {
                    return; // no room left to scroll
                }
            } else {
                // tabPlacement == LEFT || tabPlacement == RIGHT
                if (viewRect.height >= viewSize.height - viewRect.y) {
                    return;
                }
            }
            setLeadingTabIndex(tabPlacement, leadingTabIndex + 1);
            if (tabPane != null) {
                tabPane.doLayout();
            }
        }

        public void scrollTabToVisible(final int tabPlacement, final int index) {
            if (index <= leadingTabIndex) {
                setLeadingTabIndex(tabPlacement, index);
            } else {
                final Rectangle viewRect = viewport.getViewRect();
                switch (tabPlacement) {
                case TOP:
                case BOTTOM: {
                    int i = index;
                    int x = viewRect.width - rects[index].width;
                    while (i > 0 && x - rects[i - 1].width >= 0) {
                        i--;
                        x -= rects[i].width;
                    }
                    if (leadingTabIndex < i) {
                        setLeadingTabIndex(tabPlacement, i);
                    }
                    break;

                }
                case LEFT:
                case RIGHT: {
                    int i = index;
                    int y = viewRect.height - rects[index].height;
                    while (i > 0 && y - rects[i - 1].height > 0) {
                        i--;
                        y -= rects[i].height;
                    }
                    if (leadingTabIndex < i) {
                        setLeadingTabIndex(tabPlacement, i);
                    }
                    break;
                }
                default:
                    break;
                }
            }
        }

        public void setLeadingTabIndex(final int tabPlacement, final int index) {
            leadingTabIndex = index;
            final Dimension viewSize = viewport.getViewSize();
            final Rectangle viewRect = viewport.getViewRect();

            switch (tabPlacement) {
            case TOP:
            case BOTTOM:
                tabViewPosition.x = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].x;

                if (viewSize.width - tabViewPosition.x < viewRect.width) {
                    // We've scrolled to the end, so adjust the viewport size
                    // to ensure the view position remains aligned on a tab boundary
                    final Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x, viewRect.height);
                    viewport.setExtentSize(extentSize);
                }
                break;
            case LEFT:
            case RIGHT:
                tabViewPosition.y = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].y;

                if (viewSize.height - tabViewPosition.y < viewRect.height) {
                    // We've scrolled to the end, so adjust the viewport size
                    // to ensure the view position remains aligned on a tab boundary
                    final Dimension extentSize = new Dimension(viewRect.width, viewSize.height - tabViewPosition.y);
                    viewport.setExtentSize(extentSize);
                }
            }
            viewport.setViewPosition(tabViewPosition);
        }

        @Override
        public void stateChanged(final ChangeEvent e) {
            final JViewport vp = (JViewport) e.getSource();
            final int tabPlacement = tabPane.getTabPlacement();
            final int tc = tabPane.getTabCount();
            final Rectangle vpRect = vp.getBounds();
            final Dimension viewSize = vp.getViewSize();
            final Rectangle viewRect = vp.getViewRect();

            leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
            if (leadingTabIndex >= rects.length) {
                return;
            }

            // If the tab isn't right aligned, adjust it.
            if (leadingTabIndex + 1 < tc) {
                switch (tabPlacement) {
                case TOP:
                case BOTTOM:
                    if (rects[leadingTabIndex].x < viewRect.x) {
                        leadingTabIndex++;
                    }
                    break;
                case LEFT:
                case RIGHT:
                    if (rects[leadingTabIndex].y < viewRect.y) {
                        leadingTabIndex++;
                    }
                    break;
                }
            }
            final Insets contentInsets = getContentBorderInsets(tabPlacement);
            switch (tabPlacement) {
            case LEFT:
                tabPane.repaint(vpRect.x + vpRect.width, vpRect.y, contentInsets.left, vpRect.height);
                scrollBackwardButton.setEnabled(viewRect.y > 0);
                scrollForwardButton
                        .setEnabled(leadingTabIndex < tc - 1 && viewSize.height - viewRect.y > viewRect.height);
                break;
            case RIGHT:
                tabPane.repaint(vpRect.x - contentInsets.right, vpRect.y, contentInsets.right, vpRect.height);
                scrollBackwardButton.setEnabled(viewRect.y > 0);
                scrollForwardButton
                        .setEnabled(leadingTabIndex < tc - 1 && viewSize.height - viewRect.y > viewRect.height);
                break;
            case BOTTOM:
                tabPane.repaint(vpRect.x, vpRect.y - contentInsets.bottom, vpRect.width, contentInsets.bottom);
                scrollBackwardButton.setEnabled(viewRect.x > 0);
                scrollForwardButton
                        .setEnabled(leadingTabIndex < tc - 1 && viewSize.width - viewRect.x > viewRect.width);
                break;
            case TOP:
            default:
                tabPane.repaint(vpRect.x, vpRect.y + vpRect.height, vpRect.width, contentInsets.top);
                scrollBackwardButton.setEnabled(viewRect.x > 0);
                scrollForwardButton
                        .setEnabled(leadingTabIndex < tc - 1 && viewSize.width - viewRect.x > viewRect.width);
            }
        }

    }

    private final static class ScrollableTabViewport extends JViewport implements UIResource {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        public ScrollableTabViewport() {
            setScrollMode(SIMPLE_SCROLL_MODE);
            setOpaque(false);
        }

    }

    private final static class ScrollTabsBackwardAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane;
            final Object src = e.getSource();
            if (src instanceof JTabbedPane) {
                pane = (JTabbedPane) src;
            } else if (src instanceof ScrollableTabButton) {
                pane = (JTabbedPane) ((ScrollableTabButton) src).getParent();
            } else {
                return; // shouldn't happen
            }
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();

            if (ui.scrollableTabLayoutEnabled()) {
                ui.tabScroller.scrollBackward(pane.getTabPlacement());
            }
        }
    }

    private final static class ScrollTabsForwardAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane;
            final Object src = e.getSource();
            if (src instanceof JTabbedPane) {
                pane = (JTabbedPane) src;
            } else if (src instanceof ScrollableTabButton) {
                pane = (JTabbedPane) ((ScrollableTabButton) src).getParent();
            } else {
                return; // shouldn't happen
            }
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();

            if (ui.scrollableTabLayoutEnabled()) {
                ui.tabScroller.scrollForward(pane.getTabPlacement());
            }
        }
    }

    private final static class ScrollTabsPopupMenuAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            JTabbedPane tabbedPane = null;
            final Object src = e.getSource();
            if (src instanceof JTabbedPane) {
                tabbedPane = (JTabbedPane) src;
            } else if (src instanceof ScrollablePopupMenuTabButton) {
                tabbedPane = (JTabbedPane) ((ScrollablePopupMenuTabButton) src).getParent();
            } else {
                return; // shouldn't happen
            }
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) tabbedPane.getUI();
            if (ui.scrollableTabLayoutEnabled()) {
                final JPopupMenu popup = new JPopupMenu();
                for (int i = 0; i < tabbedPane.getTabCount(); i++) {
                    final JMenuItem item = new JMenuItem(tabbedPane.getTitleAt(i));
                    item.addActionListener(new ScrollTabsPopupMenuItemAction(tabbedPane, i));
                    item.setEnabled(tabbedPane.isEnabledAt(i));
                    popup.add(item);
                }
                popup.show(ui.tabScroller.popupMenuButton, 0, 0);
                if (ui.tabScroller.popupMenuButton.isShowing()) {
                    final Point pt = ui.tabScroller.popupMenuButton.getLocationOnScreen();
                    final int x = -popup.getWidth() + ui.tabScroller.popupMenuButton.getWidth();
                    final int y = ui.tabScroller.popupMenuButton.getHeight() - 1;
                    popup.setLocation(pt.x + x, pt.y + y);
                }
            }
        }
    }

    private final static class ScrollTabsPopupMenuItemAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;
        private JTabbedPane tabbedPane = null;
        private int selectIndex = 0;

        public ScrollTabsPopupMenuItemAction(final JTabbedPane pane, final int index) {
            tabbedPane = pane;
            selectIndex = index;
        }

        @Override
        public void actionPerformed(final ActionEvent e) {
            tabbedPane.setSelectedIndex(selectIndex);
        }
    }

    /**
     * Selects a tab in the JTabbedPane based on the String of the action command.
     * The tab selected is based on the first tab that has a mnemonic matching the
     * first character of the action command.
     */
    private final static class SetSelectedIndexAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();

            if (pane != null && pane.getUI() instanceof BaseTabbedPaneUI) {
                final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
                final String command = e.getActionCommand();

                if (command != null && command.length() > 0) {
                    int mnemonic = e.getActionCommand().charAt(0);
                    if (mnemonic >= 'a' && mnemonic <= 'z') {
                        mnemonic -= 'a' - 'A';
                    }
                    final Integer index = ui.mnemonicToIndexMap.get(mnemonic);
                    if (index != null && pane.isEnabledAt(index)) {
                        pane.setSelectedIndex(index);
                    }
                }
            }
        }
    }

    /**
     * This inner class is marked &quot;public&quot; due to a compiler bug. This
     * class should be treated as a &quot;protected&quot; inner class. Instantiate
     * it only within subclasses of BaseTabbedPaneUI.
     */
    public class TabbedPaneLayout implements LayoutManager {

        @Override
        public void addLayoutComponent(final String name, final Component comp) {
        }

        public void calculateLayoutInfo() {
            final int tc = tabPane.getTabCount();
            assureRectsCreated(tc);
            calculateTabRects(tabPane.getTabPlacement(), tc);
        }

        protected Dimension calculateSize(final boolean minimum) {
            final int tabPlacement = tabPane.getTabPlacement();
            final Insets insets = tabPane.getInsets();
            final Insets contentInsets = getContentBorderInsets(tabPlacement);
            final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);

            // Dimension zeroSize = new Dimension(0, 0);
            int height = contentInsets.top + contentInsets.bottom;
            int width = contentInsets.left + contentInsets.right;
            int cWidth = 0;
            int cHeight = 0;

            // Determine minimum size required to display largest
            // child in each dimension
            //
            for (int i = 0; i < tabPane.getTabCount(); i++) {
                final Component component = tabPane.getComponentAt(i);
                if (component != null) {
                    final Dimension size = minimum ? component.getMinimumSize() : component.getPreferredSize();
                    if (size != null) {
                        cHeight = Math.max(size.height, cHeight);
                        cWidth = Math.max(size.width, cWidth);
                    }
                }
            }
            // Add content border insets to minimum size
            width += cWidth;
            height += cHeight;
            final int tabExtent;

            // Calculate how much space the tabs will need, based on the
            // minimum size required to display largest child + content border
            //
            switch (tabPlacement) {
            case LEFT:
            case RIGHT:
                height = Math.max(height,
                        calculateMaxTabHeight(tabPlacement) + tabAreaInsets.top + tabAreaInsets.bottom);
                tabExtent = preferredTabAreaWidth(tabPlacement, height);
                width += tabExtent;
                break;
            case TOP:
            case BOTTOM:
            default:
                width = Math.max(width, calculateMaxTabWidth(tabPlacement) + tabAreaInsets.left + tabAreaInsets.right);
                tabExtent = preferredTabAreaHeight(tabPlacement, width);
                height += tabExtent;
            }
            return new Dimension(width + insets.left + insets.right, height + insets.bottom + insets.top);
        }

        protected void calculateTabRects(final int tabPlacement, final int tabCount) {
            final FontMetrics fm = getFontMetrics();
            final Dimension size = tabPane.getSize();
            final Insets insets = tabPane.getInsets();
            final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
            final int fontHeight = fm.getHeight();
            final int selectedIndex = tabPane.getSelectedIndex();
            final int tabRunOverlay;
            int i, j;
            int x, y;
            final int returnAt;
            final boolean verticalTabRuns = tabPlacement == LEFT || tabPlacement == RIGHT;
            final boolean leftToRight = JTattooUtilities.isLeftToRight(tabPane);

            //
            // Calculate bounds within which a tab run must fit
            //
            switch (tabPlacement) {
            case LEFT:
                maxTabWidth = calculateMaxTabWidth(tabPlacement);
                x = insets.left + tabAreaInsets.left;
                y = insets.top + tabAreaInsets.top;
                returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
                break;
            case RIGHT:
                maxTabWidth = calculateMaxTabWidth(tabPlacement);
                x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
                y = insets.top + tabAreaInsets.top;
                returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
                break;
            case BOTTOM:
                maxTabHeight = calculateMaxTabHeight(tabPlacement);
                x = insets.left + tabAreaInsets.left;
                y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
                returnAt = size.width - (insets.right + tabAreaInsets.right);
                break;
            case TOP:
            default:
                maxTabHeight = calculateMaxTabHeight(tabPlacement);
                x = insets.left + tabAreaInsets.left;
                y = insets.top + tabAreaInsets.top;
                returnAt = size.width - (insets.right + tabAreaInsets.right);
                break;
            }

            tabRunOverlay = getTabRunOverlay(tabPlacement);

            runCount = 0;
            selectedRun = -1;

            if (tabCount == 0) {
                return;
            }

            // Run through tabs and partition them into runs
            Rectangle rect;
            for (i = 0; i < tabCount; i++) {
                rect = rects[i];

                if (!verticalTabRuns) {
                    // Tabs on TOP or BOTTOM....
                    if (i > 0) {
                        rect.x = rects[i - 1].x + rects[i - 1].width;
                    } else {
                        tabRuns[0] = 0;
                        runCount = 1;
                        maxTabWidth = 0;
                        rect.x = x;
                    }
                    rect.width = calculateTabWidth(tabPlacement, i, fm);
                    maxTabWidth = Math.max(maxTabWidth, rect.width);

                    // Never move a TAB down a run if it is in the first column.
                    // Even if there isn't enough room, moving it to a fresh
                    // line won't help.
                    if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
                        if (runCount > tabRuns.length - 1) {
                            expandTabRunsArray();
                        }
                        tabRuns[runCount] = i;
                        runCount++;
                        rect.x = x;
                    }
                    // Initialize y position in case there's just one run
                    rect.y = y;
                    rect.height = maxTabHeight/* - 2 */;

                } else {
                    // Tabs on LEFT or RIGHT...
                    if (i > 0) {
                        rect.y = rects[i - 1].y + rects[i - 1].height;
                    } else {
                        tabRuns[0] = 0;
                        runCount = 1;
                        maxTabHeight = 0;
                        rect.y = y;
                    }
                    rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
                    maxTabHeight = Math.max(maxTabHeight, rect.height);

                    // Never move a TAB over a run if it is in the first run.
                    // Even if there isn't enough room, moving it to a fresh
                    // column won't help.
                    if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
                        if (runCount > tabRuns.length - 1) {
                            expandTabRunsArray();
                        }
                        tabRuns[runCount] = i;
                        runCount++;
                        rect.y = y;
                    }
                    // Initialize x position in case there's just one column
                    rect.x = x;
                    rect.width = maxTabWidth/* - 2 */;

                }
                if (i == selectedIndex) {
                    selectedRun = runCount - 1;
                }
            }

            if (runCount > 1) {
                // Re-distribute tabs in case last run has leftover space
                normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns ? y : x, returnAt);

                selectedRun = getRunForTab(tabCount, selectedIndex);

                // Rotate run array so that selected run is first
                if (shouldRotateTabRuns(tabPlacement)) {
                    rotateTabRuns(tabPlacement, selectedRun);
                }
            }

            // Step through runs from back to front to calculate
            // tab y locations and to pad runs appropriately
            for (i = runCount - 1; i >= 0; i--) {
                final int start = tabRuns[i];
                final int next = tabRuns[i == runCount - 1 ? 0 : i + 1];
                final int end = next != 0 ? next - 1 : tabCount - 1;
                if (!verticalTabRuns) {
                    for (j = start; j <= end; j++) {
                        rect = rects[j];
                        rect.y = y;
                        rect.x += getTabRunIndent(tabPlacement, i);
                    }
                    if (shouldPadTabRun(tabPlacement, i)) {
                        padTabRun(tabPlacement, start, end, returnAt);
                    }
                    if (tabPlacement == BOTTOM) {
                        y -= maxTabHeight - tabRunOverlay;
                    } else {
                        y += maxTabHeight - tabRunOverlay;
                    }
                } else {
                    for (j = start; j <= end; j++) {
                        rect = rects[j];
                        rect.x = x;
                        rect.y += getTabRunIndent(tabPlacement, i);
                    }
                    if (shouldPadTabRun(tabPlacement, i)) {
                        padTabRun(tabPlacement, start, end, returnAt);
                    }
                    if (tabPlacement == RIGHT) {
                        x -= maxTabWidth - tabRunOverlay;
                    } else {
                        x += maxTabWidth - tabRunOverlay;
                    }
                }
            }

            // Pad the selected tab so that it appears raised in front
            padSelectedTab(tabPlacement, selectedIndex);

            // if right to left and tab placement on the top or
            // the bottom, flip x positions and adjust by widths
            if (!leftToRight && !verticalTabRuns) {
                final int rightMargin = size.width - (insets.right + tabAreaInsets.right);
                for (i = 0; i < tabCount; i++) {
                    rects[i].x = rightMargin - rects[i].x - rects[i].width;
                }
            }
        }

        @Override
        public void layoutContainer(final Container parent) {
            /*
             * Some of the code in this method deals with changing the visibility of
             * components to hide and show the contents for the selected tab. This is older
             * code that has since been duplicated in JTabbedPane.fireStateChanged(), so as
             * to allow visibility changes to happen sooner (see the note there). This code
             * remains for backward compatibility as there are some cases, such as
             * subclasses that don't fireStateChanged() where it may be used. Any changes
             * here need to be kept in synch with JTabbedPane.fireStateChanged().
             */

            final int tabPlacement = tabPane.getTabPlacement();
            final Insets insets = tabPane.getInsets();
            final int selectedIndex = tabPane.getSelectedIndex();
            final Component visibleComponent = getVisibleComponent();

            calculateLayoutInfo();

            Component selectedComponent = null;
            if (selectedIndex < 0) {
                if (visibleComponent != null) {
                    // The last tab was removed, so remove the component
                    setVisibleComponent(null);
                }
            } else {
                try {
                    selectedComponent = tabPane.getComponentAt(selectedIndex);
                } catch (final Exception e) {
                    log.info(e.getMessage());
                }
            }
            final int cx;
            int cy;
            int cw;
            final int ch;
            int totalTabWidth = 0;
            int totalTabHeight = 0;
            final Insets contentInsets = getContentBorderInsets(tabPlacement);

            boolean shouldChangeFocus = false;

            // In order to allow programs to use a single component
            // as the display for multiple tabs, we will not change
            // the visible compnent if the currently selected tab
            // has a null component. This is a bit dicey, as we don't
            // explicitly state we support this in the spec, but since
            // programs are now depending on this, we're making it work.
            //
            if (selectedComponent != null) {
                if (selectedComponent != visibleComponent && visibleComponent != null) {
                    if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
                        shouldChangeFocus = true;
                    }
                }
                setVisibleComponent(selectedComponent);
            }

            final Rectangle bounds = tabPane.getBounds();
            final int numChildren = tabPane.getComponentCount();

            if (numChildren > 0) {

                switch (tabPlacement) {
                case LEFT:
                    totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                    cx = insets.left + totalTabWidth + contentInsets.left;
                    cy = insets.top + contentInsets.top;
                    break;
                case RIGHT:
                    totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                    cx = insets.left + contentInsets.left;
                    cy = insets.top + contentInsets.top;
                    break;
                case BOTTOM:
                    totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                    cx = insets.left + contentInsets.left;
                    cy = insets.top + contentInsets.top;
                    break;
                case TOP:
                default:
                    totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                    cx = insets.left + contentInsets.left;
                    cy = insets.top + totalTabHeight + contentInsets.top;
                }

                cw = bounds.width - totalTabWidth - insets.left - insets.right - contentInsets.left
                        - contentInsets.right;
                ch = bounds.height - totalTabHeight - insets.top - insets.bottom - contentInsets.top
                        - contentInsets.bottom;

                for (int i = 0; i < numChildren; i++) {
                    try {
                        final Component child = tabPane.getComponent(i);
                        if (tabContainer == child) {

                            final int tabContainerWidth = totalTabWidth == 0 ? cw : totalTabWidth;
                            final int tabContainerHeight = totalTabHeight == 0 ? ch : totalTabHeight;

                            int tabContainerX = 0;
                            int tabContainerY = 0;
                            if (tabPlacement == BOTTOM) {
                                tabContainerY = bounds.height - tabContainerHeight;
                            } else if (tabPlacement == RIGHT) {
                                tabContainerX = bounds.width - tabContainerWidth;
                            }
                            child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
                        } else {
                            child.setBounds(cx, cy, cw, ch);
                        }
                    } catch (final Exception e) {
                        log.info(e.getMessage());
                    }
                }
            }
            layoutTabComponents();
            if (shouldChangeFocus) {
                if (!requestFocusForVisibleComponent()) {
                    tabPane.requestFocus();
                }
            }
        }

        private void layoutTabComponents() {
            if (tabContainer == null) {
                return;
            }
            final Rectangle rect = new Rectangle();
            final Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
            if (scrollableTabLayoutEnabled()) {
                translatePointToTabPanel(0, 0, delta);
            }
            for (int i = 0; i < tabPane.getTabCount(); i++) {
                final Component tabComponent = getTabComponentAt(i);
                if (tabComponent == null) {
                    continue;
                }
                getTabBounds(i, rect);
                final Dimension preferredSize = tabComponent.getPreferredSize();
                final Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
                final int outerX = rect.x + insets.left + delta.x;
                final int outerY = rect.y + insets.top + delta.y;
                final int outerWidth = rect.width - insets.left - insets.right;
                final int outerHeight = rect.height - insets.top - insets.bottom;
                // centralize component
                final int x = outerX + (outerWidth - preferredSize.width) / 2;
                final int y = outerY + (outerHeight - preferredSize.height) / 2;
                final int tabPlacement = tabPane.getTabPlacement();
                final boolean isSeleceted = i == tabPane.getSelectedIndex();
                tabComponent.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted),
                        y + getTabLabelShiftY(tabPlacement, i, isSeleceted), preferredSize.width, preferredSize.height);
            }
        }

        @Override
        public Dimension minimumLayoutSize(final Container parent) {
            return calculateSize(true);
        }

        protected void normalizeTabRuns(final int tabPlacement, final int tabCount, final int start, final int max) {
            // Only normalize the runs for top & bottom; normalizing
            // doesn't look right for Metal's vertical tabs
            // because the last run isn't padded and it looks odd to have
            // fat tabs in the first vertical runs, but slimmer ones in the
            // last (this effect isn't noticeable for horizontal tabs).
            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                int run = runCount - 1;
                boolean keepAdjusting = true;
                double weight = 1.25;

                // At this point the tab runs are packed to fit as many
                // tabs as possible, which can leave the last run with a lot
                // of extra space (resulting in very fat tabs on the last run).
                // So we'll attempt to distribute this extra space more evenly
                // across the runs in order to make the runs look more consistent.
                //
                // Starting with the last run, determine whether the last tab in
                // the previous run would fit (generously) in this run; if so,
                // move tab to current run and shift tabs accordingly. Cycle
                // through remaining runs using the same algorithm.
                //
                while (keepAdjusting) {
                    final int last = lastTabInRun(tabCount, run);
                    final int prevLast = lastTabInRun(tabCount, run - 1);
                    final int end;
                    final int prevLastLen;

                    end = rects[last].x + rects[last].width;
                    prevLastLen = (int) (maxTabWidth * weight);

                    // Check if the run has enough extra space to fit the last tab
                    // from the previous row...
                    if (max - end > prevLastLen) {

                        // Insert tab from previous row and shift rest over
                        tabRuns[run] = prevLast;
                        rects[prevLast].x = start;
                        for ( int i = prevLast + 1; i <= last; i++) {
                            rects[i].x = rects[i - 1].x + rects[i - 1].width;
                        }

                    } else if (run == runCount - 1) {
                        // no more room left in last run, so we're done!
                        keepAdjusting = false;
                    }
                    if (run - 1 > 0) {
                        // check previous run next...
                        run -= 1;
                    } else {
                        // check last run again...but require a higher ratio
                        // of extraspace-to-tabsize because we don't want to
                        // end up with too many tabs on the last run!
                        run = runCount - 1;
                        weight += .25;
                    }
                }
            }
        }

        protected void padSelectedTab(final int tabPlacement, final int selectedIndex) {
//            if ((selectedIndex >= 0) && (selectedIndex < rects.length)) {
//                Rectangle selRect = rects[selectedIndex];
//                Insets padInsets = getSelectedTabPadInsets(tabPlacement);
//                selRect.x -= padInsets.left;
//                selRect.width += (padInsets.left + padInsets.right);
//                selRect.y -= padInsets.top;
//                selRect.height += (padInsets.top + padInsets.bottom);
//            }
        }

        protected void padTabRun(final int tabPlacement, final int start, final int end, final int max) {
            final Rectangle lastRect = rects[end];
            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                final int runWidth = lastRect.x + lastRect.width - rects[start].x;
                final int deltaWidth = max - (lastRect.x + lastRect.width);
                final float factor = (float) deltaWidth / (float) runWidth;

                for ( int j = start; j <= end; j++) {
                    final Rectangle pastRect = rects[j];
                    if (j > start) {
                        pastRect.x = rects[j - 1].x + rects[j - 1].width;
                    }
                    pastRect.width += Math.round(pastRect.width * factor);
                }
                lastRect.width = max - lastRect.x;
            } else {
                final int runHeight = lastRect.y + lastRect.height - rects[start].y;
                final int deltaHeight = max - (lastRect.y + lastRect.height);
                final float factor = (float) deltaHeight / (float) runHeight;

                for ( int j = start; j <= end; j++) {
                    final Rectangle pastRect = rects[j];
                    if (j > start) {
                        pastRect.y = rects[j - 1].y + rects[j - 1].height;
                    }
                    pastRect.height += Math.round(pastRect.height * factor);
                }
                lastRect.height = max - lastRect.y;
            }
        }

        @Override
        public Dimension preferredLayoutSize(final Container parent) {
            return calculateSize(false);
        }

        protected int preferredTabAreaHeight(final int tabPlacement, final int width) {
            final FontMetrics fm = getFontMetrics();
            final int tc = tabPane.getTabCount();
            int total = 0;
            if (tc > 0) {
                int rows = 1;
                int x = 0;
                final int maxTabHeight = calculateMaxTabHeight(tabPlacement);

                for ( int i = 0; i < tc; i++) {
                    final int tabWidth = calculateTabWidth(tabPlacement, i, fm);

                    if (x != 0 && x + tabWidth > width) {
                        rows++;
                        x = 0;
                    }
                    x += tabWidth;
                }
                total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
            }
            return total;
        }

        protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
            final FontMetrics fm = getFontMetrics();
            final int tc = tabPane.getTabCount();
            int total = 0;
            if (tc > 0) {
                int columns = 1;
                int y = 0;
                final int fontHeight = fm.getHeight();

                maxTabWidth = calculateMaxTabWidth(tabPlacement);

                for ( int i = 0; i < tc; i++) {
                    final int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);

                    if (y != 0 && y + tabHeight > height) {
                        columns++;
                        y = 0;
                    }
                    y += tabHeight;
                }
                total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
            }
            return total;
        }

        @Override
        public void removeLayoutComponent(final Component comp) {
        }

        /*
         * Rotates the run-index array so that the selected run is run[0]
         */
        protected void rotateTabRuns(final int tabPlacement, final int selectedRun) {
            for ( int i = 0; i < selectedRun; i++) {
                final int save = tabRuns[0];
                if (runCount - 1 >= 0) System.arraycopy(tabRuns, 1, tabRuns, 0, runCount - 1);
                tabRuns[runCount - 1] = save;
            }
        }
    }

    private final class TabbedPaneScrollLayout extends TabbedPaneLayout {

        @Override
        protected void calculateTabRects(final int tabPlacement, final int tabCount) {
            final FontMetrics fm = getFontMetrics();
            final Dimension size = tabPane.getSize();
            final Insets insets = tabPane.getInsets();
            final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
            final int fontHeight = fm.getHeight();
            final boolean verticalTabRuns = tabPlacement == LEFT || tabPlacement == RIGHT;
            final boolean leftToRight = JTattooUtilities.isLeftToRight(tabPane);
            final int x = tabAreaInsets.left;
            final int y = tabAreaInsets.top;
            int totalWidth = 0;
            int totalHeight = 0;

            //
            // Calculate bounds within which a tab run must fit
            //
            switch (tabPlacement) {
            case LEFT:
            case RIGHT:
                maxTabWidth = calculateMaxTabWidth(tabPlacement);
                break;
            case BOTTOM:
            case TOP:
            default:
                maxTabHeight = calculateMaxTabHeight(tabPlacement);
            }

            runCount = 0;
            selectedRun = -1;

            if (tabCount == 0) {
                return;
            }

            selectedRun = 0;
            runCount = 1;

            // Run through tabs and lay them out in a single run
            Rectangle rect;
            for ( int i = 0; i < tabCount; i++) {
                rect = rects[i];

                if (!verticalTabRuns) {
                    // Tabs on TOP or BOTTOM....
                    if (i > 0) {
                        rect.x = rects[i - 1].x + rects[i - 1].width;
                    } else {
                        tabRuns[0] = 0;
                        maxTabWidth = 0;
                        totalHeight += maxTabHeight;
                        rect.x = x;
                    }
                    rect.width = calculateTabWidth(tabPlacement, i, fm);
                    totalWidth = rect.x + rect.width;
                    maxTabWidth = Math.max(maxTabWidth, rect.width);

                    rect.y = y;
                    rect.height = maxTabHeight/* - 2 */;

                } else {
                    // Tabs on LEFT or RIGHT...
                    if (i > 0) {
                        rect.y = rects[i - 1].y + rects[i - 1].height;
                    } else {
                        tabRuns[0] = 0;
                        maxTabHeight = 0;
                        totalWidth = maxTabWidth;
                        rect.y = y;
                    }
                    rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
                    totalHeight = rect.y + rect.height;
                    maxTabHeight = Math.max(maxTabHeight, rect.height);

                    rect.x = x;
                    rect.width = maxTabWidth/* - 2 */;

                }
            }

            // if right to left and tab placement on the top or
            // the bottom, flip x positions and adjust by widths
            if (!leftToRight && !verticalTabRuns) {
                final int rightMargin = size.width - (insets.right + tabAreaInsets.right);
                for ( int i = 0; i < tabCount; i++) {
                    rects[i].x = rightMargin - rects[i].x - rects[i].width;
                }
            }
            // tabPanel.setSize(totalWidth, totalHeight);
            tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
        }

        @Override
        public void layoutContainer(final Container parent) {
            final int tabPlacement = tabPane.getTabPlacement();
            final int tc = tabPane.getTabCount();
            final Insets insets = tabPane.getInsets();
            final int selectedIndex = tabPane.getSelectedIndex();
            final Component visibleComponent = getVisibleComponent();

            calculateLayoutInfo();

            Component selectedComponent = null;
            if (selectedIndex < 0) {
                if (visibleComponent != null) {
                    // The last tab was removed, so remove the component
                    setVisibleComponent(null);
                }
            } else {
                try {
                    selectedComponent = tabPane.getComponentAt(selectedIndex);
                } catch (final Exception ex) {
                    log.info(ex.getMessage());
                }
            }
            boolean shouldChangeFocus = false;

            // In order to allow programs to use a single component
            // as the display for multiple tabs, we will not change
            // the visible compnent if the currently selected tab
            // has a null component. This is a bit dicey, as we don't
            // explicitly state we support this in the spec, but since
            // programs are now depending on this, we're making it work.
            //
            if (selectedComponent != null) {
                if (selectedComponent != visibleComponent && visibleComponent != null) {
                    if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
                        shouldChangeFocus = true;
                    }
                }
                setVisibleComponent(selectedComponent);
            }
            final int tx;  // tab area bounds
            int ty;
            int tw;
            final int th;
            final int cx;  // content area bounds
            int cy;
            int cw;
            final int ch;
            final Insets contentInsets = getContentBorderInsets(tabPlacement);
            final Rectangle bounds = tabPane.getBounds();
            final int numChildren = tabPane.getComponentCount();

            final int space = 60;
            if (numChildren > 0 && tc > 0) {
                switch (tabPlacement) {
                case LEFT:
                    // calculate tab area bounds
                    tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                    th = bounds.height - insets.top - insets.bottom;
                    tx = insets.left;
                    ty = insets.top;

                    // calculate content area bounds
                    cx = tx + tw + contentInsets.left;
                    cy = ty + contentInsets.top;
                    cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right;
                    ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
                    break;
                case RIGHT:
                    // calculate tab area bounds
                    tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                    th = bounds.height - insets.top - insets.bottom;
                    tx = bounds.width - insets.right - tw;
                    ty = insets.top;

                    // calculate content area bounds
                    cx = insets.left + contentInsets.left;
                    cy = insets.top + contentInsets.top;
                    cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right;
                    ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
                    break;
                case BOTTOM:
                    // calculate tab area bounds
                    tw = bounds.width - insets.left - insets.right;
                    th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                    tx = insets.left;
                    ty = bounds.height - insets.bottom - th;

                    // calculate content area bounds
                    cx = insets.left + contentInsets.left;
                    cy = insets.top + contentInsets.top;
                    cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
                    ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom;
                    break;
                case TOP:
                default:
                    // calculate tab area bounds
                    tw = bounds.width - insets.left - insets.right;
                    th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                    tx = insets.left;
                    ty = insets.top;

                    // calculate content area bounds
                    cx = tx + contentInsets.left;
                    cy = ty + th + contentInsets.top;
                    cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
                    ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom;
                }
                for ( int i = 0; i < numChildren; i++) {
                    final Component child = tabPane.getComponent(i);
                    if (child instanceof ScrollableTabViewport) {
                        final JViewport viewport = (JViewport) child;
                        final Rectangle viewRect = viewport.getViewRect();
                        int vw = tw;
                        int vh = th;
                        switch (tabPlacement) {
                        case LEFT:
                        case RIGHT:
                            final int totalTabHeight = rects[tc - 1].y + rects[tc - 1].height;
                            if (totalTabHeight > th) {
                                // Allow space for scrollbuttons
                                vh = Math.max(th - space, space);
                                if (totalTabHeight - viewRect.y <= vh) {
                                    // Scrolled to the end, so ensure the viewport size is
                                    // such that the scroll offset aligns with a tab
                                    vh = totalTabHeight - viewRect.y;
                                }
                            }
                            break;
                        case BOTTOM:
                        case TOP:
                        default:
                            final int totalTabWidth = rects[tc - 1].x + rects[tc - 1].width;
                            if (totalTabWidth > tw) {
                                // Allow space for scrollbuttons
                                vw = Math.max(tw - space, space);
                                if (totalTabWidth - viewRect.x <= vw) {
                                    // Scrolled to the end, so ensure the viewport size is
                                    // such that the scroll offset aligns with a tab
                                    vw = totalTabWidth - viewRect.x;
                                }
                            }
                        }

                        child.setBounds(tx, ty, vw, vh);

                    } else if (child instanceof ScrollableTabButton) {
                        final ScrollableTabButton scrollbutton = (ScrollableTabButton) child;
                        final Dimension bsize = scrollbutton.getPreferredSize();
                        int bx = 0;
                        int by = 0;
                        final int bw = bsize.width;
                        final int bh = bsize.height;
                        boolean visible = false;

                        switch (tabPlacement) {
                        case LEFT:
                        case RIGHT:
                            final int totalTabHeight = rects[tc - 1].y + rects[tc - 1].height;
                            if (totalTabHeight > th) {
                                final int dir = scrollbutton.scrollsForward() ? SOUTH : NORTH;
                                scrollbutton.setDirection(dir);
                                visible = true;
                                bx = tabPlacement == LEFT ? tw - insets.left - tabAreaInsets.bottom - bsize.width
                                        : bounds.width - insets.left - bsize.width;
                                by = dir == SOUTH ? bounds.height - insets.bottom - 2 * bsize.height - 2
                                        : bounds.height - insets.bottom - 3 * bsize.height - 2;
                            }
                            break;

                        case BOTTOM:
                        case TOP:
                        default:
                            final int totalTabWidth = rects[tc - 1].x + rects[tc - 1].width;
                            if (totalTabWidth > tw) {
                                final int dir = scrollbutton.scrollsForward() ? EAST : WEST;
                                scrollbutton.setDirection(dir);
                                visible = true;
                                bx = dir == EAST ? bounds.width - insets.left - 2 * bsize.width - 2
                                        : bounds.width - insets.left - 3 * bsize.width - 2;
                                by = ty + (th - bsize.height - tabAreaInsets.bottom) / 2;
                                if (tabPlacement == BOTTOM) {
                                    by += tabAreaInsets.bottom;
                                } else {
                                    by++;
                                }
                            }
                        }

                        child.setVisible(visible);
                        if (visible) {
                            child.setBounds(bx, by, bw, bh);
                        }

                    } else if (child instanceof ScrollablePopupMenuTabButton) {
                        final ScrollablePopupMenuTabButton button = (ScrollablePopupMenuTabButton) child;
                        final Dimension bsize = button.getPreferredSize();
                        int bx = 0;
                        int by = 0;
                        final int bw = bsize.width;
                        final int bh = bsize.height;
                        boolean visible = false;

                        switch (tabPlacement) {
                        case LEFT:
                        case RIGHT:
                            final int totalTabHeight = rects[tc - 1].y + rects[tc - 1].height;
                            if (totalTabHeight > th) {
                                visible = true;
                                bx = tabPlacement == LEFT ? tw - insets.left - tabAreaInsets.bottom - bsize.width
                                        : bounds.width - insets.left - bsize.width;
                                by = bounds.height - insets.bottom - bsize.height;
                            }
                            break;

                        case BOTTOM:
                        case TOP:
                        default:
                            final int totalTabWidth = rects[tc - 1].x + rects[tc - 1].width;
                            if (totalTabWidth > tw) {
                                visible = true;
                                bx = bounds.width - insets.left - bsize.width;
                                by = ty + (th - bsize.height - tabAreaInsets.bottom) / 2;
                                if (tabPlacement == BOTTOM) {
                                    by += tabAreaInsets.bottom;
                                } else {
                                    by++;
                                }
                            }
                        }

                        child.setVisible(visible);
                        if (visible) {
                            child.setBounds(bx, by, bw, bh);
                        }
                    } else {
                        // All content children...
                        child.setBounds(cx, cy, cw, ch);
                    }
                }
                super.layoutTabComponents();
                if (shouldChangeFocus) {
                    if (!requestFocusForVisibleComponent()) {
                        tabPane.requestFocus();
                    }
                }
            }
        }

        @Override
        protected int preferredTabAreaHeight(final int tabPlacement, final int width) {
            return calculateMaxTabHeight(tabPlacement);
        }

        @Override
        protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
            return calculateMaxTabWidth(tabPlacement);
        }
    }

    public class TabComponentHandler implements ComponentListener {

        @Override
        public void componentHidden(final ComponentEvent ce) {
        }

        @Override
        public void componentMoved(final ComponentEvent ce) {
        }

        @Override
        public void componentResized(final ComponentEvent ce) {
            SwingUtilities.invokeLater(() -> {
                if (tabPane != null) {
                    tabPane.doLayout();
                }
            });
        }

        @Override
        public void componentShown(final ComponentEvent ce) {

        }
    }

    private final class TabContainer extends JPanel implements UIResource {

        /**
         *
         */
        private static final long serialVersionUID = 1L;
        private boolean notifyTabbedPane = true;

        public TabContainer() {
            super(null);
            setOpaque(false);
        }

        @Override
        public void remove(final Component comp) {
            final int index = tabPane.indexOfTabComponent(comp);
            final PropertyChangeListener[] listeners = comp.getPropertyChangeListeners();
            for (final PropertyChangeListener listener : listeners) {
                if (listener instanceof MyTabComponentListener) {
                    comp.removePropertyChangeListener(listener);
                }
            }
            super.remove(comp);
            if (notifyTabbedPane && index != -1) {
                tabPane.setTabComponentAt(index, null);
            }
        }

        private void removeUnusedTabComponents() {
            for ( int i = 0; i < getComponentCount(); i++) {
                final Component c = getComponent(i);
                if (!(c instanceof UIResource)) {
                    final int index = tabPane.indexOfTabComponent(c);
                    if (index == -1) {
                        final PropertyChangeListener[] listeners = c.getPropertyChangeListeners();
                        for (final PropertyChangeListener listener : listeners) {
                            if (listener instanceof MyTabComponentListener) {
                                c.removePropertyChangeListener(listener);
                            }
                        }
                        super.remove(c);
                    }
                }
            }
        }

    } // end of class ContainerHandler

    /**
     * This inner class is marked &quot;public&quot; due to a compiler bug. This
     * class should be treated as a &quot;protected&quot; inner class. Instantiate
     * it only within subclasses of BaseTabbedPaneUI.
     */
    public static class TabSelectionHandler implements ChangeListener {

        @Override
        public void stateChanged(final ChangeEvent e) {
            final JTabbedPane tabPane = (JTabbedPane) e.getSource();
            if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
                final int index = tabPane.getSelectedIndex();
                if (index >= 0) {
                    final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) tabPane.getUI();
                    ui.tabScroller.scrollTabToVisible(tabPane.getTabPlacement(), index);
                }
            }
            tabPane.revalidate();
            tabPane.repaint();
        }
    }

    private final static class UpAction extends AbstractAction {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(final ActionEvent e) {
            final JTabbedPane pane = (JTabbedPane) e.getSource();
            final BaseTabbedPaneUI ui = (BaseTabbedPaneUI) pane.getUI();
            ui.navigateSelectedTab(NORTH);
        }
    }

    /** Constant NULL_BORDER_INSETS */
    protected static final Insets NULL_BORDER_INSETS = new Insets(0, 0, 0, 0);
    /** Constant GAP=5 */
    protected static final int GAP = 5;
    private static final int CROP_SEGMENT = 12;

    /** {@inheritDoc} */
    public static ComponentUI createUI(final JComponent c) {
        return new BaseTabbedPaneUI();
    }

    /**
     * <p>rotateInsets.</p>
     *
     * @param topInsets a {@link java.awt.Insets} object.
     * @param targetInsets a {@link java.awt.Insets} object.
     * @param targetPlacement a {@link java.lang.Integer} object.
     */
    protected static void rotateInsets(final Insets topInsets, final Insets targetInsets, final int targetPlacement) {
        switch (targetPlacement) {
        case LEFT:
            targetInsets.top = topInsets.left;
            targetInsets.left = topInsets.top;
            targetInsets.bottom = topInsets.right;
            targetInsets.right = topInsets.bottom;
            break;
        case BOTTOM:
            targetInsets.top = topInsets.bottom;
            targetInsets.left = topInsets.left;
            targetInsets.bottom = topInsets.top;
            targetInsets.right = topInsets.right;
            break;
        case RIGHT:
            targetInsets.top = topInsets.left;
            targetInsets.left = topInsets.bottom;
            targetInsets.bottom = topInsets.right;
            targetInsets.right = topInsets.top;
            break;
        case TOP:
        default:
            targetInsets.top = topInsets.top;
            targetInsets.left = topInsets.left;
            targetInsets.bottom = topInsets.bottom;
            targetInsets.right = topInsets.right;
        }
    }

    // Instance variables initialized at installation
    protected JTabbedPane tabPane;

    protected Color tabAreaBackground;

    protected Color selectedColor;

    protected int textIconGap;

    protected int tabRunOverlay;

    protected Insets tabInsets;

    protected Insets selectedTabPadInsets;

    protected Insets tabAreaInsets;

    protected Insets contentBorderInsets;

    // Transient variables (recalculated each time TabbedPane is layed out)
    protected int[] tabRuns = new int[10];

    protected int runCount = 0;

    protected int selectedRun = -1;

    protected Rectangle[] rects = new Rectangle[0];

    protected int maxTabHeight;

    protected int maxTabWidth;

    // Listeners
    protected ChangeListener tabChangeListener;

    protected ComponentListener tabComponentListener;

    protected PropertyChangeListener propertyChangeListener;

    protected MouseListener mouseListener;

    protected MouseMotionListener mouseMotionListener;

    protected FocusListener focusListener;

    // PENDING(api): See comment for ContainerHandler
    private ContainerListener containerListener;

    // Private instance data
    private final Insets currentPadInsets = new Insets(0, 0, 0, 0);

    private final Insets currentTabAreaInsets = new Insets(0, 0, 0, 0);

    private Component visibleComponent;

    // PENDING(api): See comment for ContainerHandler
    private List<View> htmlViews;

    private Map<Integer, Integer> mnemonicToIndexMap;

    /**
     * InputMap used for mnemonics. Only non-null if the JTabbedPane has mnemonics
     * associated with it. Lazily created in initMnemonics.
     */
    private InputMap mnemonicInputMap;

    // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
    private ScrollableTabSupport tabScroller;

    private TabContainer tabContainer;

    /**
     * A rectangle used for general layout calculations in order to avoid
     * constructing many new Rectangles on the fly.
     */
    protected transient Rectangle calcRect = new Rectangle(0, 0, 0, 0);

    /**
     * Number of tabs. When the count differs, the mnemonics are updated.
     */
    // PENDING: This wouldn't be necessary if JTabbedPane had a better
    // way of notifying listeners when the count changed.
    private int tabCount;

    protected int oldRolloverIndex = -1;

    protected int rolloverIndex = -1;

    protected boolean roundedTabs = true;

    protected boolean simpleButtonBorder = false;

    /*
     * This method will create and return a polygon shape for the given tab
     * rectangle which has been cropped at the specified cropline with a torn edge
     * visual. e.g. A "File" tab which has cropped been cropped just after the "i":
     * ------------- | ..... | | . | | ... . | | . . | | . . | | . . |
     * --------------
     *
     * The x, y arrays below define the pattern used to create a "torn" edge segment
     * which is repeated to fill the edge of the tab. For tabs placed on TOP and
     * BOTTOM, this righthand torn edge is created by line segments which are
     * defined by coordinates obtained by subtracting xCropLen[i] from (tab.x +
     * tab.width) and adding yCroplen[i] to (tab.y). For tabs placed on LEFT or
     * RIGHT, the bottom torn edge is created by subtracting xCropLen[i] from (tab.y
     * + tab.height) and adding yCropLen[i] to (tab.x).
     */
    private final int[] xCropLen = { 1, 1, 0, 0, 1, 1, 2, 2 };

    private final int[] yCropLen = { 0, 3, 3, 6, 6, 9, 9, 12 };

    /**
     * Adds the specified mnemonic at the specified index.
     */
    private void addMnemonic(final int index, final int mnemonic) {
        if (mnemonicToIndexMap == null) {
            initMnemonics();
        }
        mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, InputEvent.ALT_MASK), "setSelectedIndex");
        mnemonicToIndexMap.put(mnemonic, index);
    }

    private void addMyPropertyChangeListeners(final Component component) {
        component.addPropertyChangeListener(new MyTabComponentListener());
        if (component instanceof Container) {
            final Container container = (Container) component;
            for ( int i = 0; i < container.getComponentCount(); i++) {
                final Component c = container.getComponent(i);
                addMyPropertyChangeListeners(c);
            }
        }
    }

    /**
     * <p>assureRectsCreated.</p>
     *
     * @param tabCount a {@link java.lang.Integer} object.
     */
    protected void assureRectsCreated(final int tabCount) {
        final int rectArrayLen = rects.length;
        if (tabCount != rectArrayLen) {
            final Rectangle[] tempRectArray = new Rectangle[tabCount];
            System.arraycopy(rects, 0, tempRectArray, 0, Math.min(rectArrayLen, tabCount));
            rects = tempRectArray;
            for ( int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
                rects[rectIndex] = new Rectangle();
            }
        }
    }

    /**
     * <p>calculateMaxTabHeight.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int calculateMaxTabHeight(final int tabPlacement) {
        final int tc = tabPane.getTabCount();
        int result = 0;
        final int fontHeight = getFontMetrics().getHeight();
        for ( int i = 0; i < tc; i++) {
            result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
        }
        return result;
    }

    /**
     * <p>calculateMaxTabWidth.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int calculateMaxTabWidth(final int tabPlacement) {
        final int tc = tabPane.getTabCount();
        int result = 0;
        for ( int i = 0; i < tc; i++) {
            result = Math.max(calculateTabWidth(tabPlacement, i, getFontMetrics()), result);
        }
        return result;
    }

    /**
     * <p>calculateTabAreaHeight.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param horizRunCount a {@link java.lang.Integer} object.
     * @param maxTabHeight a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int calculateTabAreaHeight(final int tabPlacement, final int horizRunCount, final int maxTabHeight) {
        if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) {
            final Insets insets = getTabAreaInsets(tabPlacement);
            final int overlay = getTabRunOverlay(tabPlacement);
            return horizRunCount > 0 ? horizRunCount * (maxTabHeight - overlay) + overlay + insets.top + insets.bottom
                    : 0;
        } else {
            return tabPane.getHeight();
        }
    }

    /**
     * <p>calculateTabAreaWidth.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param vertRunCount a {@link java.lang.Integer} object.
     * @param maxTabWidth a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int calculateTabAreaWidth(final int tabPlacement, final int vertRunCount, final int maxTabWidth) {
        if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) {
            final Insets insets = getTabAreaInsets(tabPlacement);
            final int overlay = getTabRunOverlay(tabPlacement);
            return vertRunCount > 0 ? vertRunCount * (maxTabWidth - overlay) + overlay + insets.left + insets.right : 0;
        } else {
            return tabPane.getWidth();
        }
    }

    /**
     * <p>calculateTabHeight.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param fontHeight a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int calculateTabHeight(final int tabPlacement, final int tabIndex, final int fontHeight) {
        int height = 0;
        final Component tabComponent = getTabComponentAt(tabIndex);
        if (tabComponent != null) {
            height = tabComponent.getPreferredSize().height;
        } else {
            final View v = getTextViewForTab(tabIndex);
            if (v != null) {
                // html
                height += (int) v.getPreferredSpan(View.Y_AXIS);
            } else {
                // plain text
                height += fontHeight;
            }
            final Icon icon = getIconForTab(tabIndex);
            if (icon != null) {
                height = Math.max(height, icon.getIconHeight());
            }
        }
        final Insets ti = getTabInsets(tabPlacement, tabIndex);
        height += ti.top + ti.bottom + 2;
        return height;
    }

    /**
     * <p>calculateTabWidth.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param metrics a {@link java.awt.FontMetrics} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int calculateTabWidth(final int tabPlacement, final int tabIndex, final FontMetrics metrics) {
        final Insets insets = getTabInsets(tabPlacement, tabIndex);
        int width = insets.left + insets.right + 3;
        final Component tabComponent = getTabComponentAt(tabIndex);
        if (tabComponent != null) {
            width += tabComponent.getPreferredSize().width;
        } else {
            final Icon icon = getIconForTab(tabIndex);
            if (icon != null) {
                width += icon.getIconWidth() + textIconGap;
            }
            final View v = getTextViewForTab(tabIndex);
            if (v != null) {
                // html
                width += (int) v.getPreferredSpan(View.X_AXIS);
            } else {
                // plain text
                final String title = tabPane.getTitleAt(tabIndex);
                width += SwingUtilities.computeStringWidth(metrics, title);
            }
        }

        return width;
    }

    ActionMap createActionMap() {
        final ActionMap map = new ActionMapUIResource();
        map.put("navigateNext", new NextAction());
        map.put("navigatePrevious", new PreviousAction());
        map.put("navigateRight", new RightAction());
        map.put("navigateLeft", new LeftAction());
        map.put("navigateUp", new UpAction());
        map.put("navigateDown", new DownAction());
        map.put("navigatePageUp", new PageUpAction());
        map.put("navigatePageDown", new PageDownAction());
        map.put("requestFocus", new RequestFocusAction());
        map.put("requestFocusForVisibleComponent", new RequestFocusForVisibleAction());
        map.put("setSelectedIndex", new SetSelectedIndexAction());
        map.put("scrollTabsForwardAction", new ScrollTabsForwardAction());
        map.put("scrollTabsBackwardAction", new ScrollTabsBackwardAction());
        map.put("scrollTabsPopupMenuAction", new ScrollTabsPopupMenuAction());
        return map;
    }

    /**
     * <p>createChangeListener.</p>
     *
     * @return a {@link javax.swing.event.ChangeListener} object.
     */
    protected ChangeListener createChangeListener() {
        return new TabSelectionHandler();
    }

    /**
     * <p>createComponentListener.</p>
     *
     * @return a {@link java.awt.event.ComponentListener} object.
     */
    protected ComponentListener createComponentListener() {
        return new TabComponentHandler();
    }

    private Polygon createCroppedTabClip(final int tabPlacement, final Rectangle tabRect, final int cropline) {
        final int rlen;
        final int start;
        final int end;
        final int ostart;

        switch (tabPlacement) {
        case LEFT:
        case RIGHT:
            rlen = tabRect.width;
            start = tabRect.x;
            end = tabRect.x + tabRect.width;
            ostart = tabRect.y;
            break;
        case TOP:
        case BOTTOM:
        default:
            rlen = tabRect.height;
            start = tabRect.y;
            end = tabRect.y + tabRect.height;
            ostart = tabRect.x;
        }
        int rcnt = rlen / CROP_SEGMENT;
        if (rlen % CROP_SEGMENT > 0) {
            rcnt++;
        }
        final int npts = 2 + rcnt * 8;
        final int[] xp = new int[npts];
        final int[] yp = new int[npts];
        int pcnt = 0;

        xp[pcnt] = ostart;
        yp[pcnt++] = end;
        xp[pcnt] = ostart;
        yp[pcnt++] = start;
        for ( int i = 0; i < rcnt; i++) {
            for ( int j = 0; j < xCropLen.length; j++) {
                xp[pcnt] = cropline - xCropLen[j];
                yp[pcnt] = start + i * CROP_SEGMENT + yCropLen[j];
                if (yp[pcnt] >= end) {
                    yp[pcnt] = end;
                    pcnt++;
                    break;
                }
                pcnt++;
            }
        }
        if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) {
            return new Polygon(xp, yp, pcnt);
        } else {
            // LEFT or RIGHT
            return new Polygon(yp, xp, pcnt);
        }
    }

    /**
     * <p>createFocusListener.</p>
     *
     * @return a {@link java.awt.event.FocusListener} object.
     */
    protected FocusListener createFocusListener() {
        return new FocusHandler();
    }

    private List<View> createHTMLViewList() {
        final ArrayList<View> viewList = new ArrayList<>();
        final int count = tabPane.getTabCount();
        for ( int i = 0; i < count; i++) {
            final String title = tabPane.getTitleAt(i);
            if (BasicHTML.isHTMLString(title)) {
                viewList.add(BasicHTML.createHTMLView(tabPane, title));
            } else {
                viewList.add(null);
            }
        }
        return viewList;
    }

    /**
     * Invoked by installUI to create a layout manager object to manage
     * the JTabbedPane.
     *
     * @return a layout manager object
     * @see TabbedPaneLayout
     * @see javax.swing.JTabbedPane#getTabLayoutPolicy
     */
    protected LayoutManager createLayoutManager() {
        if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
            return new TabbedPaneScrollLayout();
        }
        /* WRAP_TAB_LAYOUT */
        return new TabbedPaneLayout();
    }

    /**
     * <p>createMouseListener.</p>
     *
     * @return a {@link java.awt.event.MouseListener} object.
     */
    protected MouseListener createMouseListener() {
        return new MouseHandler();
    }

    /**
     * <p>createMouseMotionListener.</p>
     *
     * @return a {@link java.awt.event.MouseMotionListener} object.
     */
    protected MouseMotionListener createMouseMotionListener() {
        return new MouseMotionHandler();
    }

    /**
     * <p>createPropertyChangeListener.</p>
     *
     * @return a {@link java.beans.PropertyChangeListener} object.
     */
    protected PropertyChangeListener createPropertyChangeListener() {
        return new PropertyChangeHandler();
    }

    // TabbedPaneUI methods
    private void ensureCurrentLayout() {
        // TabPane maybe still invalid. See bug 4237677.
        ((TabbedPaneLayout) tabPane.getLayout()).calculateLayoutInfo();
    }

    /**
     * <p>expandTabRunsArray.</p>
     */
    protected void expandTabRunsArray() {
        final int rectLen = tabRuns.length;
        final int[] newArray = new int[rectLen + 10];
        System.arraycopy(tabRuns, 0, newArray, 0, runCount);
        tabRuns = newArray;
    }

    ActionMap getActionMap() {
        ActionMap map = (ActionMap) UIManager.get("TabbedPane.actionMap");

        if (map == null) {
            map = createActionMap();
            if (map != null) {
                UIManager.getLookAndFeelDefaults().put("TabbedPane.actionMap", map);
            }
        }
        return map;
    }

    /*
     * Returns the index of the tab closest to the passed in location, note that the
     * returned tab may not contain the location x,y.
     */
    /**
     * <p>getClosestTab.</p>
     *
     * @param x a {@link java.lang.Integer} object.
     * @param y a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getClosestTab(final int x, final int y) {
        int min = 0;
        final int tc = Math.min(rects.length, tabPane.getTabCount());
        int max = tc;
        final int tabPlacement = tabPane.getTabPlacement();
        final boolean useX = tabPlacement == TOP || tabPlacement == BOTTOM;
        final int want = useX ? x : y;

        while (min != max) {
            final int current = (max + min) / 2;
            final int minLoc;
            final int maxLoc;

            if (useX) {
                minLoc = rects[current].x;
                maxLoc = minLoc + rects[current].width;
            } else {
                minLoc = rects[current].y;
                maxLoc = minLoc + rects[current].height;
            }
            if (want < minLoc) {
                max = current;
                if (min == max) {
                    return Math.max(0, current - 1);
                }
            } else if (want >= maxLoc) {
                min = current;
                if (max - min <= 1) {
                    return Math.max(current + 1, tc - 1);
                }
            } else {
                return current;
            }
        }
        return min;
    }

    /**
     * <p>getContentBorderColor.</p>
     *
     * @return a {@link java.awt.Color} object.
     */
    protected Color getContentBorderColor() {
        return AbstractLookAndFeel.getFrameColor();
    }

    /**
     * <p>getContentBorderColors.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return an array of {@link java.awt.Color} objects.
     */
    protected Color[] getContentBorderColors(final int tabPlacement) {
        final int sepHeight = tabAreaInsets.bottom;
        final Color[] selColors = AbstractLookAndFeel.getTheme().getSelectedColors();
        final Color loColor = selColors[selColors.length - 1];
        final Color darkLoColor = ColorHelper.darker(loColor, 20);
        return ColorHelper.createColorArr(loColor, darkLoColor, sepHeight);
    }

    /**
     * <p>Getter for the field contentBorderInsets.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return a {@link java.awt.Insets} object.
     */
    protected Insets getContentBorderInsets(final int tabPlacement) {
        if (tabPane.getBorder() == null) {
            return NULL_BORDER_INSETS;
        }
        return contentBorderInsets;
    }

    /**
     * <p>getFontMetrics.</p>
     *
     * @return a {@link java.awt.FontMetrics} object.
     */
    protected FontMetrics getFontMetrics() {
        final Font font = tabPane.getFont().deriveFont(Font.BOLD);
        return JTattooUtilities.getFontMetrics(tabPane, null, font);
    }

    /**
     * <p>getGapColor.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @return a {@link java.awt.Color} object.
     */
    protected Color getGapColor(final int tabIndex) {
        if (isTabOpaque() || tabIndex == tabPane.getSelectedIndex()) {
            if (tabIndex >= 0 && tabIndex < tabCount) {
                final Color[] tabColors = getTabColors(tabIndex, tabIndex == tabPane.getSelectedIndex(), false);
                if (tabColors != null && tabColors.length > 0) {
                    return tabColors[tabColors.length - 1];
                } else {
                    return tabPane.getBackgroundAt(tabIndex);
                }
            }
        }
        if (!tabPane.isOpaque()) {
            Container parent = tabPane.getParent();
            while (parent != null) {
                if (parent.isOpaque()) {
                    return parent.getBackground();
                }
                parent = parent.getParent();
            }
        }
        return tabAreaBackground;
    }

    /**
     * <p>getHiBorderColor.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @return a {@link java.awt.Color} object.
     */
    protected Color getHiBorderColor(final int tabIndex) {
        final Color backColor = tabPane.getBackgroundAt(tabIndex);
        if (tabIndex == tabPane.getSelectedIndex()) {
            if (backColor instanceof UIResource) {
                return MetalLookAndFeel.getControlHighlight();
            } else {
                return ColorHelper.brighter(backColor, 40);
            }
        }
        if (tabIndex >= 0 && tabIndex <= tabCount) {
            if (!isTabOpaque() || backColor instanceof UIResource) {
                return MetalLookAndFeel.getControlHighlight();
            } else {
                return ColorHelper.brighter(backColor, 40);
            }
        }
        return MetalLookAndFeel.getControlHighlight();
    }

    /**
     * <p>getIconForTab.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @return a {@link javax.swing.Icon} object.
     */
    protected Icon getIconForTab(final int tabIndex) {
        if (tabIndex >= 0 && tabIndex < tabCount) {
            return !tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex) ? tabPane.getDisabledIconAt(tabIndex)
                    : tabPane.getIconAt(tabIndex);
        }
        return null;
    }

    InputMap getInputMap(final int condition) {
        if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
            return (InputMap) UIManager.get("TabbedPane.ancestorInputMap");
        } else if (condition == JComponent.WHEN_FOCUSED) {
            return (InputMap) UIManager.get("TabbedPane.focusInputMap");
        }
        return null;
    }

    /**
     * <p>getLoBorderColor.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @return a {@link java.awt.Color} object.
     */
    protected Color getLoBorderColor(final int tabIndex) {
        return MetalLookAndFeel.getControlDarkShadow();
    }

    /** {@inheritDoc} */
    @Override
    public Dimension getMaximumSize(final JComponent c) {
        // Default to LayoutManager's maximumLayoutSize
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public Dimension getMinimumSize(final JComponent c) {
        // Default to LayoutManager's minimumLayoutSize
        return null;
    }

    /**
     * <p>getNextTabIndex.</p>
     *
     * @param base a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getNextTabIndex(final int base) {
        return (base + 1) % tabPane.getTabCount();
    }

    /**
     * <p>getNextTabIndexInRun.</p>
     *
     * @param tabCount a {@link java.lang.Integer} object.
     * @param base a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getNextTabIndexInRun(final int tabCount, final int base) {
        if (runCount < 2) {
            return getNextTabIndex(base);
        }
        final int currentRun = getRunForTab(tabCount, base);
        final int next = getNextTabIndex(base);
        if (next == tabRuns[getNextTabRun(currentRun)]) {
            return tabRuns[currentRun];
        }
        return next;
    }

    /**
     * <p>getNextTabRun.</p>
     *
     * @param baseRun a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getNextTabRun(final int baseRun) {
        return (baseRun + 1) % runCount;
    }

    // Geometry
    /** {@inheritDoc} */
    @Override
    public Dimension getPreferredSize(final JComponent c) {
        // Default to LayoutManager's preferredLayoutSize
        return null;
    }

    /**
     * <p>getPreviousTabIndex.</p>
     *
     * @param base a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getPreviousTabIndex(final int base) {
        final int tabIndex = base - 1 >= 0 ? base - 1 : tabPane.getTabCount() - 1;
        return Math.max(tabIndex, 0);
    }

    /**
     * <p>getPreviousTabIndexInRun.</p>
     *
     * @param tabCount a {@link java.lang.Integer} object.
     * @param base a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getPreviousTabIndexInRun(final int tabCount, final int base) {
        if (runCount < 2) {
            return getPreviousTabIndex(base);
        }
        final int currentRun = getRunForTab(tabCount, base);
        if (base == tabRuns[currentRun]) {
            final int previous = tabRuns[getNextTabRun(currentRun)] - 1;
            return previous != -1 ? previous : tabCount - 1;
        }
        return getPreviousTabIndex(base);
    }

    /**
     * <p>getPreviousTabRun.</p>
     *
     * @param baseRun a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getPreviousTabRun(final int baseRun) {
        final int runIndex = baseRun - 1 >= 0 ? baseRun - 1 : runCount - 1;
        return Math.max(runIndex, 0);
    }

    /**
     * <p>getRunForTab.</p>
     *
     * @param tabCount a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getRunForTab(final int tabCount, final int tabIndex) {
        for ( int i = 0; i < runCount; i++) {
            final int first = tabRuns[i];
            final int last = lastTabInRun(tabCount, i);
            if (tabIndex >= first && tabIndex <= last) {
                return i;
            }
        }
        return 0;
    }

    /**
     * <p>Getter for the field selectedTabPadInsets.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return a {@link java.awt.Insets} object.
     */
    protected Insets getSelectedTabPadInsets(final int tabPlacement) {
        rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
        return currentPadInsets;
    }

    /**
     * <p>Getter for the field tabAreaInsets.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return a {@link java.awt.Insets} object.
     */
    protected Insets getTabAreaInsets(final int tabPlacement) {
        rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
        return currentTabAreaInsets;
    }

    /*
     * Returns the tab index which intersects the specified point in the coordinate
     * space of the component where the tabs are actually rendered, which could be
     * the JTabbedPane (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel
     * (SCROLL_TAB_LAYOUT).
     */
    /**
     * <p>getTabAtLocation.</p>
     *
     * @param x a {@link java.lang.Integer} object.
     * @param y a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getTabAtLocation(final int x, final int y) {
        ensureCurrentLayout();
        final int tc = tabPane.getTabCount();
        for ( int i = 0; i < tc; i++) {
            if (rects[i].contains(x, y)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Returns the bounds of the specified tab in the coordinate space of the
     * JTabbedPane component. This is required because the tab rects are by default
     * defined in the coordinate space of the component where they are rendered,
     * which could be the JTabbedPane (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel
     * (SCROLL_TAB_LAYOUT). This method should be used whenever the tab rectangle
     * must be relative to the JTabbedPane itself and the result should be placed in
     * a designated Rectangle object (rather than instantiating and returning a new
     * Rectangle each time). The tab index parameter must be a valid tabbed pane tab
     * index (0 to tab count - 1, inclusive). The destination rectangle parameter
     * must be a valid Rectangle instance. The handling of invalid
     * parameters is unspecified.
     *
     * @param tabIndex the index of the tab
     * @param dest     the rectangle where the result should be placed
     * @return the resulting rectangle
     * @since 1.4
     */
    protected Rectangle getTabBounds(final int tabIndex, final Rectangle dest) {
        dest.width = rects[tabIndex].width;
        dest.height = rects[tabIndex].height;

        if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
            // Need to translate coordinates based on viewport location &
            // view position
            final Point vpp = tabScroller.viewport.getLocation();
            final Point viewp = tabScroller.viewport.getViewPosition();
            dest.x = rects[tabIndex].x + vpp.x - viewp.x;
            dest.y = rects[tabIndex].y + vpp.y - viewp.y;

        } else { // WRAP_TAB_LAYOUT
            dest.x = rects[tabIndex].x;
            dest.y = rects[tabIndex].y;
        }
        return dest;
    }

    /*
     * Returns the bounds of the specified tab index. The bounds are with respect to
     * the JTabbedPane's coordinate space.
     */
    /** {@inheritDoc} */
    @Override
    public Rectangle getTabBounds(final JTabbedPane pane, final int i) {
        ensureCurrentLayout();
        final Rectangle tabRect = new Rectangle();
        return getTabBounds(i, tabRect);
    }

    // colors
    /**
     * <p>getTabColors.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     * @param isRollover a boolean.
     * @return an array of {@link java.awt.Color} objects.
     */
    protected Color[] getTabColors(final int tabIndex, final boolean isSelected, final boolean isRollover) {
        Color[] colorArr = AbstractLookAndFeel.getTheme().getTabColors();
        if (tabIndex >= 0 && tabIndex < tabPane.getTabCount()) {
            final boolean isEnabled = tabPane.isEnabledAt(tabIndex);
            final Color backColor = tabPane.getBackgroundAt(tabIndex);
            if (backColor instanceof UIResource) {
                if (isSelected) {
                    colorArr = AbstractLookAndFeel.getTheme().getSelectedColors();
                } else if (isRollover && isEnabled) {
                    colorArr = AbstractLookAndFeel.getTheme().getRolloverColors();
                } else {
                    if (JTattooUtilities.isFrameActive(tabPane)) {
                        colorArr = AbstractLookAndFeel.getTheme().getTabColors();
                    } else {
                        colorArr = AbstractLookAndFeel.getTheme().getInActiveColors();
                    }
                }
            } else if (backColor != null) {
                if (isSelected) {
                    colorArr = ColorHelper.createColorArr(ColorHelper.brighter(backColor, 60), backColor, 20);
                } else if (isRollover && isEnabled) {
                    colorArr = ColorHelper.createColorArr(ColorHelper.brighter(backColor, 80),
                            ColorHelper.brighter(backColor, 20), 20);
                } else {
                    colorArr = ColorHelper.createColorArr(ColorHelper.brighter(backColor, 40),
                            ColorHelper.darker(backColor, 10), 20);
                }
            }
        }
        return colorArr;
    }

    private Component getTabComponentAt(final int index) {
        return tabPane.getTabComponentAt(index);
    }

    /**
     * <p>getTabFont.</p>
     *
     * @param isSelected a boolean.
     * @return a {@link java.awt.Font} object.
     */
    protected Font getTabFont(final boolean isSelected) {
        return tabPane.getFont();
    }

    /**
     * <p>Getter for the field tabInsets.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @return a {@link java.awt.Insets} object.
     */
    protected Insets getTabInsets(final int tabPlacement, final int tabIndex) {
        return tabInsets;
    }

    /**
     * <p>getTabLabelShiftX.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getTabLabelShiftX(final int tabPlacement, final int tabIndex, final boolean isSelected) {
        return 0;
    }

    /**
     * <p>getTabLabelShiftY.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getTabLabelShiftY(final int tabPlacement, final int tabIndex, final boolean isSelected) {
        if (!isSelected) {
            if (tabPlacement == TOP) {
                return 1;
            } else if (tabPlacement == BOTTOM) {
                return -1;
            }
        }
        return 0;
    }

    /** {@inheritDoc} */
    @Override
    public int getTabRunCount(final JTabbedPane pane) {
        ensureCurrentLayout();
        return runCount;
    }

    /**
     * <p>getTabRunIndent.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param run a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getTabRunIndent(final int tabPlacement, final int run) {
        return 0;
    }

    /**
     * <p>getTabRunOffset.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabCount a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param forward a boolean.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getTabRunOffset(final int tabPlacement, final int tabCount, final int tabIndex, final boolean forward) {
        final int run = getRunForTab(tabCount, tabIndex);
        final int offset;
        switch (tabPlacement) {
        case LEFT: {
            if (run == 0) {
                offset = forward ? -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth)
                        : -maxTabWidth;

            } else if (run == runCount - 1) {
                offset = forward ? maxTabWidth
                        : calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth;
            } else {
                offset = forward ? maxTabWidth : -maxTabWidth;
            }
            break;
        }
        case RIGHT: {
            if (run == 0) {
                offset = forward ? maxTabWidth
                        : calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth;
            } else if (run == runCount - 1) {
                offset = forward ? -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth)
                        : -maxTabWidth;
            } else {
                offset = forward ? maxTabWidth : -maxTabWidth;
            }
            break;
        }
        case BOTTOM: {
            if (run == 0) {
                offset = forward ? maxTabHeight
                        : calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight;
            } else if (run == runCount - 1) {
                offset = forward ? -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight)
                        : -maxTabHeight;
            } else {
                offset = forward ? maxTabHeight : -maxTabHeight;
            }
            break;
        }
        case TOP:
        default: {
            if (run == 0) {
                offset = forward ? -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight)
                        : -maxTabHeight;
            } else if (run == runCount - 1) {
                offset = forward ? maxTabHeight
                        : calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight;
            } else {
                offset = forward ? maxTabHeight : -maxTabHeight;
            }
        }
        }
        return offset;
    }

    /**
     * <p>Getter for the field tabRunOverlay.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int getTabRunOverlay(final int tabPlacement) {
        return tabRunOverlay;
    }

    /**
     * Returns the text View object required to render stylized text (HTML) for the
     * specified tab or null if no specialized text rendering is needed for this
     * tab. This is provided to support html rendering inside tabs.
     *
     * @param tabIndex the index of the tab
     * @return the text view to render the tab's text or null if no specialized
     *         rendering is required
     * @since 1.4
     */
    protected View getTextViewForTab(final int tabIndex) {
        if (htmlViews != null && htmlViews.size() > tabIndex) {
            return htmlViews.get(tabIndex);
        }
        return null;
    }

    // BaseTabbedPaneUI methods
    /**
     * <p>Getter for the field visibleComponent.</p>
     *
     * @return a {@link java.awt.Component} object.
     */
    protected Component getVisibleComponent() {
        return visibleComponent;
    }

    /**
     * <p>hasInnerBorder.</p>
     *
     * @return a boolean.
     */
    protected boolean hasInnerBorder() {
        return false;
    }

    /**
     * Installs the state needed for mnemonics.
     */
    private void initMnemonics() {
        mnemonicToIndexMap = new HashMap<>();
        mnemonicInputMap = new ComponentInputMapUIResource(tabPane);
        mnemonicInputMap.setParent(SwingUtilities.getUIInputMap(tabPane, JComponent.WHEN_IN_FOCUSED_WINDOW));
        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_IN_FOCUSED_WINDOW, mnemonicInputMap);
    }

    /**
     * Creates and installs any required subcomponents for the JTabbedPane. Invoked
     * by installUI.
     *
     * @since 1.4
     */
    protected void installComponents() {
        if (scrollableTabLayoutEnabled()) {
            if (tabScroller == null) {
                tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
                tabPane.add(tabScroller.viewport);
                tabPane.add(tabScroller.scrollForwardButton);
                tabPane.add(tabScroller.scrollBackwardButton);
                tabPane.add(tabScroller.popupMenuButton);
                tabScroller.tabPanel.setBackground(tabAreaBackground);
            }
        }
        installTabContainer();
    }

    /**
     * <p>installDefaults.</p>
     */
    protected void installDefaults() {
        LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background", "TabbedPane.foreground", "TabbedPane.font");
        tabAreaBackground = UIManager.getColor("TabbedPane.tabAreaBackground");
        selectedColor = UIManager.getColor("TabbedPane.selected");
        textIconGap = UIManager.getInt("TabbedPane.textIconGap");
        tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
        selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
        tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
        contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
        tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
        tabPane.setBorder(UIManager.getBorder("TabbedPane.boder"));
    }

    /**
     * <p>installKeyboardActions.</p>
     */
    protected void installKeyboardActions() {
        InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
        km = getInputMap(JComponent.WHEN_FOCUSED);
        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
        final ActionMap am = getActionMap();
        SwingUtilities.replaceUIActionMap(tabPane, am);
        if (scrollableTabLayoutEnabled()) {
            tabScroller.scrollForwardButton.setAction(am.get("scrollTabsForwardAction"));
            tabScroller.scrollBackwardButton.setAction(am.get("scrollTabsBackwardAction"));
            tabScroller.popupMenuButton.setAction(am.get("scrollTabsPopupMenuAction"));
        }
    }

    /**
     * <p>installListeners.</p>
     */
    protected void installListeners() {
        if ((propertyChangeListener = createPropertyChangeListener()) != null) {
            tabPane.addPropertyChangeListener(propertyChangeListener);
        }
        if ((tabChangeListener = createChangeListener()) != null) {
            tabPane.addChangeListener(tabChangeListener);
        }
        if ((tabComponentListener = createComponentListener()) != null) {
            tabPane.addComponentListener(tabComponentListener);
        }
        if ((mouseListener = createMouseListener()) != null) {
            if (scrollableTabLayoutEnabled()) {
                tabScroller.tabPanel.addMouseListener(mouseListener);

            } else { // WRAP_TAB_LAYOUT
                tabPane.addMouseListener(mouseListener);
            }
        }
        if ((mouseMotionListener = createMouseMotionListener()) != null) {
            if (scrollableTabLayoutEnabled()) {
                tabScroller.tabPanel.addMouseMotionListener(mouseMotionListener);

            } else { // WRAP_TAB_LAYOUT
                tabPane.addMouseMotionListener(mouseMotionListener);
            }
        }
        if ((focusListener = createFocusListener()) != null) {
            tabPane.addFocusListener(focusListener);
        }
        // PENDING(api) : See comment for ContainerHandler
        containerListener = new ContainerHandler();
        tabPane.addContainerListener(containerListener);
        if (tabPane.getTabCount() > 0) {
            htmlViews = createHTMLViewList();
        }
    }

    private void installTabContainer() {
        for ( int i = 0; i < tabPane.getTabCount(); i++) {
            final Component tabComponent = getTabComponentAt(i);
            if (tabComponent != null) {
                if (tabContainer == null) {
                    tabContainer = new TabContainer();
                }
                tabContainer.add(tabComponent);
                addMyPropertyChangeListeners(tabComponent);
            }
        }
        if (tabContainer == null) {
            return;
        }
        if (scrollableTabLayoutEnabled()) {
            tabScroller.tabPanel.add(tabContainer);
        } else {
            tabPane.add(tabContainer);
        }
    }

    // UI Installation/De-installation
    /** {@inheritDoc} */
    @Override
    public void installUI(final JComponent c) {
        this.tabPane = (JTabbedPane) c;
        c.setLayout(createLayoutManager());
        installComponents();
        installDefaults();
        installListeners();
        installKeyboardActions();
    }

    /**
     * <p>isContentOpaque.</p>
     *
     * @return a boolean.
     */
    protected boolean isContentOpaque() {
        if (!tabPane.isOpaque()) {
            if (UIManager.get("TabbedPane.contentOpaque") != null) {
                return UIManager.getBoolean("TabbedPane.contentOpaque");
            }
        }
        return true;
    }

    /**
     * <p>isTabOpaque.</p>
     *
     * @return a boolean.
     */
    protected boolean isTabOpaque() {
        if (!tabPane.isOpaque()) {
            if (UIManager.get("TabbedPane.tabsOpaque") != null) {
                return UIManager.getBoolean("TabbedPane.tabsOpaque");
            }
        }
        return true;
    }

    /**
     * <p>lastTabInRun.</p>
     *
     * @param tabCount a {@link java.lang.Integer} object.
     * @param run a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    protected int lastTabInRun(final int tabCount, final int run) {
        if (runCount == 1) {
            return tabCount - 1;
        }
        final int nextRun = run == runCount - 1 ? 0 : run + 1;
        if (tabRuns[nextRun] == 0) {
            return tabCount - 1;
        }
        return tabRuns[nextRun] - 1;
    }

    /**
     * <p>layoutLabel.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param metrics a {@link java.awt.FontMetrics} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param title a {@link java.lang.String} object.
     * @param icon a {@link javax.swing.Icon} object.
     * @param tabRect a {@link java.awt.Rectangle} object.
     * @param iconRect a {@link java.awt.Rectangle} object.
     * @param textRect a {@link java.awt.Rectangle} object.
     * @param isSelected a boolean.
     */
    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) {
        textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
        final View v = getTextViewForTab(tabIndex);
        if (v != null) {
            tabPane.putClientProperty("html", v);
        }

        SwingUtilities.layoutCompoundLabel(tabPane, metrics, title, icon, SwingConstants.CENTER, SwingConstants.CENTER,
                SwingConstants.CENTER, SwingConstants.TRAILING, tabRect, iconRect, textRect, textIconGap);

        tabPane.putClientProperty("html", null);

        final int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
        final int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
        iconRect.x += xNudge;
        iconRect.y += yNudge;
        textRect.x += xNudge;
        textRect.y += yNudge;
    }

    // Tab Navigation methods
    /**
     * <p>navigateSelectedTab.</p>
     *
     * @param direction a {@link java.lang.Integer} object.
     */
    protected void navigateSelectedTab(final int direction) {
        final int tabPlacement = tabPane.getTabPlacement();
        final int current = tabPane.getSelectedIndex();
        final int tc = tabPane.getTabCount();
        final boolean leftToRight = JTattooUtilities.isLeftToRight(tabPane);

        // If we have no tabs then don't navigate.
        if (tc <= 0) {
            return;
        }

        final int offset;
        switch (tabPlacement) {
        case NEXT:
            selectNextTab(current);
            break;
        case PREVIOUS:
            selectPreviousTab(current);
            break;
        case LEFT:
        case RIGHT:
            switch (direction) {
            case NORTH:
                selectPreviousTabInRun(current);
                break;
            case SOUTH:
                selectNextTabInRun(current);
                break;
            case WEST:
                offset = getTabRunOffset(tabPlacement, tc, current, false);
                selectAdjacentRunTab(tabPlacement, current, offset);
                break;
            case EAST:
                offset = getTabRunOffset(tabPlacement, tc, current, true);
                selectAdjacentRunTab(tabPlacement, current, offset);
                break;
            default:
            }
            break;
        case BOTTOM:
        case TOP:
        default:
            switch (direction) {
            case NORTH:
                offset = getTabRunOffset(tabPlacement, tc, current, false);
                selectAdjacentRunTab(tabPlacement, current, offset);
                break;
            case SOUTH:
                offset = getTabRunOffset(tabPlacement, tc, current, true);
                selectAdjacentRunTab(tabPlacement, current, offset);
                break;
            case EAST:
                if (leftToRight) {
                    selectNextTabInRun(current);
                } else {
                    selectPreviousTabInRun(current);
                }
                break;
            case WEST:
                if (leftToRight) {
                    selectPreviousTabInRun(current);
                } else {
                    selectNextTabInRun(current);
                }
                break;
            default:
            }
        }
    }

    // UI Rendering
    /** {@inheritDoc} */
    @Override
    public void paint(final Graphics g, final JComponent c) {
        final int tc = tabPane.getTabCount();
        if (tabCount != tc) {
            tabCount = tc;
            updateMnemonics();
        }

        final int selectedIndex = tabPane.getSelectedIndex();
        final int tabPlacement = tabPane.getTabPlacement();

        ensureCurrentLayout();

        // Paint content border
        paintContentBorder(g, tabPlacement, selectedIndex, 0, 0, c.getWidth(), c.getHeight());

        // Paint tab area
        // If scrollable tabs are enabled, the tab area will be
        // painted by the scrollable tab panel instead.
        //
        if (!scrollableTabLayoutEnabled()) {
            // WRAP_TAB_LAYOUT
            paintTabArea(g, tabPlacement, selectedIndex);
        }
    }

    /**
     * <p>paintBottomTabBorder.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param g a {@link java.awt.Graphics} object.
     * @param x1 a {@link java.lang.Integer} object.
     * @param y1 a {@link java.lang.Integer} object.
     * @param x2 a {@link java.lang.Integer} object.
     * @param y2 a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    protected void paintBottomTabBorder(final int tabIndex, final Graphics g, final int x1, final int y1, final int x2, final int y2, final boolean isSelected) {
        final int tc = tabPane.getTabCount();
        final int currentRun = getRunForTab(tc, tabIndex);
        final int lastIndex = lastTabInRun(tc, currentRun);
        final int firstIndex = tabRuns[currentRun];
        final boolean leftToRight = JTattooUtilities.isLeftToRight(tabPane);

        final Color loColor = getLoBorderColor(tabIndex);
        final Color hiColor = getHiBorderColor(tabIndex);

        g.setColor(loColor);
        g.drawLine(x1, y1, x1, y2 - GAP);
        g.drawLine(x1, y2 - GAP, x1 + GAP, y2);
        g.drawLine(x1 + GAP, y2, x2, y2);
        g.drawLine(x2, y2, x2, y1);
        g.setColor(hiColor);
        g.drawLine(x1 + 1, y1, x1 + 1, y2 - GAP - 1);
        g.drawLine(x1 + 1, y2 - GAP, x1 + GAP, y2 - 1);

        // paint gap
        final int gapTabIndex = getTabAtLocation(x1 + 2, y2 + 2);
        final Color gapColor = getGapColor(gapTabIndex);

        g.setColor(gapColor);
        for ( int i = 0; i < GAP; i++) {
            g.drawLine(x1, y2 - i, x1 + GAP - i - 1, y2 - i);
        }
        if (leftToRight) {
            if (tabIndex != firstIndex || currentRun != runCount - 1) {
                g.setColor(loColor);
                g.drawLine(x1, y2 - GAP, x1, y2);
            }
        } else {
            if (tabIndex != lastIndex || currentRun != runCount - 1) {
                g.setColor(loColor);
                g.drawLine(x1, y2 - GAP, x1, y2);
            }
        }
    }

    /**
     * <p>paintContentBorder.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param selectedIndex a {@link java.lang.Integer} object.
     * @param x a {@link java.lang.Integer} object.
     * @param y a {@link java.lang.Integer} object.
     * @param w a {@link java.lang.Integer} object.
     * @param h a {@link java.lang.Integer} object.
     */
    protected void paintContentBorder(final Graphics g, final int tabPlacement, final int selectedIndex, final int x, final int y, final int w, final int h) {
        final int tabAreaHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
        final int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);

        // paint the background
        if (tabPane.isOpaque()) {
            final int xt = tabPlacement == RIGHT ? w - tabAreaWidth : 0;
            final int yt = tabPlacement == BOTTOM ? h - tabAreaHeight : 0;
            final int wt = tabPlacement == TOP || tabPlacement == BOTTOM ? w : tabAreaWidth;
            final int ht = tabPlacement == LEFT || tabPlacement == RIGHT ? h : tabAreaHeight;
            g.setColor(tabAreaBackground);
            g.fillRect(xt, yt, wt, ht);
        }
        if (isContentOpaque()) {
            final int xt = tabPlacement == LEFT ? tabAreaWidth : 0;
            final int yt = tabPlacement == TOP ? tabAreaHeight : 0;
            final int wt = tabPlacement == LEFT || tabPlacement == RIGHT ? w - tabAreaWidth : w;
            final int ht = tabPlacement == TOP || tabPlacement == BOTTOM ? h - tabAreaHeight : h;
            g.setColor(tabPane.getBackground());
            g.fillRect(xt, yt, wt, ht);
        }

        Insets bi = new Insets(0, 0, 0, 0);
        if (tabPane.getBorder() != null) {
            bi = tabPane.getBorder().getBorderInsets(tabPane);
        }
        if (hasInnerBorder()) {
            final Color loColor = MetalLookAndFeel.getControlDarkShadow();
            final Color hiColor = MetalLookAndFeel.getControlHighlight();
            g.setColor(loColor);
            switch (tabPlacement) {
            case TOP: {
                final int x1 = x + bi.left - 1;
                final int y1 = y + tabAreaHeight + bi.top - 2;
                final int x2 = x1 + w - bi.left - bi.right + 1;
                final int ws = w - bi.left - bi.right + 1;
                final int hs = h - tabAreaHeight - bi.top - bi.bottom + 2;

                if (tabPane.getBorder() == null) {
                    g.drawLine(x1, y1, x2, y1);
                    g.setColor(hiColor);
                    g.drawLine(x1, y1 + 1, x2, y1 + 1);
                } else {
                    g.drawRect(x1, y1, ws, hs);
                    g.setColor(hiColor);
                    g.drawLine(x1 + 1, y1 + 1, x2 - 1, y1 + 1);
                }
                break;
            }
            case LEFT: {
                final int x1 = x + tabAreaWidth + bi.left - 2;
                final int y1 = y + bi.top - 1;
                // int x2 = w - bi.right;
                final int y2 = y1 + h - bi.top - bi.bottom + 1;
                final int ws = w - tabAreaWidth - bi.left - bi.right + 2;
                final int hs = h - bi.top - bi.bottom + 1;

                if (tabPane.getBorder() == null) {
                    g.drawLine(x1, y1, x1, y2);
                    g.setColor(hiColor);
                    g.drawLine(x1 + 1, y1, x1 + 1, y2);
                } else {
                    g.drawRect(x1, y1, ws, hs);
                    g.setColor(hiColor);
                    g.drawLine(x1 + 1, y1 + 1, x1 + 1, y2 - 1);
                }
                break;
            }
            case BOTTOM: {
                final int x1 = x + bi.left - 1;
                final int y1 = y + bi.top - 1;
                final int x2 = x1 + w - bi.left - bi.right + 1;
                final int y2 = h - tabAreaHeight - bi.bottom;
                final int ws = w - bi.left - bi.right + 1;
                final int hs = h - tabAreaHeight - bi.top - bi.bottom + 2;

                if (tabPane.getBorder() == null) {
                    g.drawLine(x1, y2, x2, y2);
                } else {
                    g.drawRect(x1, y1, ws, hs);
                }
                break;
            }
            case RIGHT: {
                final int x1 = x + bi.left - 1;
                final int y1 = y + bi.top - 1;
                final int x2 = w - tabAreaWidth - bi.right + 1;
                final int y2 = y1 + h - bi.top - bi.bottom + 1;
                final int ws = w - tabAreaWidth - bi.left - bi.right + 2;
                final int hs = h - bi.top - bi.bottom + 1;

                if (tabPane.getBorder() == null) {
                    g.drawLine(x2, y1, x2, y2);
                } else {
                    g.drawRect(x1, y1, ws, hs);
                }
                break;
            }
            }
        } else {
            final int sepHeight = tabAreaInsets.bottom;
            if (sepHeight > 0) {
                switch (tabPlacement) {
                case TOP: {
                    final Color[] colors = getContentBorderColors(tabPlacement);
                    final int ys = y + tabAreaHeight - sepHeight + bi.top;
                    for ( int i = 0; i < colors.length; i++) {
                        g.setColor(colors[i]);
                        g.drawLine(x, ys + i, x + w, ys + i);
                    }
                    break;
                }
                case LEFT: {
                    final Color[] colors = getContentBorderColors(tabPlacement);
                    final int xs = x + tabAreaWidth - sepHeight + bi.left;
                    for ( int i = 0; i < colors.length; i++) {
                        g.setColor(colors[i]);
                        g.drawLine(xs + i, y, xs + i, y + h);
                    }
                    break;
                }
                case BOTTOM: {
                    final Color[] colors = getContentBorderColors(tabPlacement);
                    final int ys = y + h - tabAreaHeight - bi.bottom;
                    for (int i = 0; i < colors.length; i++) {
                        g.setColor(colors[i]);
                        g.drawLine(x, ys + i, x + w, ys + i);
                    }
                    break;
                }
                case RIGHT: {
                    final Color[] colors = getContentBorderColors(tabPlacement);
                    final int xs = x + w - tabAreaWidth - bi.right;
                    for ( int i = 0; i < colors.length; i++) {
                        g.setColor(colors[i]);
                        g.drawLine(xs + i, y, xs + i, y + h);
                    }
                    break;
                }
                }
            }
        }
    }

    /*
     * If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
     * indicating the tab is cropped in the viewport display
     */
    private void paintCroppedTabEdge(final Graphics g, final int tabPlacement, final int tabIndex, final int x, final int y) {
        g.setColor(Color.gray);
        switch (tabPlacement) {
        case LEFT:
        case RIGHT:
            int xx = x;
            while (xx <= x + rects[tabIndex].width) {
                for ( int i = 0; i < xCropLen.length; i += 2) {
                    g.drawLine(xx + yCropLen[i], y - xCropLen[i], xx + yCropLen[i + 1] - 1, y - xCropLen[i + 1]);
                }
                xx += CROP_SEGMENT;
            }
            break;
        case TOP:
        case BOTTOM:
        default:
            int yy = y;
            while (yy <= y + rects[tabIndex].height) {
                for ( int i = 0; i < xCropLen.length; i += 2) {
                    g.drawLine(x - xCropLen[i], yy + yCropLen[i], x - xCropLen[i + 1], yy + yCropLen[i + 1] - 1);
                }
                yy += CROP_SEGMENT;
            }
        }
    }

    /**
     * <p>paintFocusIndicator.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param rects an array of {@link java.awt.Rectangle} objects.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param iconRect a {@link java.awt.Rectangle} object.
     * @param textRect a {@link java.awt.Rectangle} object.
     * @param isSelected a boolean.
     */
    protected void paintFocusIndicator(final Graphics g, final int tabPlacement, final Rectangle[] rects, final int tabIndex,
                                       final Rectangle iconRect, final Rectangle textRect, final boolean isSelected) {
        if (tabPane.isRequestFocusEnabled() && tabPane.hasFocus() && isSelected && tabIndex >= 0
                && textRect.width > 8) {
            g.setColor(AbstractLookAndFeel.getTheme().getFocusColor());
            BasicGraphicsUtils.drawDashedRect(g, textRect.x - 4, textRect.y + 1, textRect.width + 8, textRect.height);
        }
    }

    /**
     * <p>paintIcon.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param icon a {@link javax.swing.Icon} object.
     * @param iconRect a {@link java.awt.Rectangle} object.
     * @param isSelected a boolean.
     */
    protected void paintIcon(final Graphics g, final int tabPlacement, final int tabIndex, final Icon icon, final Rectangle iconRect,
                             final boolean isSelected) {
        if (icon != null) {
            icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
        }
    }

    /**
     * <p>paintLeftTabBorder.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param g a {@link java.awt.Graphics} object.
     * @param x1 a {@link java.lang.Integer} object.
     * @param y1 a {@link java.lang.Integer} object.
     * @param x2 a {@link java.lang.Integer} object.
     * @param y2 a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    protected void paintLeftTabBorder(final int tabIndex, final Graphics g, final int x1, final int y1, final int x2, final int y2, final boolean isSelected) {
        final Graphics2D g2D = (Graphics2D) g;

        final int tc = tabPane.getTabCount();
        final int currentRun = getRunForTab(tc, tabIndex);
        final int lastIndex = lastTabInRun(tc, currentRun);
        final int firstIndex = tabRuns[currentRun];

        Color loColor = getLoBorderColor(tabIndex);
        Color hiColor = getHiBorderColor(tabIndex);

        g.setColor(hiColor);
        final Composite savedComposite = g2D.getComposite();
        final AlphaComposite alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f);
        g2D.setComposite(alpha);
        g.drawLine(x1 + GAP + 1, y1 + 1, x2 - 1, y1 + 1);
        g.drawLine(x1 + GAP, y1 + 1, x1 + 1, y1 + GAP);
        g.drawLine(x1 + 1, y1 + GAP + 1, x1 + 1, y2 - 1);
        g2D.setComposite(savedComposite);

        g.setColor(loColor);
        g.drawLine(x1 + GAP, y1, x2 - 1, y1);
        g.drawLine(x1 + GAP, y1, x1, y1 + GAP);
        g.drawLine(x1, y1 + GAP, x1, y2);
        g.drawLine(x1 + GAP, y2, x2 - 1, y2);
        if (tabIndex == lastIndex) {
            g.drawLine(x1, y2, x1 + GAP, y2);
        }
        // paint gap
        final int gapTabIndex = getTabAtLocation(x1 + 2, y1 - 2);
        final Color gapColor = getGapColor(gapTabIndex);
        g.setColor(gapColor);
        for ( int i = 0; i < GAP; i++) {
            g.drawLine(x1, y1 + i, x1 + GAP - i - 1, y1 + i);
        }

        if (tabIndex != firstIndex || currentRun != runCount - 1) {
            loColor = getLoBorderColor(gapTabIndex);
            g.setColor(loColor);
            g.drawLine(x1, y1, x1, y1 + GAP - 1);
            if (tabIndex != firstIndex) {
                g2D.setComposite(alpha);
                hiColor = getHiBorderColor(gapTabIndex);
                g.setColor(hiColor);
                g.drawLine(x1 + 1, y1, x1 + 1, y1 + GAP - 2);
                g2D.setComposite(savedComposite);
            }
        }
    }

    /**
     * <p>paintRightTabBorder.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param g a {@link java.awt.Graphics} object.
     * @param x1 a {@link java.lang.Integer} object.
     * @param y1 a {@link java.lang.Integer} object.
     * @param x2 a {@link java.lang.Integer} object.
     * @param y2 a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    protected void paintRightTabBorder(final int tabIndex, final Graphics g, final int x1, final int y1, final int x2, final int y2, final boolean isSelected) {
        final Graphics2D g2D = (Graphics2D) g;

        final int tc = tabPane.getTabCount();
        final int currentRun = getRunForTab(tc, tabIndex);
        final int lastIndex = lastTabInRun(tc, currentRun);
        final int firstIndex = tabRuns[currentRun];

        Color loColor = getLoBorderColor(tabIndex);
        final Color hiColor = getHiBorderColor(tabIndex);

        final Composite savedComposite = g2D.getComposite();
        final AlphaComposite alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f);
        g2D.setComposite(alpha);
        g.setColor(hiColor);
        g.drawLine(x1, y1 + 1, x2 - GAP - 1, y1 + 1);
        g.drawLine(x2 - GAP, y1 + 1, x2 - 1, y1 + GAP);
        g2D.setComposite(savedComposite);

        g.setColor(loColor);
        g.drawLine(x1, y1, x2 - GAP, y1);
        g.drawLine(x2 - GAP, y1, x2, y1 + GAP);
        g.drawLine(x2, y1 + GAP, x2, y2);
        if (tabIndex == lastIndex) {
            g.drawLine(x2, y2, x1, y2);
        }

        // paint gap
        final int gapTabIndex = getTabAtLocation(x1 + 2, y1 - 2);
        final Color gapColor = getGapColor(gapTabIndex);
        g.setColor(gapColor);
        for ( int i = 0; i < GAP; i++) {
            g.drawLine(x2 - GAP + i + 1, y1 + i, x2, y1 + i);
        }

        if (tabIndex != firstIndex || currentRun != runCount - 1) {
            loColor = getLoBorderColor(gapTabIndex);
            g.setColor(loColor);
            g.drawLine(x2, y1, x2, y1 + GAP - 1);
        }
    }

    /**
     * <p>paintRoundedBottomTabBorder.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param g a {@link java.awt.Graphics} object.
     * @param x1 a {@link java.lang.Integer} object.
     * @param y1 a {@link java.lang.Integer} object.
     * @param x2 a {@link java.lang.Integer} object.
     * @param y2 a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    protected void paintRoundedBottomTabBorder(final int tabIndex, final Graphics g, final int x1, final int y1, final int x2, final int y2,
                                               final boolean isSelected) {
        final Graphics2D g2D = (Graphics2D) g;
        final Object savedRederingHint = g2D.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        final Color loColor = getLoBorderColor(tabIndex);
        final int d = 2 * GAP;
        g.setColor(loColor);
        g.drawLine(x1 + GAP, y2, x2 - GAP, y2);
        g.drawArc(x1, y2 - d, d, d, 180, 90);
        g.drawArc(x2 - d, y2 - d, d, d, -90, 90);
        g.drawLine(x1, y1, x1, y2 - GAP - 1);
        g.drawLine(x2, y1, x2, y2 - GAP - 1);

        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, savedRederingHint);
    }

    /**
     * <p>paintRoundedTopTabBorder.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param g a {@link java.awt.Graphics} object.
     * @param x1 a {@link java.lang.Integer} object.
     * @param y1 a {@link java.lang.Integer} object.
     * @param x2 a {@link java.lang.Integer} object.
     * @param y2 a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    protected void paintRoundedTopTabBorder(final int tabIndex, final Graphics g, final int x1, final int y1, final int x2, final int y2,
                                            final boolean isSelected) {
        final Graphics2D g2D = (Graphics2D) g;
        final Object savedRederingHint = g2D.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        final Color borderColor = getLoBorderColor(tabIndex);
        g.setColor(borderColor);
        final int d = 2 * GAP;
        if (isSelected) {
            g.drawLine(x1 + GAP, y1, x2 - GAP, y1);
            g.drawArc(x1, y1, d, d, 90, 90);
            g.drawArc(x2 - d, y1, d, d, 0, 90);
            g.drawLine(x1, y1 + GAP + 1, x1, y2);
            g.drawLine(x2, y1 + GAP + 1, x2, y2);
        } else {
            g.drawLine(x1 + GAP, y1, x2 - GAP, y1);
            g.drawArc(x1, y1, d, d, 90, 90);
            g.drawArc(x2 - d, y1, d, d, 0, 90);
            g.drawLine(x1, y1 + GAP + 1, x1, y2 - 1);
            g.drawLine(x2, y1 + GAP + 1, x2, y2 - 1);
        }
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, savedRederingHint);
    }

    /**
     * <p>paintScrollContentBorder.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param selectedIndex a {@link java.lang.Integer} object.
     * @param x a {@link java.lang.Integer} object.
     * @param y a {@link java.lang.Integer} object.
     * @param w a {@link java.lang.Integer} object.
     * @param h a {@link java.lang.Integer} object.
     */
    protected void paintScrollContentBorder(final Graphics g, final int tabPlacement, final int selectedIndex, final int x, final int y, final int w,
                                            final int h) {
        Insets bi = new Insets(0, 0, 0, 0);
        if (tabPane.getBorder() != null) {
            bi = tabPane.getBorder().getBorderInsets(tabPane);
        }
        switch (tabPane.getTabPlacement()) {
        case TOP:
            paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex(), x, y - bi.top, w, h);
            break;
        case BOTTOM:
            paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex(), x, y + bi.bottom, w, h);
            break;
        case LEFT:
            paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex(), x - bi.left, y, w, h);
            break;
        case RIGHT:
            paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex(), x + bi.right, y, w, h);
            break;
        default:
            break;
        }
    }

    /**
     * <p>paintTab.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param rects an array of {@link java.awt.Rectangle} objects.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param iconRect a {@link java.awt.Rectangle} object.
     * @param textRect a {@link java.awt.Rectangle} object.
     */
    protected void paintTab(final Graphics g, final int tabPlacement, final Rectangle[] rects, final int tabIndex, final Rectangle iconRect,
                            final Rectangle textRect) {
        final Rectangle tabRect = rects[tabIndex];
        final int selectedIndex = tabPane.getSelectedIndex();
        final boolean isSelected = selectedIndex == tabIndex;
        Graphics2D g2D = null;
        Polygon cropShape = null;
        Shape savedClip = null;
        int cropx = 0;
        int cropy = 0;

        if (scrollableTabLayoutEnabled()) {
            if (g instanceof Graphics2D) {
                g2D = (Graphics2D) g;

                // Render visual for cropped tab edge...
                final Rectangle viewRect = tabScroller.viewport.getViewRect();
                final int cropline;
                switch (tabPlacement) {
                case LEFT:
                case RIGHT:
                    cropline = viewRect.y + viewRect.height;
                    if (tabRect.y < cropline && tabRect.y + tabRect.height > cropline) {
                        cropShape = createCroppedTabClip(tabPlacement, tabRect, cropline);
                        cropx = tabRect.x;
                        cropy = cropline - 1;
                    }
                    break;
                case TOP:
                case BOTTOM:
                default:
                    cropline = viewRect.x + viewRect.width;
                    if (tabRect.x < cropline && tabRect.x + tabRect.width > cropline) {
                        cropShape = createCroppedTabClip(tabPlacement, tabRect, cropline);
                        cropx = cropline - 1;
                        cropy = tabRect.y;
                    }
                }
                if (cropShape != null) {
                    savedClip = g2D.getClip();
                    g2D.clip(cropShape);
                }
            }
        }

        paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y, tabRect.width, tabRect.height, isSelected);
        paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y, tabRect.width, tabRect.height, isSelected);

        try {
            final boolean doPaintContent = getTabComponentAt(tabIndex) == null;
            if (doPaintContent) {
                final String title = tabPane.getTitleAt(tabIndex);
                final Font font = getTabFont(isSelected);
                final FontMetrics fm = JTattooUtilities.getFontMetrics(tabPane, g, font);
                final Icon icon = getIconForTab(tabIndex);

                layoutLabel(tabPlacement, fm, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected);
                paintText(g, tabPlacement, font, fm, tabIndex, title, textRect, isSelected);
                paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
            }
            paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect, textRect, isSelected);
        } catch (final Exception e) {
            log.info(e.getMessage());
        }

        if (cropShape != null) {
            paintCroppedTabEdge(g, tabPlacement, tabIndex, cropx, cropy);
            if (g2D != null && savedClip != null) {
                g2D.setClip(savedClip);
            }
        }
    }

    /**
     * Paints the tabs in the tab area. Invoked by paint(). The graphics parameter
     * must be a valid Graphics object. Tab placement may be either:
     * JTabbedPane.TOP, JTabbedPane.BOTTOM,
     * JTabbedPane.LEFT, or JTabbedPane.RIGHT. The
     * selected index must be a valid tabbed pane tab index (0 to tab count - 1,
     * inclusive) or -1 if no tab is currently selected. The handling of invalid
     * parameters is unspecified.
     *
     * @param g             the graphics object to use for rendering
     * @param tabPlacement  the placement for the tabs within the JTabbedPane
     * @param selectedIndex the tab index of the selected component
     * @since 1.4
     */
    protected void paintTabArea(final Graphics g, final int tabPlacement, final int selectedIndex) {
        final int tc = tabPane.getTabCount();
        final Rectangle iconRect = new Rectangle();
        final Rectangle textRect = new Rectangle();
        final Shape savedClip = g.getClip();
        final Rectangle clipRect = g.getClipBounds();
        // Dirty trick to fix clipping problem
        if (scrollableTabLayoutEnabled() && tabScroller.scrollBackwardButton.isVisible()) {
            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                g.setClip(clipRect.x, clipRect.y, clipRect.width + 1, clipRect.height);
            } else {
                g.setClip(clipRect.x, clipRect.y, clipRect.width, clipRect.height + 1);
            }
        }
        // Paint tabRuns of tabs from back to front
        for ( int i = runCount - 1; i >= 0; i--) {
            final int start = tabRuns[i];
            final int next = tabRuns[i == runCount - 1 ? 0 : i + 1];
            final int end = next != 0 ? next - 1 : tc - 1;
            for ( int j = start; j <= end; j++) {
                if (rects[j].intersects(clipRect)) {
                    paintTab(g, tabPlacement, rects, j, iconRect, textRect);
                }
            }
        }

        // Paint selected tab if its in the front run
        // since it may overlap other tabs
        if (selectedIndex >= 0 && selectedIndex < rects.length && getRunForTab(tc, selectedIndex) == 0) {
            if (rects[selectedIndex].intersects(clipRect)) {
                paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
            }
        }
        g.setClip(savedClip);
    }

    /**
     * <p>paintTabBackground.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param x a {@link java.lang.Integer} object.
     * @param y a {@link java.lang.Integer} object.
     * @param w a {@link java.lang.Integer} object.
     * @param h a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    protected void paintTabBackground(final Graphics g, final int tabPlacement, final int tabIndex, final int x, final int y, final int w, final int h,
            final boolean isSelected) {
        if (isTabOpaque() || isSelected) {
            final Graphics2D g2D = (Graphics2D) g;
            final Shape savedClip = g.getClip();
            Area orgClipArea = new Area(new Rectangle2D.Double(x, y, w, h));
            if (savedClip != null) {
                orgClipArea = new Area(savedClip);
            }
            final Color[] colorArr = getTabColors(tabIndex, isSelected, tabIndex == rolloverIndex);
            final int d = 2 * GAP;
            switch (tabPlacement) {
            case TOP:
            default:
                final Area clipArea = new Area(new RoundRectangle2D.Double(x, y, w, h + 4, d, d));
                final Area rectArea;
                if (isSelected) {
                    rectArea = new Area(new Rectangle2D.Double(x, y, w, h + 2));
                } else {
                    rectArea = new Area(new Rectangle2D.Double(x, y, w, h));
                }
                clipArea.intersect(rectArea);
                clipArea.intersect(orgClipArea);
                g2D.setClip(clipArea);
                JTattooUtilities.fillHorGradient(g, colorArr, x, y, w, h + 4);
                g2D.setClip(savedClip);
                break;
            case LEFT:
                if (isSelected) {
                    JTattooUtilities.fillHorGradient(g, colorArr, x + 1, y + 1, w + 1, h - 1);
                } else {
                    JTattooUtilities.fillHorGradient(g, colorArr, x + 1, y + 1, w - 1, h - 1);
                }
                break;
            case BOTTOM:
                final Area clipA = new Area(new RoundRectangle2D.Double(x, y - 4, w, h + 4, d, d));
                final Area rectA;
                if (isSelected) {
                    rectA = new Area(new Rectangle2D.Double(x, y - 2, w, h + 1));
                } else {
                    rectA = new Area(new Rectangle2D.Double(x, y, w, h));
                }
                clipA.intersect(rectA);
                clipA.intersect(orgClipArea);
                g2D.setClip(clipA);
                JTattooUtilities.fillHorGradient(g, colorArr, x, y - 4, w, h + 4);
                g2D.setClip(savedClip);
                break;
            case RIGHT:
                if (isSelected) {
                    JTattooUtilities.fillHorGradient(g, colorArr, x - 2, y + 1, w + 2, h - 1);
                } else {
                    JTattooUtilities.fillHorGradient(g, colorArr, x, y + 1, w + 1, h - 1);
                }
                break;
            }
        }
    }

    /*
     * this function draws the border around each tab note that this function does
     * now draw the background of the tab. that is done elsewhere
     */
    /**
     * <p>paintTabBorder.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param x a {@link java.lang.Integer} object.
     * @param y a {@link java.lang.Integer} object.
     * @param w a {@link java.lang.Integer} object.
     * @param h a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    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) {
        final int x2 = x + w;
        final int y2 = y + h;
        switch (tabPlacement) {
        case LEFT:
            paintLeftTabBorder(tabIndex, g, x, y, x2, y2, isSelected);
            break;
        case RIGHT:
            paintRightTabBorder(tabIndex, g, x, y, x2, y2, isSelected);
            break;
        case BOTTOM:
            if (roundedTabs) {
                paintRoundedBottomTabBorder(tabIndex, g, x, y, x2, y2 - 1, isSelected);
            } else {
                paintBottomTabBorder(tabIndex, g, x, y, x2, y2 - 1, isSelected);
            }
            break;
        case TOP:
        default:
            if (roundedTabs) {
                paintRoundedTopTabBorder(tabIndex, g, x, y, x2, y2, isSelected);
            } else {
                paintTopTabBorder(tabIndex, g, x, y, x2, y2, isSelected);
            }
        }
    }

    /**
     * <p>paintText.</p>
     *
     * @param g a {@link java.awt.Graphics} object.
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param font a {@link java.awt.Font} object.
     * @param metrics a {@link java.awt.FontMetrics} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param title a {@link java.lang.String} object.
     * @param textRect a {@link java.awt.Rectangle} object.
     * @param isSelected a boolean.
     */
    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) {
        g.setFont(font);
        final View v = getTextViewForTab(tabIndex);
        if (v != null) {
            // html
            final Graphics2D g2D = (Graphics2D) g;
            Object savedRenderingHint = null;
            if (AbstractLookAndFeel.getTheme().isTextAntiAliasingOn()) {
                savedRenderingHint = g2D.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
                g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                        AbstractLookAndFeel.getTheme().getTextAntiAliasingHint());
            }
            v.paint(g, textRect);
            if (AbstractLookAndFeel.getTheme().isTextAntiAliasingOn()) {
                g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, savedRenderingHint);
            }
        } else {
            // plain text
            final int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);

            if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
                if (isSelected) {
                    final Color backColor = tabPane.getBackgroundAt(tabIndex);
                    if (backColor instanceof UIResource) {
                        g.setColor(AbstractLookAndFeel.getTabSelectionForegroundColor());
                    } else {
                        g.setColor(tabPane.getForegroundAt(tabIndex));
                    }
                } else {
                    if (tabIndex == rolloverIndex) {
                        g.setColor(AbstractLookAndFeel.getTheme().getRolloverForegroundColor());
                    } else {
                        g.setColor(tabPane.getForegroundAt(tabIndex));
                    }
                }
                JTattooUtilities.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x,
                        textRect.y + metrics.getAscent());

            } else { // tab disabled
                g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
                JTattooUtilities.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x,
                        textRect.y + metrics.getAscent());
                g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
                JTattooUtilities.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x - 1,
                        textRect.y + metrics.getAscent() - 1);
            }
        }
    }

    /**
     * <p>paintTopTabBorder.</p>
     *
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param g a {@link java.awt.Graphics} object.
     * @param x1 a {@link java.lang.Integer} object.
     * @param y1 a {@link java.lang.Integer} object.
     * @param x2 a {@link java.lang.Integer} object.
     * @param y2 a {@link java.lang.Integer} object.
     * @param isSelected a boolean.
     */
    protected void paintTopTabBorder(final int tabIndex, final Graphics g, final int x1, final int y1, final int x2, final int y2, final boolean isSelected) {
        final int tc = tabPane.getTabCount();
        final int currentRun = getRunForTab(tc, tabIndex);
        final int lastIndex = lastTabInRun(tc, currentRun);
        final int firstIndex = tabRuns[currentRun];
        final boolean leftToRight = JTattooUtilities.isLeftToRight(tabPane);

        final Color loColor = getLoBorderColor(tabIndex);
        final Color hiColor = getHiBorderColor(tabIndex);

        g.setColor(loColor);
        g.drawLine(x1 + GAP, y1, x2, y1);
        g.drawLine(x1 + GAP, y1, x1, y1 + GAP);
        g.drawLine(x1, y1 + GAP + 1, x1, y2);
        g.drawLine(x2, y1, x2, y2);
        g.setColor(hiColor);
        g.drawLine(x1 + GAP + 1, y1 + 1, x2 - 1, y1 + 1);
        g.drawLine(x1 + GAP + 1, y1 + 1, x1 + 1, y1 + GAP + 1);
        g.drawLine(x1 + 1, y1 + GAP + 1, x1 + 1, y2 - 1);

        // paint gap
        final int gapTabIndex = getTabAtLocation(x1 + 2, y1 - 2);
        final Color gapColor = getGapColor(gapTabIndex);
        g.setColor(gapColor);
        for ( int i = 0; i < GAP; i++) {
            g.drawLine(x1, y1 + i, x1 + GAP - i - 1, y1 + i);
        }

        if (leftToRight) {
            if (tabIndex != firstIndex || currentRun != runCount - 1) {
                g.setColor(loColor);
                g.drawLine(x1, y1, x1, y1 + GAP);
            }
            if (!isSelected && tabIndex == firstIndex && currentRun != runCount - 1) {
                g.setColor(hiColor);
                g.drawLine(x1 + 1, y1, x1 + 1, y1 + GAP - 2);
            }
        } else {
            if (tabIndex != lastIndex || currentRun != runCount - 1) {
                g.setColor(loColor);
                g.drawLine(x1, y1, x1, y1 + GAP);
            }
        }
    }

    private void removeMyPropertyChangeListeners(final Component component) {
        final PropertyChangeListener[] listeners = component.getPropertyChangeListeners();
        for (final PropertyChangeListener listener : listeners) {
            if (listener instanceof MyTabComponentListener) {
                component.removePropertyChangeListener(listener);
            }
        }
        if (component instanceof Container) {
            final Container container = (Container) component;
            for ( int i = 0; i < container.getComponentCount(); i++) {
                final Component c = container.getComponent(i);
                removeMyPropertyChangeListeners(c);
            }
        }
    }

    /**
     * <p>requestFocusForVisibleComponent.</p>
     *
     * @return a boolean.
     */
    protected boolean requestFocusForVisibleComponent() {
        final Component vc = getVisibleComponent();
        if (vc.isFocusable()) {
            vc.requestFocus();
            return true;
        } else if (vc instanceof JComponent) {
            return ((JComponent) vc).requestDefaultFocus();
        }
        return false;
    }

    /**
     * Resets the mnemonics bindings to an empty state.
     */
    private void resetMnemonics() {
        if (mnemonicToIndexMap != null) {
            mnemonicToIndexMap.clear();
            mnemonicInputMap.clear();
        }
    }

    /*
     * In an attempt to preserve backward compatibility for programs which have
     * extended BaseTabbedPaneUI to do their own layout, the UI uses the installed
     * layoutManager (and not tabLayoutPolicy) to determine if scrollTabLayout is
     * enabled.
     */
    /**
     * <p>scrollableTabLayoutEnabled.</p>
     *
     * @return a boolean.
     */
    protected boolean scrollableTabLayoutEnabled() {
        return tabPane.getLayout() instanceof TabbedPaneScrollLayout;
    }

    /**
     * <p>selectAdjacentRunTab.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param tabIndex a {@link java.lang.Integer} object.
     * @param offset a {@link java.lang.Integer} object.
     */
    protected void selectAdjacentRunTab(final int tabPlacement, final int tabIndex, final int offset) {
        if (runCount < 2) {
            return;
        }
        int newIndex;
        final Rectangle r = rects[tabIndex];
        switch (tabPlacement) {
        case LEFT:
        case RIGHT:
            newIndex = getTabAtLocation(r.x + r.width / 2 + offset, r.y + r.height / 2);
            break;
        case BOTTOM:
        case TOP:
        default:
            newIndex = getTabAtLocation(r.x + r.width / 2, r.y + r.height / 2 + offset);
        }
        if (newIndex != -1) {
            while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
                newIndex = getNextTabIndex(newIndex);
            }
            tabPane.setSelectedIndex(newIndex);
        }
    }

    /**
     * <p>selectNextTab.</p>
     *
     * @param current a {@link java.lang.Integer} object.
     */
    protected void selectNextTab(final int current) {
        int tabIndex = getNextTabIndex(current);
        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
            tabIndex = getNextTabIndex(tabIndex);
        }
        tabPane.setSelectedIndex(tabIndex);
    }

    /**
     * <p>selectNextTabInRun.</p>
     *
     * @param current a {@link java.lang.Integer} object.
     */
    protected void selectNextTabInRun(final int current) {
        final int tc = tabPane.getTabCount();
        int tabIndex = getNextTabIndexInRun(tc, current);
        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
            tabIndex = getNextTabIndexInRun(tc, tabIndex);
        }
        tabPane.setSelectedIndex(tabIndex);
    }

    /**
     * <p>selectPreviousTab.</p>
     *
     * @param current a {@link java.lang.Integer} object.
     */
    protected void selectPreviousTab(final int current) {
        int tabIndex = getPreviousTabIndex(current);
        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
            tabIndex = getPreviousTabIndex(tabIndex);
        }
        tabPane.setSelectedIndex(tabIndex);
    }

    /**
     * <p>selectPreviousTabInRun.</p>
     *
     * @param current a {@link java.lang.Integer} object.
     */
    protected void selectPreviousTabInRun(final int current) {
        final int tc = tabPane.getTabCount();
        int tabIndex = getPreviousTabIndexInRun(tc, current);
        while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
            tabIndex = getPreviousTabIndexInRun(tc, tabIndex);
        }
        tabPane.setSelectedIndex(tabIndex);
    }

    /**
     * <p>Setter for the field visibleComponent.</p>
     *
     * @param component a {@link java.awt.Component} object.
     */
    protected void setVisibleComponent(final Component component) {
        if (visibleComponent != null && visibleComponent != component && visibleComponent.getParent() == tabPane) {
            visibleComponent.setVisible(false);
        }
        if (component != null && !component.isVisible()) {
            component.setVisible(true);
        }
        visibleComponent = component;
    }

    /**
     * <p>shouldPadTabRun.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @param run a {@link java.lang.Integer} object.
     * @return a boolean.
     */
    protected boolean shouldPadTabRun(final int tabPlacement, final int run) {
        return runCount > 1;
    }

    /**
     * <p>shouldRotateTabRuns.</p>
     *
     * @param tabPlacement a {@link java.lang.Integer} object.
     * @return a boolean.
     */
    protected boolean shouldRotateTabRuns(final int tabPlacement) {
        return true;
    }

    /*
     * Returns the tab index which intersects the specified point in the
     * JTabbedPane's coordinate space.
     */
    /** {@inheritDoc} */
    @Override
    public int tabForCoordinate(final JTabbedPane pane, final int x, final int y) {
        ensureCurrentLayout();
        final Point p = new Point(x, y);

        if (scrollableTabLayoutEnabled()) {
            translatePointToTabPanel(x, y, p);
        }
        final int tc = tabPane.getTabCount();
        for ( int i = 0; i < tc; i++) {
            if (rects[i].contains(p.x, p.y)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Returns a point which is translated from the specified point in the
     * JTabbedPane's coordinate space to the coordinate space of the
     * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY.
     */
    private Point translatePointToTabPanel(final int srcx, final int srcy, final Point dest) {
        final Point vpp = tabScroller.viewport.getLocation();
        final Point viewp = tabScroller.viewport.getViewPosition();
        dest.x = srcx - vpp.x + viewp.x;
        dest.y = srcy - vpp.y + viewp.y;
        return dest;
    }

    /**
     * Removes any installed subcomponents from the JTabbedPane. Invoked by
     * uninstallUI.
     *
     * @since 1.4
     */
    protected void uninstallComponents() {
        uninstallTabContainer();
        if (scrollableTabLayoutEnabled()) {
            tabPane.remove(tabScroller.viewport);
            tabPane.remove(tabScroller.scrollForwardButton);
            tabPane.remove(tabScroller.scrollBackwardButton);
            tabPane.remove(tabScroller.popupMenuButton);
            tabScroller = null;
        }
    }

    /**
     * <p>uninstallDefaults.</p>
     */
    protected void uninstallDefaults() {
        tabInsets = null;
        selectedTabPadInsets = null;
        tabAreaInsets = null;
        contentBorderInsets = null;
    }

    /**
     * <p>uninstallKeyboardActions.</p>
     */
    protected void uninstallKeyboardActions() {
        SwingUtilities.replaceUIActionMap(tabPane, null);
        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, null);
        SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_IN_FOCUSED_WINDOW, null);
    }

    /**
     * <p>uninstallListeners.</p>
     */
    protected void uninstallListeners() {
        if (mouseListener != null) {
            if (scrollableTabLayoutEnabled()) {
                // SCROLL_TAB_LAYOUT
                tabScroller.tabPanel.removeMouseListener(mouseListener);
            } else {
                // WRAP_TAB_LAYOUT
                tabPane.removeMouseListener(mouseListener);
            }
            mouseListener = null;
        }
        if (mouseMotionListener != null) {
            if (scrollableTabLayoutEnabled()) {
                // SCROLL_TAB_LAYOUT
                tabScroller.tabPanel.removeMouseMotionListener(mouseMotionListener);
            } else {
                // WRAP_TAB_LAYOUT
                tabPane.removeMouseMotionListener(mouseMotionListener);
            }
            mouseMotionListener = null;
        }
        if (focusListener != null) {
            tabPane.removeFocusListener(focusListener);
            focusListener = null;
        }

        // PENDING(api): See comment for ContainerHandler
        if (containerListener != null) {
            tabPane.removeContainerListener(containerListener);
            containerListener = null;
            if (htmlViews != null) {
                htmlViews.clear();
                htmlViews = null;
            }
        }
        if (tabChangeListener != null) {
            tabPane.removeChangeListener(tabChangeListener);
            tabChangeListener = null;
        }
        if (tabComponentListener != null) {
            tabPane.removeComponentListener(tabComponentListener);
            tabChangeListener = null;
        }
        if (propertyChangeListener != null) {
            tabPane.removePropertyChangeListener(propertyChangeListener);
            propertyChangeListener = null;
        }
    }

    private void uninstallTabContainer() {
        if (tabContainer == null) {
            return;
        }
        // Remove all the tabComponents, making sure not to notify the tabbedpane.
        tabContainer.notifyTabbedPane = false;
        for ( int i = 0; i < tabContainer.getComponentCount(); i++) {
            final Component c = tabContainer.getComponent(i);
            removeMyPropertyChangeListeners(c);
        }
        tabContainer.removeAll();
        if (scrollableTabLayoutEnabled()) {
            tabScroller.tabPanel.remove(tabContainer);
        } else {
            tabPane.remove(tabContainer);
        }
        tabContainer = null;
    }

    /** {@inheritDoc} */
    @Override
    public void uninstallUI(final JComponent c) {
        uninstallKeyboardActions();
        uninstallListeners();
        uninstallDefaults();
        uninstallComponents();
        c.setLayout(null);

        this.tabPane = null;
    }

    /**
     * Reloads the mnemonics. This should be invoked when a memonic changes, when
     * the title of a mnemonic changes, or when tabs are added/removed.
     */
    private void updateMnemonics() {
        resetMnemonics();
        for ( int counter = tabPane.getTabCount() - 1; counter >= 0; counter--) {
            final int mnemonic = tabPane.getMnemonicAt(counter);
            if (mnemonic > 0) {
                addMnemonic(counter, mnemonic);
            }
        }
    }

} // end of class BaseTabbedPaneUI