Package javax.swing.plaf.basic

Source Code of javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout

/*
*  Licensed to the Apache Software Foundation (ASF) under one or more
*  contributor license agreements.  See the NOTICE file distributed with
*  this work for additional information regarding copyright ownership.
*  The ASF licenses this file to You under the Apache License, Version 2.0
*  (the "License"); you may not use this file except in compliance with
*  the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/

/**
* @author Vadim L. Bogdanov
* @version $Revision$
*/
package javax.swing.plaf.basic;


import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
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.ComponentUI;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.UIResource;

import javax.swing.text.View;

import org.apache.harmony.x.swing.ButtonCommons;
import org.apache.harmony.x.swing.StringConstants;
import org.apache.harmony.x.swing.Utilities;

public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants {

    public class FocusHandler extends FocusAdapter {
        public void focusGained(final FocusEvent e) {
            tabPane.repaint();
        }

        public void focusLost(final FocusEvent e) {
            tabPane.repaint();
        }
    }

    public class MouseHandler extends MouseAdapter {
        public void mousePressed(final MouseEvent e) {
            int index = calculateTabIndexByMouseEvent(e);

            if (index != -1 && tabPane.isEnabledAt(index)) {
                // tab is clicked
                if (!isSelectedTab(index)) {
                    boolean visibleComponentFocused = getFocusIndex() == -1;
                    tabPane.setSelectedIndex(index);
                    if (visibleComponentFocused && getVisibleComponent() != null) {
                        getVisibleComponent().requestFocus();
                    }
                } else {
                    tabPane.requestFocus();
                }
            }
        }
    }

    private class MouseMotionHandler extends MouseMotionAdapter {
        public void mouseMoved(final MouseEvent e) {
            setRolloverTab(calculateTabIndexByMouseEvent(e));
        }
    }

    public class PropertyChangeHandler implements PropertyChangeListener {
        public void propertyChange(final PropertyChangeEvent e) {
            if ("tabPlacement".equals(e.getPropertyName())) {
                if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
                    installScrollableTabsComponents();
                }
                tabPane.revalidate();
                tabPane.repaint();
            } else if ("tabLayoutPolicy".equals(e.getPropertyName())) {
                if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
                    installScrollableTabsComponents();
                } else {
                    uninstallScrollableTabsComponents();
                }
                tabPane.setLayout(createLayoutManager());
                tabPane.revalidate();
                tabPane.repaint();
            }
        }
    }

    public class TabbedPaneLayout implements LayoutManager {
        public void addLayoutComponent(final String name, final Component comp) {
            if (!(comp instanceof UIResource)) {
                calculateLayoutInfo();
            }
        }

        public void calculateLayoutInfo() {
            if (tabPane == null) {
                return;
            }
            int tabCount = tabPane.getTabCount();
            final Component selectedComponent = tabPane.getSelectedComponent();
            if (selectedComponent != null) {
                setVisibleComponent(selectedComponent);
            }

            assureRectsCreated(tabCount);
            calculateTabRects(tabPane.getTabPlacement(), tabCount);
        }

        /**
         * Implements both minimum and preferred size calculations.
         */
        protected Dimension calculateSize(final boolean minimum) {
            int tabPlacement = tabPane.getTabPlacement();
            Dimension size = calculateTabAreaSize(tabPlacement);

            Dimension contentAreaSize = calculateContentAreaSize(minimum);
            Utilities.addInsets(contentAreaSize,
                                getContentBorderInsets(tabPlacement));

            if (isVerticalRun(tabPlacement)) {
                size.width += contentAreaSize.width;
                size.height = Math.max(size.height, contentAreaSize.height);
            } else {
                size.height += contentAreaSize.height;
                size.width = Math.max(size.width, contentAreaSize.width);
            }

            Utilities.addInsets(size, tabPane.getInsets());
            return size;
        }

        private Dimension calculateTabAreaSize(final int tabPlacement) {
            Dimension size = new Dimension();
            Insets insets = getTabAreaInsets(tabPlacement);

            if (isVerticalRun(tabPlacement)) {
                size.height = calculateMaxTabHeight(tabPlacement);
                size.height += insets.top + insets.bottom;
                size.width = preferredTabAreaWidth(tabPlacement, size.height);
            } else {
                size.width = calculateMaxTabWidth(tabPlacement);
                size.width += insets.left + insets.right;
                size.height = preferredTabAreaHeight(tabPlacement, size.width);
            }

            return size;
        }

        private Dimension calculateContentAreaSize(final boolean minimum) {
            Dimension contentAreaSize = new Dimension();

            for (int i = 0; i < tabPane.getComponentCount(); i++) {
                Dimension size;
                if (minimum) {
                    size = tabPane.getComponentAt(i).getMinimumSize();
                } else {
                    size = tabPane.getComponentAt(i).getPreferredSize();
                }
                if (size.width > contentAreaSize.width) {
                    contentAreaSize.width = size.width;
                }
                if (size.height > contentAreaSize.height) {
                    contentAreaSize.height = size.height;
                }
            }

            return contentAreaSize;
        }

        private void getNewTabRunOffsets(final int tabPlacement,
                                         final int runCount,
                                         final Point initialOffset,
                                         final Point newTabRunOffset) {
            newTabRunOffset.setLocation(initialOffset);
            int tabRunIndent = getTabRunIndent(tabPlacement, runCount);
            if (isVerticalRun(tabPlacement)) {
                newTabRunOffset.y += tabRunIndent;
            } else {
                newTabRunOffset.x += tabRunIndent;
            }
        }

        void calculateAvailableRectangleToPlaceTabs(final int tabPlacement,
                final Rectangle tabsRect, final Rectangle tabAreaInnerBounds) {
            SwingUtilities.calculateInnerArea(tabPane, tabsRect);
            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
            calculateInnerArea(tabsRect, tabAreaInsets);
            tabAreaInnerBounds.setBounds(tabsRect);
        }

        protected void calculateTabRects(final int tabPlacement,
                                         final int tabCount) {
            if (tabCount == 0) {
                return;
            }

            runCount = 1;
            tabRuns[0] = 0;

            FontMetrics fm = getFontMetrics();
            maxTabHeight = calculateMaxTabHeight(tabPlacement);
            maxTabWidth = calculateMaxTabWidth(tabPlacement);

            calculateAvailableRectangleToPlaceTabs(tabPlacement, calcRect,
                                                   tabAreaInnerBounds);

            // split tabs into runs
            Point initialOffset = calcRect.getLocation();
            Point currentOffset = new Point();
            getNewTabRunOffsets(tabPlacement, runCount, initialOffset, currentOffset);
            boolean isVerticalRun = isVerticalRun(tabPlacement);
            boolean justStartedNewRun = true; // not very good to use this flag
            for (int i = 0; i < tabCount; i++) {
                int tabWidth = calculateTabWidth(tabPlacement, i, fm);
                int tabHeight = calculateTabHeight(tabPlacement, i, fm.getHeight());
                if (isVerticalRun) {
                    rects[i].setSize(maxTabWidth, tabHeight);
                } else {
                    rects[i].setSize(tabWidth, maxTabHeight);
                }
                rects[i].setLocation(currentOffset);
                if (!calcRect.contains(rects[i]) && !justStartedNewRun) {
                    // start a new tab run
                    getNewTabRunOffsets(tabPlacement, runCount,
                                        initialOffset, currentOffset);
                    if (runCount >= tabRuns.length) {
                        expandTabRunsArray();
                    }
                    tabRuns[runCount] = i;
                    runCount++;
                }
                rects[i].setLocation(currentOffset);
                if (isVerticalRun) {
                    currentOffset.y += tabHeight;
                } else {
                    currentOffset.x += tabWidth;
                }
                justStartedNewRun = false;
            }
            int start = isVerticalRun
                        ? tabAreaInnerBounds.y
                        : tabAreaInnerBounds.x;
            int length = isVerticalRun
                         ? tabAreaInnerBounds.height
                         : tabAreaInnerBounds.width;
            normalizeTabRuns(tabPlacement, tabCount, start, start + length);

            selectedRun = getRunForTab(tabCount, tabPane.getSelectedIndex());
            if (shouldRotateTabRuns(tabPlacement)) {
                rotateTabRuns(tabPlacement, selectedRun);
            }

            // calculate remaining coordinate (x or y depending on tabPlacement)
            for (int i = 0; i < tabCount; i++) {
                int sign = 1;
                if (tabPlacement == JTabbedPane.BOTTOM) {
                    sign = -1;
                    rects[i].y += tabAreaInnerBounds.height - maxTabHeight;
                } else if (tabPlacement == JTabbedPane.RIGHT) {
                    sign = -1;
                    rects[i].x += tabAreaInnerBounds.width - maxTabWidth;
                }
                if (isVerticalRun) {
                    rects[i].x += sign * (runCount - getRunForTab(tabCount, i) - 1)
                                  * (maxTabWidth - getTabRunOverlay(tabPlacement));
                } else {
                    rects[i].y += sign * (runCount - getRunForTab(tabCount, i) - 1)
                                  * (maxTabHeight - getTabRunOverlay(tabPlacement));
                }
            }

            // pad tab runs
            int lastTabInRun = -1;
            int curRun = getRunForTab(tabCount, 0);
            for (int i = 0; i < runCount; i++) {
                int firstTabInRun = getNextElementInRing(lastTabInRun, tabCount);
                lastTabInRun = lastTabInRun(tabCount, curRun);
                if (shouldPadTabRun(tabPlacement, curRun)) {
                    int max = isVerticalRun ? calcRect.height : calcRect.width;
                    padTabRun(tabPlacement, firstTabInRun, lastTabInRun, max);
                }
                curRun = getNextTabRun(curRun);
            }

            padSelectedTab(tabPlacement, tabPane.getSelectedIndex());
        }

        public void layoutContainer(final Container parent) {
            calculateLayoutInfo();

            int tabPlacement = tabPane.getTabPlacement();

            int tabAreaSize;
            if (isVerticalRun(tabPlacement)) {
                tabAreaSize = calculateTabAreaWidth(tabPlacement,
                                                    runCount, maxTabWidth);
            } else {
                tabAreaSize = calculateTabAreaHeight(tabPlacement,
                                                     runCount, maxTabHeight);
            }

            Rectangle rect = SwingUtilities.calculateInnerArea(tabPane, null);
            if (tabPlacement == JTabbedPane.TOP) {
                rect.y += tabAreaSize;
                rect.height -= tabAreaSize;
            } else if (tabPlacement == JTabbedPane.BOTTOM) {
                rect.height -= tabAreaSize;
            } else if (tabPlacement == JTabbedPane.LEFT) {
                rect.x += tabAreaSize;
                rect.width -= tabAreaSize;
            } else if (tabPlacement == JTabbedPane.RIGHT) {
                rect.width -= tabAreaSize;
            }
            contentAreaBounds.setBounds(rect);

            calculateInnerArea(rect, getContentBorderInsets(tabPlacement));

            if (getVisibleComponent() != null) {
                getVisibleComponent().setBounds(rect);
            }

            calculateTabAreaClipRect(tabPlacement);
        }

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

        /**
         * This function prevents from appearing of runs with
         * small number of tabs. If it is necessary, tabs are
         * moved from the next to last tab run to the last tab run.
         */
        protected void normalizeTabRuns(final int tabPlacement,
                                        final int tabCount,
                                        final int start,
                                        final int max) {
            if (runCount < 2) {
                return;
            }

            final float NORMALIZE_THRESHOLD = 5f / 8f;

            // find out the last tab in the next to last tab run
            boolean isVerticalRun = isVerticalRun(tabPlacement);
            int lastTabPos = isVerticalRun
                             ? (int)rects[tabCount - 1].getMaxY()
                             : (int)rects[tabCount - 1].getMaxX();
            int desiredPos = start + (int)((max - start) * NORMALIZE_THRESHOLD);
            int lastTab = lastTabInRun(tabCount, runCount - 2);
            int firstTab = firstTabInRun(tabCount, runCount - 2);
            while (lastTab - firstTab >= 2 && lastTabPos < desiredPos) {
                lastTabPos += isVerticalRun
                              ? rects[lastTab].height
                              : rects[lastTab].width;
                lastTab--;
            }

            lastTab = getNextTabIndex(lastTab);

            if (tabRuns[runCount - 1] == lastTab) {
                return; // no tabs were moved
            }

            tabRuns[runCount - 1] = lastTab;

            // correct bounds of tabs in the last tab run
            lastTabPos = start;
            for (; lastTab < tabCount; lastTab++) {
                if (isVerticalRun) {
                    rects[lastTab].y = lastTabPos;
                    lastTabPos += rects[lastTab].height;
                } else {
                    rects[lastTab].x = lastTabPos;
                    lastTabPos += rects[lastTab].width;
                }
            }
        }

        /**
         * Increases size of the selected tab using selected tab pad insets.
         */
        protected void padSelectedTab(final int tabPlacement,
                                      final int selectedIndex) {
            if (selectedIndex == -1) {
                return;
            }

            Insets insets = getSelectedTabPadInsets(tabPlacement);
            Rectangle rect = rects[selectedIndex];

            rect.translate(-insets.left, -insets.top);
            rect.width += insets.left + insets.right;
            rect.height += insets.top + insets.bottom;
        }

        /**
         * Increases size of tabs in the given tab run to make width or height
         * of the tab run (depending on <code>tabPlacement</code>) equal
         * to <code>max</code>.
         *
         * @param tabPlacement the tab placement
         * @param start index of the first tab in the tab run
         * @param end index of the last tab in the tab run
         * @param max the desired width or height of the tab run
         */
        protected void padTabRun(final int tabPlacement, final int start,
                                 final int end, final int max) {
            int size = 0;
            for (int i = start; i <= end; i++) {
                size += isVerticalRun(tabPlacement) ? rects[i].height : rects[i].width;
            }
            int increment = (max - size) / (end - start + 1);
            int additionalIncrementToLast = (max - size) % (end - start + 1);

            for (int i = 0; i <= end - start; i++) {
                if (isVerticalRun(tabPlacement)) {
                    rects[start + i].height += increment;
                    rects[start + i].y += i * increment;
                } else {
                    rects[start + i].width += increment;
                    rects[start + i].x += i * increment;
                }
            }
            if (isVerticalRun(tabPlacement)) {
                rects[end].height += additionalIncrementToLast;
            } else {
                rects[end].width += additionalIncrementToLast;
            }
        }

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

        protected int preferredTabAreaHeight(final int tabPlacement,
                                             final int width) {
            int horizRunCount = isVerticalRun(tabPlacement)
                                ? 0
                                : getTabRunCount(tabPane);
            int height = calculateTabAreaHeight(tabPlacement, horizRunCount,
                                            calculateMaxTabHeight(tabPlacement));
            return height;
        }

        protected int preferredTabAreaWidth(final int tabPlacement,
                                            final int height) {
            int vertRunCount = isVerticalRun(tabPlacement)
                               ? getTabRunCount(tabPane)
                               : 0;
            int width = calculateTabAreaWidth(tabPlacement, vertRunCount,
                                              calculateMaxTabWidth(tabPlacement));
            return width;
        }

        public void removeLayoutComponent(final Component comp) {
            if (!(comp instanceof UIResource)) {
                calculateLayoutInfo();
            }
        }

        protected void rotateTabRuns(final int tabPlacement,
                                     final int selectedRun) {
            // rotate tabRuns to move selectedRun to the last position
            int[] temp = new int[selectedRun];
            System.arraycopy(tabRuns, 0, temp, 0, selectedRun);
            System.arraycopy(tabRuns, selectedRun,
                             tabRuns, 0, runCount - selectedRun);
            System.arraycopy(temp, 0, tabRuns,
                             runCount - selectedRun, selectedRun);
        }

        void calculateTabAreaClipRect(final int tabPlacement) {
            SwingUtilities.calculateInnerArea(tabPane, tabAreaClipRect);
            if (tabPlacement == TOP) {
                tabAreaClipRect.height = contentAreaBounds.y - tabAreaClipRect.y;
            else if (tabPlacement == LEFT) {
                tabAreaClipRect.width = contentAreaBounds.x - tabAreaClipRect.x;
            } else if (tabPlacement == BOTTOM) {
                tabAreaClipRect.y = (int)contentAreaBounds.getMaxY();
                tabAreaClipRect.height -= tabAreaClipRect.y;
            else if (tabPlacement == RIGHT) {
                tabAreaClipRect.x = (int)contentAreaBounds.getMaxX();
                tabAreaClipRect.width -= tabAreaClipRect.x;
            }
        }
    }

    private class ScrollableTabLayout extends TabbedPaneLayout {
        void calculateAvailableRectangleToPlaceTabs(final int tabPlacement,
                final Rectangle tabsRect, final Rectangle tabAreaInnerBounds) {
            super.calculateAvailableRectangleToPlaceTabs(tabPlacement, tabsRect,
                                                         tabAreaInnerBounds);
            tabsRect.setSize(Short.MAX_VALUE, Short.MAX_VALUE);

            if (isVerticalRun(tabPlacement)) {
                tabAreaInnerBounds.height -= leftScrollButton.getHeight()
                        + rightScrollButton.getHeight();
            } else {
                tabAreaInnerBounds.width -= leftScrollButton.getWidth()
                + rightScrollButton.getWidth();
            }
        }

        public void layoutContainer(final Container parent) {
            super.layoutContainer(parent);
            updateScrollButtons();
            layoutScrollButtons();
        }

        protected void padSelectedTab(final int tabPlacement,
                                      final int selectedIndex) {
            // overridden to do nothing
        }

        protected void calculateTabRects(final int tabPlacement,
                                         final int tabCount) {
            super.calculateTabRects(tabPlacement, tabCount);
        }

        private void layoutScrollButtons() {
            if (!leftScrollButton.isVisible()) {
                return;
            }

            rightScrollButton.getBounds(calcRect);

            int tabPlacement = tabPane.getTabPlacement();
            Rectangle c = contentAreaBounds;
            Rectangle b = rightScrollButton.getBounds();

            if (tabPlacement == TOP) {
                b.setLocation(c.x + c.width - b.width, c.y - b.height);
                rightScrollButton.setBounds(b);
                b.translate(-b.width, 0);
                leftScrollButton.setBounds(b);
            } else if (tabPlacement == BOTTOM) {
                b.setLocation(c.x + c.width - b.width, c.y + c.height);
                rightScrollButton.setBounds(b);
                b.translate(-b.width, 0);
                leftScrollButton.setBounds(b);
            } else if (tabPlacement == LEFT) {
                b.setLocation(c.x - b.width, c.y + c.height - b.height);
                rightScrollButton.setBounds(b);
                b.translate(0, -b.height);
                leftScrollButton.setBounds(b);
            } else if (tabPlacement == RIGHT) {
                b.setLocation(c.x + c.width, c.y + c.height - b.height);
                rightScrollButton.setBounds(b);
                b.translate(0, -b.height);
                leftScrollButton.setBounds(b);
            }
        }

        void calculateTabAreaClipRect(final int tabPlacement) {
            super.calculateTabAreaClipRect(tabPlacement);
            if (isVerticalRun(tabPlacement)) {
                tabAreaClipRect.y = tabAreaInnerBounds.y;
                tabAreaClipRect.height = tabAreaInnerBounds.height;
            } else {
                tabAreaClipRect.x = tabAreaInnerBounds.x;
                tabAreaClipRect.width = tabAreaInnerBounds.width;
            }
        }
    }

    private class ScrollButton extends BasicArrowButton
        implements UIResource, ActionListener {

        public ScrollButton(final int direction) {
            super(direction);

            setSize(16, 16);
            setFocusable(false);
            addActionListener(this);
        }

        public void actionPerformed(final ActionEvent e) {
            if (direction == EAST || direction == SOUTH) {
                scrollToShowTab(true);
            } else if (direction == WEST || direction == NORTH) {
                scrollToShowTab(false);
            }
            tabPane.repaint();
        }
    }

    public class TabSelectionHandler implements ChangeListener {
        public void stateChanged(final ChangeEvent e) {
            // we have to relayout immediatelly; in other case we can get
            // in listeners/actions inconsistent state (ex.: NavigateAction)
            tabPane.doLayout();

            scrollToShowTab(tabPane.getSelectedIndex());
            tabPane.revalidate();
            tabPane.repaint();
        }
    }

    private class NavigateAction extends AbstractAction {
        private int direction;

        public NavigateAction(final int direction) {
            this.direction = direction;
        }

        public void actionPerformed(final ActionEvent e) {
            boolean visibleComponentFocused = getFocusIndex() == -1;
            navigateSelectedTab(direction);
            if (visibleComponentFocused && getVisibleComponent() != null) {
                getVisibleComponent().requestFocus();
            }
        }
    }

    private static class MnemonicAction extends AbstractAction {
        public void actionPerformed(final ActionEvent e) {
            JTabbedPane tabPane = (JTabbedPane)e.getSource();

            int keyCode = Utilities.keyCharToKeyCode(e.getActionCommand().charAt(0));
            for (int i = 0; i < tabPane.getTabCount(); i++) {
                if (keyCode == tabPane.getMnemonicAt(i)) {
                    tabPane.setSelectedIndex(i);
                    break;
                }
            }
        }
    }

    public static ComponentUI createUI(final JComponent c) {
        return new BasicTabbedPaneUI();
    }

    protected static void rotateInsets(final Insets topInsets,
                                       final Insets targetInsets,
                                       final int targetPlacement) {
        if (targetPlacement == JTabbedPane.TOP) {
            targetInsets.set(topInsets.top, topInsets.left,
                             topInsets.bottom, topInsets.right);
        } else if (targetPlacement == JTabbedPane.LEFT) {
            targetInsets.set(topInsets.left, topInsets.top,
                             topInsets.right, topInsets.bottom);
        } else if (targetPlacement == JTabbedPane.BOTTOM) {
            targetInsets.set(topInsets.bottom, topInsets.left,
                             topInsets.top, topInsets.right);
        } else if (targetPlacement == JTabbedPane.RIGHT) {
            targetInsets.set(topInsets.left, topInsets.bottom,
                             topInsets.right, topInsets.top);
        } else {
            assert false : "incorrect targetPlacement";
        }
    }

    private static int TAB_RUNS_ARRAY_SIZE_INCREMENT = 5;
    private static AbstractAction MNEMONIC_ACTION = new MnemonicAction();

    protected transient Rectangle calcRect = new Rectangle();

    protected Color highlight;
    protected Color lightHighlight;
    protected Color shadow;
    protected Color darkShadow;
    protected Color focus;

    protected int maxTabHeight;
    protected int maxTabWidth;

    protected FocusListener focusListener;
    protected MouseListener mouseListener;
    protected ChangeListener tabChangeListener;
    protected PropertyChangeListener propertyChangeListener;

    protected JTabbedPane tabPane;

    protected Rectangle[] rects = {new Rectangle()};
    protected int runCount;
    protected int selectedRun;

    protected Insets contentBorderInsets;
    protected Insets selectedTabPadInsets;
    protected Insets tabAreaInsets;
    protected Insets tabInsets;
    protected int tabRunOverlay;

    private JButton leftScrollButton;
    private JButton rightScrollButton;
    private Component visibleComponent;

    private ActionMap actionMap;

    private MouseMotionAdapter mouseMotionListener;

    /**
     * Tab area's inner area bounds (excluding tab area insets).
     * It is calculated by a layout manager.
     */
    private Rectangle tabAreaInnerBounds = new Rectangle();

    /**
     * Tab area's clip region. It is used to prevent from painting on
     * content area and on scroll buttons when <code>SCROLL_TAB_LAYOUT</code>
     * is used.
     */
    private Rectangle tabAreaClipRect = new Rectangle();
    private int scrollableTabsOffset = 0;

    /**
     * Content area bounds including content area insets but excluding
     * tabbed pane insets. It is calculated by a layout manager.
     */
    private Rectangle contentAreaBounds = new Rectangle();

    private Color selectedTabBackground;

    private int rolloverTab = -1;

    protected int[] tabRuns = new int[5];
    protected int textIconGap;

    /**
     * @deprecated
     */
    protected KeyStroke leftKey;
    /**
     * @deprecated
     */
    protected KeyStroke rightKey;
    /**
     * @deprecated
     */
    protected KeyStroke upKey;
    /**
     * @deprecated
     */
    protected KeyStroke downKey;

    protected void assureRectsCreated(final int tabCount) {
        Rectangle[] oldRects = rects;
        if (rects.length < tabCount) {
            rects = new Rectangle[tabCount];
        }

        System.arraycopy(oldRects, 0, rects, 0, oldRects.length);
        for (int i = oldRects.length; i < rects.length; i++) {
            rects[i] = new Rectangle();
        }
    }

    protected int calculateMaxTabHeight(final int tabPlacement) {
        int height = 0;
        int fontHeight = getFontMetrics().getHeight();
        for (int i = 0; i < tabPane.getTabCount(); i++) {
            height = Math.max(calculateTabHeight(tabPlacement, i, fontHeight),
                              height);
        }
        return height;
    }

    protected int calculateMaxTabWidth(final int tabPlacement) {
        int width = 0;
        FontMetrics fm = getFontMetrics();
        for (int i = 0; i < tabPane.getTabCount(); i++) {
            width = Math.max(calculateTabWidth(tabPlacement, i, fm),
                             width);
        }
        return width;
    }

    protected int calculateTabAreaHeight(final int tabPlacement,
                                         final int horizRunCount,
                                         final int maxTabHeight) {
        int height = maxTabHeight * horizRunCount
                     - getTabRunOverlay(tabPlacement) * (horizRunCount - 1);
        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
        return height + tabAreaInsets.top + tabAreaInsets.bottom;
    }

    protected int calculateTabAreaWidth(final int tabPlacement,
                                        final int vertRunCount,
                                        final int maxTabWidth) {
        int width = maxTabWidth * vertRunCount
                    - getTabRunOverlay(tabPlacement) * (vertRunCount - 1);
        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
        return width + tabAreaInsets.left + tabAreaInsets.right;
    }

    protected int calculateTabHeight(final int tabPlacement,
                                     final int tabIndex,
                                     final int fontHeight) {
        int height = fontHeight;

        Icon icon = getIconForTab(tabIndex);
        if (icon != null) {
            height = Math.max(height, icon.getIconHeight());
        }

        height += Math.abs(getTabLabelShiftY(tabPlacement, tabIndex, true) -
                           getTabLabelShiftY(tabPlacement, tabIndex, false));

        // add space to paint a focus indicator and a tab's border
        height += 4;

        Insets insets = getTabInsets(tabPlacement, tabIndex);
        return height + insets.top + insets.bottom;
    }

    protected int calculateTabWidth(final int tabPlacement,
                                    final int tabIndex,
                                    final FontMetrics fm) {
        int width = fm.stringWidth(tabPane.getTitleAt(tabIndex));

        Icon icon = getIconForTab(tabIndex);
        if (icon != null) {
            width += textIconGap + icon.getIconWidth();
        }

        width += Math.abs(getTabLabelShiftX(tabPlacement, tabIndex, true) -
                          getTabLabelShiftX(tabPlacement, tabIndex, false));

        // add space to paint a focus indicator and a tab's border
        width += 3;

        Insets insets = getTabInsets(tabPlacement, tabIndex);
        return width + insets.left + insets.right;
    }

    protected ChangeListener createChangeListener() {
        return new TabSelectionHandler();
    }

    protected FocusListener createFocusListener() {
        return new FocusHandler();
    }

    protected LayoutManager createLayoutManager() {
        if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT) {
            return new TabbedPaneLayout();
        } else {
            return new ScrollableTabLayout();
        }
    }

    protected MouseListener createMouseListener() {
        return new MouseHandler();
    }

    protected PropertyChangeListener createPropertyChangeListener() {
        return new PropertyChangeHandler();
    }

    protected JButton createScrollButton(final int direction) {
        if (direction != NORTH && direction != SOUTH && direction != EAST && direction != WEST) {
            throw new IllegalArgumentException();
        }

        return new ScrollButton(direction);
    }

    protected void expandTabRunsArray() {
        int[] oldTabRuns = tabRuns;
        tabRuns = new int[oldTabRuns.length + TAB_RUNS_ARRAY_SIZE_INCREMENT];
        System.arraycopy(oldTabRuns, 0, tabRuns, 0, oldTabRuns.length);
    }

    protected Insets getContentBorderInsets(final int tabPlacement) {
        return contentBorderInsets;
    }

    protected int getFocusIndex() {
        Component focusOwner =
            KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
        if (focusOwner != tabPane) {
            return -1;
        }

        return tabPane.getSelectedIndex();
    }

    protected FontMetrics getFontMetrics() {
        return Utilities.getFontMetrics(tabPane);
    }

    protected Icon getIconForTab(final int index) {
        return tabPane.isEnabledAt(index)
               ? tabPane.getIconAt(index)
               : tabPane.getDisabledIconAt(index);
    }

    public Dimension getMaximumSize(final JComponent c) {
        return null;
    }

    public Dimension getMinimumSize(final JComponent c) {
        return null;
    }

    protected int getNextTabIndex(final int base) {
        return getNextElementInRing(base, tabPane.getTabCount());
    }

    protected int getPreviousTabIndex(final int base) {
        return getPreviousElementInRing(base, tabPane.getTabCount());
    }

    protected int getNextTabIndexInRun(final int tabCount, final int base) {
        int run = getRunForTab(tabCount, base);
        int firstTabInRun = firstTabInRun(tabCount, run);
        int lastTabInRun = lastTabInRun(tabCount, run);
        return firstTabInRun
            + getNextElementInRing(base - firstTabInRun,
                                   lastTabInRun - firstTabInRun + 1);
    }

    protected int getPreviousTabIndexInRun(final int tabCount, final int base) {
        int run = getRunForTab(tabCount, base);
        int firstTabInRun = firstTabInRun(tabCount, run);
        int lastTabInRun = lastTabInRun(tabCount, run);
        return firstTabInRun
            + getPreviousElementInRing(base - firstTabInRun,
                                       lastTabInRun - firstTabInRun + 1);
    }

    protected int getNextTabRun(final int baseRun) {
        // using getTabRunCount() instead of runCount leads to stack overflow
        return getNextElementInRing(baseRun, runCount);
    }

    protected int getPreviousTabRun(final int baseRun) {
        return getPreviousElementInRing(baseRun, runCount);
    }

    protected int getRunForTab(final int tabCount, final int tabIndex) {
        for (int run = 0; run < runCount; run++) {
            int firstTabInRun = tabRuns[run];
            if (firstTabInRun <= tabIndex
                    && tabIndex <= lastTabInRun(tabCount, run)) {
                return run;
            }
        }
        return 0;
    }

    protected Insets getSelectedTabPadInsets(final int tabPlacement) {
        Insets rotatedInsets = new Insets(0, 0, 0, 0);
        rotateInsets(selectedTabPadInsets, rotatedInsets, tabPlacement);
        return rotatedInsets;
    }

    protected Insets getTabAreaInsets(final int tabPlacement) {
        Insets rotatedInsets = new Insets(0, 0, 0, 0);
        rotateInsets(tabAreaInsets, rotatedInsets, tabPlacement);
        return rotatedInsets;
    }

    protected Rectangle getTabBounds(final int tabIndex, final Rectangle dest) {
        dest.setBounds(rects[tabIndex]);
        if (isVerticalRun(tabPane.getTabPlacement())) {
            dest.translate(0, scrollableTabsOffset);
        } else {
            dest.translate(scrollableTabsOffset, 0);
        }
        return dest;
    }

    public Rectangle getTabBounds(final JTabbedPane pane, final int index) {
        Rectangle result = new Rectangle();
        return getTabBounds(index, result);
    }

    protected Insets getTabInsets(final int tabPlacement, final int tabIndex) {
        return tabInsets;
    }

    protected int getTabLabelShiftX(final int tabPlacement, final int tabIndex,
                                    final boolean isSelected) {
        int offset = -1;
        if (isSelected) {
            if (tabPlacement == RIGHT) {
                offset = 1;
            }
        } else {
            if (tabPlacement == LEFT) {
                offset = 1;
            }
        }

        return offset;
    }

    protected int getTabLabelShiftY(final int tabPlacement, final int tabIndex,
                                    final boolean isSelected) {
        int offset = 1;
        if (isSelected) {
            if (tabPlacement == TOP) {
                offset = -1;
            }
        } else {
            if (tabPlacement == BOTTOM) {
                offset = -1;
            }
        }

        return offset;
    }

    public int getTabRunCount(final JTabbedPane pane) {
        tabPane.doLayout();
        return runCount;
    }

    protected int getTabRunIndent(final int tabPlacement, final int run) {
        return 0;
    }

    protected int getTabRunOffset(final int tabPlacement, final int tabCount,
                                  final int tabIndex, final boolean forward) {
        int curRun = getRunForTab(tabCount, tabIndex);
        int newRun = !forward
                     ? getNextTabRun(curRun)
                     : getPreviousTabRun(curRun);

        int lastTab = lastTabInRun(tabCount, newRun);
        int rc;
        if (isVerticalRun(tabPlacement)) {
            rc = (int)rects[lastTab].getCenterX()
                 - (int)rects[tabIndex].getCenterX();
        } else {
            rc = (int)rects[lastTab].getCenterY()
                 - (int)rects[tabIndex].getCenterY();
        }
        return rc;
    }

    protected int getTabRunOverlay(final int tabPlacement) {
        return tabRunOverlay;
    }

    protected View getTextViewForTab(final int tabIndex) {
        //TODO: implement when HTML styled text is supported
        return null;
    }

    protected void installComponents() {
        if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
            installScrollableTabsComponents();
        }
    }

    protected void uninstallComponents() {
        if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
            uninstallScrollableTabsComponents();
        }
    }

    protected void installDefaults() {
        LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
                                         "TabbedPane.foreground",
                                         "TabbedPane.font");

        darkShadow = UIManager.getColor("TabbedPane.darkShadow");
        shadow = UIManager.getColor("TabbedPane.shadow");
        highlight = UIManager.getColor("TabbedPane.light");
        lightHighlight = UIManager.getColor("TabbedPane.highlight");
        focus = UIManager.getColor("TabbedPane.focus");

        contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
        selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
        tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
        tabInsets = UIManager.getInsets("TabbedPane.tabInsets");

        tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
        textIconGap = UIManager.getInt("TabbedPane.textIconGap");

        selectedTabBackground = UIManager.getColor("control");
    }

    protected void uninstallDefaults() {
    }

    private ActionMap getUIActionMap() {
        if (actionMap != null) {
            return actionMap;
        }

        actionMap = new ActionMapUIResource();
        final AbstractAction navigateEastAction = new NavigateAction(EAST);
        actionMap.put("navigateRight", navigateEastAction);
        final AbstractAction navigateWestAction = new NavigateAction(WEST);
        actionMap.put("navigateLeft", navigateWestAction);
        actionMap.put("navigateUp", new NavigateAction(NORTH));
        actionMap.put("navigateDown", new NavigateAction(SOUTH));

        // "ctrl DOWN", "ctrl KP_DOWN"
        actionMap.put("requestFocusForVisibleComponent",
                      new AbstractAction() {
                        public void actionPerformed(final ActionEvent e) {
                            if (getVisibleComponent() != null) {
                                getVisibleComponent().requestFocus();
                            }
                        }
        });

        // "ctrl PAGE_DOWN", "navigatePageDown"
        actionMap.put("navigatePageDown", navigateWestAction);
        // "ctrl PAGE_UP", "navigatePageUp"
        actionMap.put("navigatePageUp", navigateEastAction);

        // "ctrl KP_UP", "ctrl UP"
        actionMap.put("requestFocus",
                      new AbstractAction() {
                        public void actionPerformed(final ActionEvent e) {
                            tabPane.requestFocus();
                        }
        });

        actionMap.put(StringConstants.MNEMONIC_ACTION, MNEMONIC_ACTION);

        return actionMap;
    }

    protected void installKeyboardActions() {
        SwingUtilities.replaceUIInputMap(tabPane,
            JComponent.WHEN_FOCUSED,
            (InputMap)UIManager.get("TabbedPane.focusInputMap"));

        SwingUtilities.replaceUIInputMap(tabPane,
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
            (InputMap)UIManager.get("TabbedPane.ancestorInputMap"));

        SwingUtilities.replaceUIActionMap(tabPane, getUIActionMap());
    }

    protected void uninstallKeyboardActions() {
        SwingUtilities.replaceUIInputMap(tabPane,
            JComponent.WHEN_FOCUSED, null);
        SwingUtilities.replaceUIInputMap(tabPane,
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
        SwingUtilities.replaceUIActionMap(tabPane, null);
    }

    protected void installListeners() {
        if (focusListener == null) {
            focusListener = createFocusListener();
        }
        tabPane.addFocusListener(focusListener);

        if (mouseListener == null) {
            mouseListener = createMouseListener();
        }
        tabPane.addMouseListener(mouseListener);

        if (mouseMotionListener == null) {
            mouseMotionListener = new MouseMotionHandler();
        }
        tabPane.addMouseMotionListener(mouseMotionListener);

        if (tabChangeListener == null) {
            tabChangeListener = createChangeListener();
        }
        tabPane.addChangeListener(tabChangeListener);

        if (propertyChangeListener == null) {
            propertyChangeListener = createPropertyChangeListener();
        }
        tabPane.addPropertyChangeListener(propertyChangeListener);
    }

    protected void uninstallListeners() {
        tabPane.removeFocusListener(focusListener);
        tabPane.removeMouseListener(mouseListener);
        tabPane.removeMouseMotionListener(mouseMotionListener);
        tabPane.removeChangeListener(tabChangeListener);
        tabPane.removePropertyChangeListener(propertyChangeListener);
    }

    public void installUI(final JComponent c) {
        tabPane = (JTabbedPane) c;
        setRolloverTab(-1);

        installDefaults();
        tabPane.setLayout(createLayoutManager());
        installComponents();
        installListeners();
        installKeyboardActions();
    }

    public void uninstallUI(final JComponent c) {
        uninstallDefaults();
        tabPane.setLayout(null);
        uninstallComponents();
        uninstallListeners();
        uninstallKeyboardActions();
    }

    protected int lastTabInRun(final int tabCount, final int run) {
        int nextRun = getNextTabRun(run);
        int firstIndex = firstTabInRun(tabCount, nextRun);
        return getPreviousTabIndex(firstIndex);
    }

    private int firstTabInRun(final int tabCount, final int run) {
        return tabRuns[run];
    }

    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) {
        iconRect.setBounds(0, 0, 0, 0);
        textRect.setBounds(0, 0, 0, 0);

        // calculate inner tab area
        calcRect.setBounds(tabRect);
        calculateInnerArea(calcRect, getTabInsets(tabPlacement, tabIndex));
        calcRect.translate(getTabLabelShiftX(tabPlacement, tabIndex, isSelected),
                           getTabLabelShiftY(tabPlacement, tabIndex, isSelected));
        boolean isLTR = tabPane.getComponentOrientation().isLeftToRight();
        SwingUtilities.layoutCompoundLabel(getFontMetrics(),
                                           title, icon,
                                           CENTER, CENTER,
                                           CENTER,
                                           isLTR ? RIGHT : LEFT,
                                           calcRect, iconRect, textRect, textIconGap);
    }

    protected void navigateSelectedTab(final int direction) {
        int tabPlacement = tabPane.getTabPlacement();
        int correctedDirection = rotateDirection(tabPlacement, direction);
        int selectedIndex = tabPane.getSelectedIndex();

        if (correctedDirection == EAST) {
            selectNextTabInRun(selectedIndex);
        } else if (correctedDirection == WEST) {
            selectPreviousTabInRun(selectedIndex);
        } else {
            int tabRunOffset = getTabRunOffset(tabPlacement,
                                               tabPane.getTabCount(),
                                               selectedIndex,
                                               correctedDirection == SOUTH);
            selectAdjacentRunTab(tabPlacement, selectedIndex, tabRunOffset);
        }
    }

    public void paint(final Graphics g, final JComponent c) {
  if (g == null) {
            throw new NullPointerException();
        }

        if (tabPane.getTabCount() == 0) {
            return;
        }

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

        paintTabArea(g, tabPlacement, selectedIndex);
        paintContentBorder(g, tabPlacement, selectedIndex);
    }

    protected void paintContentBorder(final Graphics g, final int tabPlacement,
                                      final int selectedIndex) {
        paintContentBorderTopEdge(g, tabPlacement, selectedIndex,
                                  contentAreaBounds.x, contentAreaBounds.y,
                                  contentAreaBounds.width,
                                  contentAreaBounds.height);
        paintContentBorderLeftEdge(g, tabPlacement, selectedIndex,
                                   contentAreaBounds.x, contentAreaBounds.y,
                                   contentAreaBounds.width,
                                   contentAreaBounds.height);
        paintContentBorderBottomEdge(g, tabPlacement, selectedIndex,
                                     contentAreaBounds.x, contentAreaBounds.y,
                                     contentAreaBounds.width,
                                     contentAreaBounds.height);
        paintContentBorderRightEdge(g, tabPlacement, selectedIndex,
                                    contentAreaBounds.x, contentAreaBounds.y,
                                    contentAreaBounds.width,
                                    contentAreaBounds.height);
    }

    /**
     * x, y, w, h are bounds of the content area including content area
     * insets.
     */
    protected void paintContentBorderBottomEdge(final Graphics g,
                                                final int tabPlacement,
                                                final int selectedIndex,
                                                final int x,
                                                final int y,
                                                final int w,
                                                final int h) {
        int xx = x;
        if (tabPlacement == BOTTOM) {
            getTabBounds(selectedIndex, calcRect);
            if (calcRect.x == x) {
                xx++;
                calcRect.x++;
                calcRect.width--;
            }
        }
        g.setColor(darkShadow);
        g.fillRect(xx, y + h - 1, w, 1);

        g.setColor(highlight);
        g.fillRect(x + 2, y + h - 3, w - 4, 2);

        if (tabPlacement == BOTTOM
                && isTabAdjacentToContentAreaBorder(selectedIndex)) {
            g.setColor(selectedTabBackground);
            g.fillRect(calcRect.x, y + h - 2, calcRect.width, 1);
            g.fillRect(calcRect.x, y + h - 1, calcRect.width - 1, 1);
        }
    }

    /**
     * x, y, w, h are bounds of the content area including content area
     * insets.
     */
    protected void paintContentBorderLeftEdge(final Graphics g,
                                              final int tabPlacement,
                                              final int selectedIndex,
                                              final int x,
                                              final int y,
                                              final int w,
                                              final int h) {
        g.setColor(lightHighlight);
        g.fillRect(x, y, 1, h - 1);

        g.setColor(highlight);
        g.fillRect(x + 1, y + 1, 1, h - 3);

        if (tabPlacement == LEFT) {
            getTabBounds(selectedIndex, calcRect);
            if (calcRect.y == y) {
                calcRect.y++;
                calcRect.height--;
            }
            if (isTabAdjacentToContentAreaBorder(selectedIndex)) {
                g.setColor(selectedTabBackground);
                g.fillRect(x, calcRect.y, 1, calcRect.height);
            }
        }
    }

    /**
     * x, y, w, h are bounds of the content area including content area
     * insets.
     */
    protected void paintContentBorderRightEdge(final Graphics g,
                                               final int tabPlacement,
                                               final int selectedIndex,
                                               final int x,
                                               final int y,
                                               final int w,
                                               final int h) {
        int yy = y;
        if (tabPlacement == RIGHT) {
            getTabBounds(selectedIndex, calcRect);
            if (calcRect.y == y) {
                yy++;
                calcRect.y++;
                calcRect.height--;
            }
        }
        g.setColor(darkShadow);
        g.fillRect(x + w - 1, yy, 1, h);

        g.setColor(highlight);
        g.fillRect(x + w - 3, y + 2, 2, h - 4);

        if (tabPlacement == RIGHT
                && isTabAdjacentToContentAreaBorder(selectedIndex)) {
            g.setColor(selectedTabBackground);
            g.fillRect(x + w - 1, calcRect.y, 1, calcRect.height - 1);
            g.fillRect(x + w - 2, calcRect.y, 1, calcRect.height);
        }
    }

    /**
     * x, y, w, h are bounds of the content area including content area
     * insets.
     */
    protected void paintContentBorderTopEdge(final Graphics g,
                                             final int tabPlacement,
                                             final int selectedIndex,
                                             final int x,
                                             final int y,
                                             final int w,
                                             final int h) {
        g.setColor(lightHighlight);
        g.fillRect(x, y, w - 1, 1);

        g.setColor(highlight);
        g.fillRect(x + 1, y + 1, w - 3, 1);

        if (tabPlacement == TOP && isTabAdjacentToContentAreaBorder(selectedIndex)) {
            getTabBounds(selectedIndex, calcRect);
            g.setColor(selectedTabBackground);
            g.fillRect(calcRect.x, y, calcRect.width, 1);
        }
    }

    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 (!isFocusPainted(tabIndex)) {
            return;
        }

        calcRect.setBounds(rects[tabIndex]);
        calcRect.grow(-4, -4);
        ButtonCommons.paintFocus(g, calcRect, focus);
    }

    private boolean isFocusPainted(final int tabIndex) {
        if (tabIndex != getFocusIndex()) {
            return false;
        }

        return tabPane.getIconAt(tabIndex) != null
                || !Utilities.isEmptyString(tabPane.getTitleAt(tabIndex));
    }

    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);
        }
    }

    protected void paintTab(final Graphics g, final int tabPlacement,
                            final Rectangle[] rects, final int tabIndex,
                            final Rectangle iconRect, final Rectangle textRect) {
        Rectangle r = rects[tabIndex];
        boolean isSelected = isSelectedTab(tabIndex);
        String title = tabPane.getTitleAt(tabIndex);

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

        Icon icon = getIconForTab(tabIndex);
        layoutLabel(tabPlacement, getFontMetrics(), tabIndex, title,
                    icon, r, iconRect, textRect,
                    isSelected);

        paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
        paintText(g, tabPlacement, tabPane.getFont(), getFontMetrics(),
                  tabIndex, title, textRect, isSelected);
        paintFocusIndicator(g, tabPlacement, rects, tabIndex,
                            iconRect, textRect, isSelected);
        paintTabBorder(g, tabPlacement, tabIndex, r.x, r.y,
                       r.width, r.height, isSelected);
    }

    protected void paintTabArea(final Graphics g, final int tabPlacement,
                                final int selectedIndex) {
        Shape oldClip = g.getClip();
        g.clipRect(tabAreaClipRect.x, tabAreaClipRect.y,
                   tabAreaClipRect.width, tabAreaClipRect.height);

        Point translate = new Point();
        if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
            if (isVerticalRun(tabPlacement)) {
                translate.setLocation(0, scrollableTabsOffset);
            } else {
                translate.setLocation(scrollableTabsOffset, 0);
            }
            g.translate(translate.x, translate.y);
        }

        Rectangle iconRect = new Rectangle();
        Rectangle textRect = new Rectangle();
        int tabCount = tabPane.getTabCount();

        for(int run = runCount - 1; run >=0; run--) {
            for (int i = firstTabInRun(tabCount, run);
                 i <= lastTabInRun(tabCount, run); i++) {

                paintTab(g, tabPlacement, rects, i, iconRect, textRect);
            }
            if (run == selectedRun) {
                paintTab(g, tabPlacement, rects, tabPane.getSelectedIndex(),
                         iconRect, textRect);
            }
        }

        g.setClip(oldClip);
        g.translate(-translate.x, -translate.y);
    }

    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) {
        Color background = isSelected
                           ? selectedTabBackground
                           : tabPane.getBackgroundAt(tabIndex);
        int xx = x;
        int yy = y;
        int ww = w;
        int hh = h;
        if (tabPlacement == JTabbedPane.TOP) {
            hh += 2;
        } else if (tabPlacement == JTabbedPane.BOTTOM) {
            hh += 2;
            yy -= 2;
        } else if (tabPlacement == JTabbedPane.LEFT) {
            ww += 2;
        } else if (tabPlacement == JTabbedPane.RIGHT) {
            ww += 2;
            xx -= 2;
        }

        g.setColor(background);
        g.fillRect(xx + 1, yy + 1, ww - 2, hh - 2);
    }

    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) {
        int xx = x;
        int yy = y;
        int ww = w;
        int hh = h;
        if (tabPlacement == JTabbedPane.TOP) {
            hh += 2;
        } else if (tabPlacement == JTabbedPane.BOTTOM) {
            hh += 2;
            yy -= 2;
        } else if (tabPlacement == JTabbedPane.LEFT) {
            ww += 2;
        } else if (tabPlacement == JTabbedPane.RIGHT) {
            ww += 2;
            xx -= 2;
        }

        int[] highlightX = {xx, xx, xx + ww - 2};
        int[] highlightY = {yy + hh - 2, yy, yy};
        g.setColor(lightHighlight);
        g.drawPolyline(highlightX, highlightY, highlightX.length);

        int[] shadowX = {xx + ww - 1, xx + ww - 1, xx + 1};
        int[] shadowY = {yy + 1, yy + hh - 1, yy + hh - 1};
        g.setColor(darkShadow);
        g.drawPolyline(shadowX, shadowY, shadowX.length);

        g.setColor(shadow);
        g.drawRect(xx + ww - 2, yy + 1, 0, hh - 3);
        g.drawRect(xx + 1, yy + hh - 2, ww - 3, 0);
    }

    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) {
        Color color = tabPane.isEnabledAt(tabIndex)
            ? tabPane.getForegroundAt(tabIndex)
            : tabPane.getBackgroundAt(tabIndex).darker();
        ButtonCommons.paintText(g, metrics, title,
                                tabPane.getDisplayedMnemonicIndexAt(tabIndex),
                                textRect, title, color);
    }

    protected void selectAdjacentRunTab(final int tabPlacement,
                                        final int tabIndex,
                                        final int offset) {
        getTabBounds(tabIndex, calcRect);
        int x = (int)calcRect.getCenterX();
        int y = (int)calcRect.getCenterY();
        if (isVerticalRun(tabPlacement)) {
            x += offset;
        } else {
            y += offset;
        }
        int newTabIndex = tabForCoordinate(tabPane, x, y);
        if (newTabIndex != -1) {
            if (tabPane.isEnabledAt(newTabIndex)) {
                tabPane.setSelectedIndex(newTabIndex);
            } else {
                selectNextTab(newTabIndex);
            }
        }
    }

    protected void selectNextTab(final int current) {
        int i = current;
        do {
            i = getNextTabIndex(i);
        } while (i != current && !tabPane.isEnabledAt(i));

        tabPane.setSelectedIndex(i);
    }

    protected void selectNextTabInRun(final int current) {
        int tabCount = tabPane.getTabCount();

        int i = current;
        do {
            i = getNextTabIndexInRun(tabCount, i);
        } while (i != current && !tabPane.isEnabledAt(i));

        tabPane.setSelectedIndex(i);
    }

    protected void selectPreviousTab(final int current) {
        int i = current;
        do {
            i = getPreviousTabIndex(i);
        } while (i != current && !tabPane.isEnabledAt(i));

        tabPane.setSelectedIndex(i);
    }

    protected void selectPreviousTabInRun(final int current) {
        int tabCount = tabPane.getTabCount();

        int i = current;
        do {
            i = getPreviousTabIndexInRun(tabCount, i);
        } while (i != current && !tabPane.isEnabledAt(i));

        tabPane.setSelectedIndex(i);
    }

    protected void setRolloverTab(final int index) {
        rolloverTab = index;
    }

    protected int getRolloverTab() {
        return rolloverTab;
    }

    protected void setVisibleComponent(final Component component) {
        Component oldVisible = getVisibleComponent();
        if (oldVisible != component) {
        if (oldVisible != null) {
            oldVisible.setVisible(false);
        }
        visibleComponent = component;
        }

        if (visibleComponent != null) {
            visibleComponent.setVisible(true);
        }
    }

    protected Component getVisibleComponent() {
        return visibleComponent;
    }

    protected boolean shouldPadTabRun(final int tabPlacement, final int run) {
        return runCount > 1;
    }

    protected boolean shouldRotateTabRuns(final int tabPlacement) {
        return true;
    }

    public int tabForCoordinate(final JTabbedPane pane,
                                final int x, final int y) {
        for (int i = 0; i < tabPane.getTabCount(); i++) {
            getTabBounds(i, calcRect);
            if (calcRect.contains(x, y)) {
                return i;
            }
        }

        return -1;
    }

    private int rotateDirection(final int tabPlacement, final int direction) {
        if (tabPlacement == LEFT || tabPlacement == RIGHT) {
            switch (direction) {
            case NORTH:
                return WEST;
            case SOUTH:
                return EAST;
            case WEST:
                return NORTH;
            case EAST:
                return SOUTH;
            }
        }
        return direction;
    }

    private int getNextElementInRing(final int index, final int ringSize) {
        return (index + 1) % ringSize;
    }

    private int getPreviousElementInRing(final int index, final int ringSize) {
        return (index + ringSize - 1) % ringSize;
    }

    private void installScrollableTabsComponents() {
//        if (scrollableTabArea == null) {
//            scrollableTabArea = new ScrollableTabPanel();
//        }
//        tabPane.add(scrollableTabArea);
        uninstallScrollableTabsComponents();

        if (isVerticalRun(tabPane.getTabPlacement())) {
            leftScrollButton = createScrollButton(NORTH);
            rightScrollButton = createScrollButton(SOUTH);
        } else {
            leftScrollButton = createScrollButton(WEST);
            rightScrollButton = createScrollButton(EAST);
        }
        tabPane.add(leftScrollButton);
        tabPane.add(rightScrollButton);
    }

    private void uninstallScrollableTabsComponents() {
//        tabPane.remove(scrollableTabArea);
        tabPane.remove(leftScrollButton);
        tabPane.remove(rightScrollButton);
        scrollableTabsOffset = 0;
    }

    private void calculateInnerArea(final Rectangle rect,
                                    final Insets insets) {
        rect.x += insets.left;
        rect.y += insets.top;
        rect.width -= insets.left + insets.right;
        rect.height -= insets.top + insets.bottom;
    }

    private boolean isVerticalRun(final int tabPlacement) {
        return tabPlacement == LEFT || tabPlacement == RIGHT;
    }

    private boolean isSelectedTab(final int tabIndex) {
        return tabPane.getSelectedIndex() == tabIndex;
    }

    private int findTruncatedTab(final boolean forward) {
        int inc;
        int start;
        int end;

        if (forward) {
            inc = -1;
            start = tabPane.getTabCount() - 1;
            end = 0;
        } else {
            inc = 1;
            start = 0;
            end = tabPane.getTabCount() - 1;
        }

        int rc = end;
        for (int i = start; i != end; i += inc) {
            if (tabAreaInnerBounds.intersects(getTabBounds(i, calcRect))) {
                rc = i;
                break;
            }
        }

        // correct the result if border of tabAreaInnerBounds lies
        // exactly _between_ tabs
        getTabBounds(rc, calcRect);
        if (forward) {
            // "- 1" because contains() returns false for points on the border
            if (tabAreaInnerBounds.contains(calcRect.getMaxX() - 1, calcRect.getMaxY() - 1)
                    && rc < tabPane.getTabCount() - 1) {
                rc++;
            }
        } else {
            // "+ 1" because contains() returns false for points on the border
            if (tabAreaInnerBounds.contains(calcRect.x + 1, calcRect.y + 1)
                    && rc > 0) {
                rc--;
            }
        }

        return rc;
    }

    private int calculateScrollDelta(final Rectangle r, final boolean forward) {
        int delta = 0;

        if (forward) {
            delta = Math.min(tabAreaInnerBounds.x + tabAreaInnerBounds.width
                             - (r.x + r.width),
                             tabAreaInnerBounds.y + tabAreaInnerBounds.height
                             - (r.y + r.height));
        } else {
            delta = Math.max(tabAreaInnerBounds.x - r.x,
                             tabAreaInnerBounds.y - r.y);
        }

        return delta;
    }

    private void scrollToShowTab(final boolean  forward) {
        if (tabPane.getTabLayoutPolicy() != JTabbedPane.SCROLL_TAB_LAYOUT) {
            return;
        }

        int tabIndex = findTruncatedTab(forward);
        updateScrollableTabOffset(tabIndex, forward);
    }

    private void scrollToShowTab(final int selectedIndex) {
        if (tabPane.getTabLayoutPolicy() != JTabbedPane.SCROLL_TAB_LAYOUT) {
            return;
        }

        getTabBounds(selectedIndex, calcRect);
        if (tabAreaInnerBounds.contains(calcRect)) {
            return;
        }

        boolean forward = tabAreaInnerBounds.contains(calcRect.x, calcRect.y);
        updateScrollableTabOffset(selectedIndex, forward);
    }

    private void updateScrollableTabOffset(final int tabIndex,
                                           final boolean forward) {
        getTabBounds(tabIndex, calcRect);
        int delta = calculateScrollDelta(calcRect, forward);
        if (tabIndex > 0 && tabIndex < tabPane.getTabCount() - 1
/*                || tabIndex == 0 && forward
                || tabIndex == tabPane.getTabCount() - 1 && !forward*/) {
            // the idea of this code is to make visible part of the next tab
            if (delta > 0) {
                delta += 5;
            } else if (delta < 0) {
                delta -= 5;
            }
        }
        scrollableTabsOffset += delta;
        updateScrollButtons();
    }

    private int calculateTabIndexByMouseEvent(final MouseEvent e) {
        Point p = SwingUtilities.convertPoint(e.getComponent(),
                                              e.getX(), e.getY(), tabPane);
        if (!tabAreaInnerBounds.contains(p)) {
            return -1;
        }
        return tabForCoordinate(tabPane, p.x, p.y);
    }

    private boolean isTabAdjacentToContentAreaBorder(final int selectedIndex) {
        return getRunForTab(tabPane.getTabCount(), selectedIndex) == 0;
    }

    private void updateScrollButtons() {
        leftScrollButton.setEnabled(!tabAreaInnerBounds.contains(
                getTabBounds(0, calcRect)));
        rightScrollButton.setEnabled(!tabAreaInnerBounds.contains(
                getTabBounds(tabPane.getTabCount() - 1, calcRect)));

        if (!leftScrollButton.isEnabled() && !rightScrollButton.isEnabled()) {
            leftScrollButton.setVisible(false);
            rightScrollButton.setVisible(false);
        } else {
            leftScrollButton.setVisible(true);
            rightScrollButton.setVisible(true);
        }
    }
}
TOP

Related Classes of javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.