/*
 * Copyright (c) 2001-2011 JGoodies Karsten Lentzsch. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of JGoodies Karsten Lentzsch nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jgoodies.looks.plastic;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.metal.MetalTabbedPaneUI;
import javax.swing.text.View;

import com.jgoodies.looks.Options;

/**
 * The JGoodies Plastic Look&amp;Feel implementation of
 * {@code TabbedPaneUI}. It differs from its superclass
 * {@code MetalTabbedPaneUI} in that it paints new tab shapes,
 * provides two options, and supports ClearLook.
 * <p>
 * You can enable or disable icons in tabs globally via
 * com.jgoodies.looks.Options.setTabIconsEnabled(boolean).
 * <p>
 * To disable the content border set
 * <pre>
 * JTabbedPane tabbedPane = new JTabbedPane();
 * tabbedPane.putClientProperty(Option.NO_CONTENT_BORDER_KEY, Boolean.TRUE);
 * </pre>
 * To paint embedded tabs use
 * <pre>
 * JTabbedPane tabbedPane = new JTabbedPane();
 * tabbedPane.putClientProperty(Option.EMBEDDED_TABS_KEY, Boolean.TRUE);
 * </pre>
 * <p>
 * There's a special mode that helps you detect content borders in
 * heavily wrapped component hierarchies - such as the NetBeans IDE.
 * In this marked mode the content border is painted as a Magenta line.
 * You can enable this mode by setting the System property
 * <tt>markContentBorders</tt> to <tt>true</tt>; in a command line:
 * <pre>
 * java -DmarkContentBorders=true
 * </pre><p>
 *
 * Thanks to Andrej Golovnin for his feedback and suggestions.
 *
 * @author  Karsten Lentzsch
 * @version $Revision: 1.15 $
 *
 * @see     Options
 */
public final class PlasticTabbedPaneUI extends MetalTabbedPaneUI {


    // State ******************************************************************

    /**
     * Describes if tabs are painted with or without icons.
     */
    private static boolean isTabIconsEnabled = Options.isTabIconsEnabled();

    /**
     * Describes if we paint no content border or not; is false by default.
     * You can disable the content border by setting the client property
     * Options.NO_CONTENT_BORDER_KEY to Boolean.TRUE;
     */
    private Boolean noContentBorder;

    /**
     * Describes if we paint tabs in an embedded style that is with
     * less decoration; this is false by default.
     * You can enable the embedded tabs style by setting the client property
     * Options.EMBEDDED_TABS_KEY to Boolean.TRUE.
     */
    private Boolean embeddedTabs;

    /**
     * Holds the renderer that is used to render the tabs.
     */
    private AbstractRenderer renderer;


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

    /**
     * Creates the {@code PlasticTabbedPaneUI}.
     *
     * @see javax.swing.plaf.ComponentUI#createUI(JComponent)
     */
    public static ComponentUI createUI(JComponent tabPane) {
        return new PlasticTabbedPaneUI();
    }

    /**
     * Installs the UI.
     *
     * @see javax.swing.plaf.ComponentUI#installUI(JComponent)
     */
    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        embeddedTabs    = (Boolean) c.getClientProperty(Options.EMBEDDED_TABS_KEY);
        noContentBorder = (Boolean) c.getClientProperty(Options.NO_CONTENT_BORDER_KEY);
        renderer = createRenderer(tabPane);
    }

    /**
     * Uninstalls the UI.
     * @see javax.swing.plaf.ComponentUI#uninstallUI(JComponent)
     */
    @Override
    public void uninstallUI(JComponent c) {
        renderer = null;
        super.uninstallUI(c);
    }

    /**
     * Creates and installs any required subcomponents for the JTabbedPane.
     * Invoked by installUI.
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installComponents()
     */
    @Override
    protected void installComponents() {
        if (scrollableTabLayoutEnabled()) {
            if (tabScroller == null) {
                tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
                tabPane.add(tabScroller.viewport);
            }
        }
    }

    /**
     * Removes any installed subcomponents from the JTabbedPane.
     * Invoked by uninstallUI.
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallComponents()
     */
    @Override
    protected void uninstallComponents() {
        if (scrollableTabLayoutEnabled()) {
            tabPane.remove(tabScroller.viewport);
            tabPane.remove(tabScroller.scrollForwardButton);
            tabPane.remove(tabScroller.scrollBackwardButton);
            tabScroller = null;
        }
    }


    @Override
    protected void installKeyboardActions() {
        super.installKeyboardActions();
        // if the layout policy is the SCROLL_TAB_LAYOUT, then replace
        // the forward and backward actions, installed in the action map
        // in the supper class, by our own.
        if (scrollableTabLayoutEnabled()) {
            Action forwardAction = new ScrollTabsForwardAction();
            Action backwardAction = new ScrollTabsBackwardAction();
            ActionMap am = SwingUtilities.getUIActionMap(tabPane);
            am.put("scrollTabsForwardAction", forwardAction);
            am.put("scrollTabsBackwardAction", backwardAction);
            tabScroller.scrollForwardButton.setAction(forwardAction);
            tabScroller.scrollBackwardButton.setAction(backwardAction);
        }
    }

    /**
     * Checks and answers if content border will be painted.
     * This is controlled by the component's client property
     * Options.NO_CONTENT_BORDER or Options.EMBEDDED.
     */
    private boolean hasNoContentBorder() {
        return Boolean.TRUE.equals(noContentBorder);
    }

    /**
     * Checks and answers if tabs are painted with minimal decoration.
     */
    private boolean hasEmbeddedTabs() {
        return Boolean.TRUE.equals(embeddedTabs);
    }

    /**
     * Creates the renderer used to lay out and paint the tabs.
     * @param tabbedPane               the UIs component
     * @return AbstractRenderer     the renderer that will be used to paint
     */
    private AbstractRenderer createRenderer(JTabbedPane tabbedPane) {
        return hasEmbeddedTabs()
                ? AbstractRenderer.createEmbeddedRenderer(tabbedPane)
                : AbstractRenderer.createRenderer(tabPane);
    }

    /**
     * Creates and answer a handler that listens to property changes.
     * Unlike the superclass BasicTabbedPane, the PlasticTabbedPaneUI
     * uses an extended Handler.
     */
    @Override
    protected PropertyChangeListener createPropertyChangeListener() {
        return new MyPropertyChangeHandler();
    }

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

    /*
     * Private helper method for the next three methods.
     */
    private void doLayout() {
        tabPane.revalidate();
        tabPane.repaint();
    }

     /**
      * Updates the renderer and layout. This message is sent by
      * my PropertyChangeHandler whenever the tab placement changes.
      */
     private void tabPlacementChanged() {
         renderer = createRenderer(tabPane);
         if (scrollableTabLayoutEnabled()) {
             tabScroller.createButtons();
         }
         doLayout();
     }

    /**
     * Updates the embedded tabs property. This message is sent by
     * my PropertyChangeHandler whenever the embedded tabs property changes.
     */
    private void embeddedTabsPropertyChanged(Boolean newValue) {
        embeddedTabs = newValue;
        renderer = createRenderer(tabPane);
        doLayout();
    }

     /**
      * Updates the no content border property. This message is sent
      * by my PropertyChangeHandler whenever the noContentBorder
      * property changes.
      */
     private void noContentBorderPropertyChanged(Boolean newValue) {
         noContentBorder = newValue;
         tabPane.repaint();
     }

     @Override
    public void paint(Graphics g, JComponent c) {
         int selectedIndex = tabPane.getSelectedIndex();
         int tabPlacement = tabPane.getTabPlacement();

         ensureCurrentLayout();

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

         // Paint content border
         paintContentBorder(g, tabPlacement, selectedIndex);
     }

     @Override
    protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects,
             int tabIndex, Rectangle iconRect, Rectangle textRect) {
         Rectangle tabRect = rects[tabIndex];
         int selectedIndex = tabPane.getSelectedIndex();
         boolean isSelected = selectedIndex == tabIndex;
         Graphics2D g2 = null;
         Polygon cropShape = null;
         Shape save = null;
         int cropx = 0;
         int cropy = 0;

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

                 // Render visual for cropped tab edge...
                 Rectangle viewRect = tabScroller.viewport.getViewRect();
                 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) {
                     save = g.getClip();
                     g2.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);

         String title = tabPane.getTitleAt(tabIndex);
         Font font = tabPane.getFont();
         FontMetrics metrics = g.getFontMetrics(font);
         Icon icon = getIconForTab(tabIndex);

         layoutLabel(tabPlacement, metrics, tabIndex, title, icon, tabRect,
                 iconRect, textRect, isSelected);

         paintText(g, tabPlacement, font, metrics, tabIndex, title, textRect,
                 isSelected);

         paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);

         paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect,
                 textRect, isSelected);

         if (cropShape != null) {
             paintCroppedTabEdge(g, tabPlacement, tabIndex, isSelected, cropx,
                     cropy);
             g.setClip(save);
         }
     }

     /*
      * 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 };

     private static final int CROP_SEGMENT = 12;

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

         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++;
         }
         int npts = 2 + rcnt * 8;
         int[] xp = new int[npts];
         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);

         }
         //LEFT or RIGHT
         return new Polygon(yp, xp, pcnt);
     }

     /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
      * indicating the tab is cropped in the viewport display
      */
     private void paintCroppedTabEdge(Graphics g, int tabPlacement,
             int tabIndex, boolean isSelected, int x, int y) {
         switch (tabPlacement) {
             case LEFT:
             case RIGHT:
                 int xx = x;
                 g.setColor(shadow);
                 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;
                 g.setColor(shadow);
                 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;
                 }
         }
     }

     private void ensureCurrentLayout() {
         if (!tabPane.isValid()) {
             tabPane.validate();
         }
         /* If tabPane doesn't have a peer yet, the validate() call will
          * silently fail.  We handle that by forcing a layout if tabPane
          * is still invalid.  See bug 4237677.
          */
         if (!tabPane.isValid()) {
             TabbedPaneLayout layout = (TabbedPaneLayout) tabPane.getLayout();
             layout.calculateLayoutInfo();
         }
     }

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

         if (scrollableTabLayoutEnabled()) {
             translatePointToTabPanel(x, y, p);
             Rectangle viewRect = tabScroller.viewport.getViewRect();
             if (!viewRect.contains(p)) {
                 return -1;
             }
         }
         int tabCount = tabPane.getTabCount();
         for (int i = 0; i < tabCount; i++) {
             if (rects[i].contains(p.x, p.y)) {
                 return i;
             }
         }
         return -1;
     }

     @Override
    protected Rectangle getTabBounds(int tabIndex, 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
             Point vpp = tabScroller.viewport.getLocation();
             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 index of the tab closest to the passed in location, note
      * that the returned tab may not contain the location x,y.
      */
     private int getClosestTab(int x, int y) {
         int min = 0;
         int tabCount = Math.min(rects.length, tabPane.getTabCount());
         int max = tabCount;
         int tabPlacement = tabPane.getTabPlacement();
         boolean useX = tabPlacement == TOP || tabPlacement == BOTTOM;
         int want = useX ? x : y;

         while (min != max) {
             int current = (max + min) / 2;
             int minLoc;
             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, tabCount - 1);
                 }
             } else {
                 return current;
             }
         }
         return min;
     }

     /**
      * 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(int srcx, int srcy, Point dest) {
         Point vpp = tabScroller.viewport.getLocation();
         Point viewp = tabScroller.viewport.getViewPosition();
         dest.x = srcx - vpp.x + viewp.x;
         dest.y = srcy - vpp.y + viewp.y;
         return dest;
     }

    @Override
    protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
        int tabCount = tabPane.getTabCount();

        Rectangle iconRect = new Rectangle(),
                  textRect = new Rectangle();
        Rectangle clipRect = g.getClipBounds();

        // Paint tabRuns of tabs from back to front
        for (int i = runCount - 1; i >= 0; i--) {
            int start = tabRuns[i];
            int next = tabRuns[i == runCount - 1 ? 0 : i + 1];
            int end = next != 0 ? next - 1 : tabCount - 1;
            for (int j = end; j >= start; j--) {
                if (j != selectedIndex && 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 && rects[selectedIndex].intersects(clipRect)) {
            paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
        }
    }

    /*
     * Copied here from super(super)class to avoid labels being centered on
     * vertical tab runs if they consist of icon and text
     */
    @Override
    protected void layoutLabel(
        int tabPlacement,
        FontMetrics metrics,
        int tabIndex,
        String title,
        Icon icon,
        Rectangle tabRect,
        Rectangle iconRect,
        Rectangle textRect,
        boolean isSelected) {
        textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
        //fix of issue #4
        View v = getTextViewForTab(tabIndex);
        if (v != null) {
            tabPane.putClientProperty("html", v);
        }

        Rectangle calcRectangle = new Rectangle(tabRect);
        if (isSelected) {
        	Insets calcInsets = getSelectedTabPadInsets(tabPlacement);
        	calcRectangle.x += calcInsets.left;
        	calcRectangle.y += calcInsets.top;
        	calcRectangle.width  -= calcInsets.left + calcInsets.right;
        	calcRectangle.height -= calcInsets.bottom + calcInsets.top;
        }
		int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
		int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
        if ((tabPlacement == RIGHT || tabPlacement == LEFT) && icon != null && title != null && !title.equals("")) {
            SwingUtilities.layoutCompoundLabel(
                tabPane,
                metrics,
                title,
                icon,
                SwingConstants.CENTER,
                SwingConstants.LEFT,
                SwingConstants.CENTER,
                SwingConstants.TRAILING,
                calcRectangle,
                iconRect,
                textRect,
                textIconGap);
            xNudge += 4;
        } else {
            SwingUtilities.layoutCompoundLabel(
                tabPane,
                metrics,
                title,
                icon,
                SwingConstants.CENTER,
                SwingConstants.CENTER,
                SwingConstants.CENTER,
                SwingConstants.TRAILING,
                calcRectangle,
                iconRect,
                textRect,
                textIconGap);
                iconRect.y += calcRectangle.height % 2;
        }

        //fix of issue #4
        tabPane.putClientProperty("html", null);

        iconRect.x += xNudge;
        iconRect.y += yNudge;
        textRect.x += xNudge;
        textRect.y += yNudge;
    }

    /**
     * Answers the icon for the tab with the specified index.
     * In case, we have globally switched of the use tab icons,
     * we answer {@code null} if and only if we have a title.
     */
    @Override
    protected Icon getIconForTab(int tabIndex) {
        String title = tabPane.getTitleAt(tabIndex);
        boolean hasTitle = title != null && title.length() > 0;
        return !isTabIconsEnabled  && hasTitle
                    ? null
                    : super.getIconForTab(tabIndex);
    }

    /**
     * Creates the layout manager used to set the tab's bounds.
     */
    @Override
    protected LayoutManager createLayoutManager() {
        if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
            return new TabbedPaneScrollLayout();
        }
        /* WRAP_TAB_LAYOUT */
        return new TabbedPaneLayout();
    }

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

    protected boolean isTabInFirstRun(int tabIndex) {
        return getRunForTab(tabPane.getTabCount(), tabIndex) == 0;
    }

    @Override
    protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
        int    width  = tabPane.getWidth();
        int    height = tabPane.getHeight();
        Insets insets = tabPane.getInsets();

        int x = insets.left;
        int y = insets.top;
        int w = width - insets.right - insets.left;
        int h = height - insets.top - insets.bottom;

        switch (tabPlacement) {
            case LEFT :
                x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                w -= x - insets.left;
                break;
            case RIGHT :
                w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                break;
            case BOTTOM :
                h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                break;
            case TOP :
            default :
                y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                h -= y - insets.top;
        }
        // Fill region behind content area
        g.setColor(selectColor == null
                        ? tabPane.getBackground()
                        : selectColor);
        g.fillRect(x, y, w, h);

        Rectangle selRect;
        selRect = selectedIndex < 0 ? null : getTabBounds(selectedIndex, calcRect);
        boolean drawBroken = selectedIndex >= 0 && isTabInFirstRun(selectedIndex);
        boolean isContentBorderPainted = !hasNoContentBorder();
        // It sounds a bit odd to call paintContentBorder with
        // a parameter isContentBorderPainted set to false.
        // But in this case the part of the border touching the tab
        // area will still be painted so best let the renderer decide.
        renderer.paintContentBorderTopEdge   (g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
        renderer.paintContentBorderLeftEdge  (g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
        renderer.paintContentBorderBottomEdge(g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
        renderer.paintContentBorderRightEdge (g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
    }

    //
    // Here comes a number of methods that are just delegated to the
    // appropriate renderer
    //
    /**
     * Returns the insets (i.e. the width) of the content Border.
     */
    @Override
    protected Insets getContentBorderInsets(int tabPlacement) {
        return renderer.getContentBorderInsets(super.getContentBorderInsets(tabPlacement));
    }

    /**
     * Returns the amount by which the Tab Area is inset.
     */
    @Override
    protected Insets getTabAreaInsets(int tabPlacement) {
        return renderer.getTabAreaInsets(super.getTabAreaInsets(tabPlacement));
    }

    /**
     * Returns the amount by which the label should be shifted horizontally.
     */
    @Override
    protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
        return renderer.getTabLabelShiftX(tabIndex, isSelected);
    }

    /**
     * Returns the amount by which the label should be shifted vertically.
     */
    @Override
    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
        return renderer.getTabLabelShiftY(tabIndex, isSelected);
    }

    /**
     * Returns the amount (in pixels) by which two runs should overlap.
     */
    @Override
    protected int getTabRunOverlay(int tabPlacement) {
        return renderer.getTabRunOverlay(tabRunOverlay);
    }

    /**
     * This boolean controls wheather the given run should be padded to
     * use up as much space as the others (with more tabs in them).
     */
    @Override
    protected boolean shouldPadTabRun(int tabPlacement, int run) {
        return renderer.shouldPadTabRun(run, super.shouldPadTabRun(tabPlacement, run));
    }

    /**
     * Returns the amount by which the run number {@code run}
     * should be indented. Add six pixels for every run to make
     * diagonal lines align.
     */
    @Override
    protected int getTabRunIndent(int tabPlacement, int run) {
        return renderer.getTabRunIndent(run);
    }

    /**
     * Returns the insets for this tab.
     */
    @Override
    protected Insets getTabInsets(int tabPlacement, int tabIndex) {
        return renderer.getTabInsets(tabIndex, tabInsets);
    }

    /**
     * Returns the insets for selected tab.
     */
    @Override
    protected Insets getSelectedTabPadInsets(int tabPlacement) {
        return renderer.getSelectedTabPadInsets();
    }

    /**
     * Draws the rectancle around the Tab label which indicates keyboard focus.
     */
    @Override
    protected void paintFocusIndicator(
        Graphics g,
        int tabPlacement,
        Rectangle[] rectangles,
        int tabIndex,
        Rectangle iconRect,
        Rectangle textRect,
        boolean isSelected) {
        renderer.paintFocusIndicator(g, rectangles, tabIndex, iconRect, textRect, isSelected);
    }

    /**
     * Fills the background of the given tab to make sure overlap of
     * tabs is handled correctly.
     * Note: that tab backgrounds seem to be painted somewhere else, too.
     */
    @Override
    protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
        renderer.paintTabBackground(g, tabIndex, x, y, w, h, isSelected);
    }

    /**
     * Paints the border for one tab. Gets the bounds of the tab as parameters.
     * Note that the result is not clipped so you can paint outside that
     * rectangle. Tabs painted later on have a chance to overwrite though.
     */
    @Override
    protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
        renderer.paintTabBorder(g, tabIndex, x, y, w, h, isSelected);
    }

    /**
     * Answers wheather tab runs should be rotated. If true, the layout mechanism
     * will move the run containing the selected tab so that it touches
     * the content pane.
     */
    @Override
    protected boolean shouldRotateTabRuns(int tabPlacement) {
        return false;
    }

    private final class TabSelectionHandler implements ChangeListener {

        private final Rectangle rect = new Rectangle();

        public void stateChanged(ChangeEvent e) {
            JTabbedPane tabPane = (JTabbedPane) e.getSource();
            tabPane.revalidate();
            tabPane.repaint();

            if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
                int index = tabPane.getSelectedIndex();
                if (index < rects.length && index != -1) {
                    rect.setBounds(rects[index]);
                    Point viewPosition = tabScroller.viewport.getViewPosition();
                    if (rect.x < viewPosition.x) {
                        rect.x -= renderer.getTabsOverlay();
                    } else {
                        rect.x += renderer.getTabsOverlay();
                    }
                    tabScroller.tabPanel.scrollRectToVisible(rect);
                }
            }
        }
    }

    /**
     * Catches and handles property change events. In addition to the super
     * class behavior we listen to changes of the ancestor, tab placement,
     * and JGoodies options for content border, and embedded tabs.
     */
    private final class MyPropertyChangeHandler
        extends BasicTabbedPaneUI.PropertyChangeHandler {
        @Override
        public void propertyChange(PropertyChangeEvent e) {
            String pName = e.getPropertyName();

            if (null == pName) {
                return;
            }

            super.propertyChange(e);

            if (pName.equals("tabPlacement")) {
                tabPlacementChanged();
                return;
            }
            if (pName.equals(Options.EMBEDDED_TABS_KEY)) {
                embeddedTabsPropertyChanged((Boolean) e.getNewValue());
                return;
            }
            if (pName.equals(Options.NO_CONTENT_BORDER_KEY)) {
                noContentBorderPropertyChanged((Boolean) e.getNewValue());
                return;
            }
        }
    }

    /**
     * Does all the layout work. The result is stored in the container
     * class's instance variables. Mainly the rects[] vector.
     */
    private class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {

        @Override
        protected void calculateTabRects(int tabPlacement, int tabCount) {
            FontMetrics metrics = getFontMetrics();
            Dimension size = tabPane.getSize();
            Insets insets = tabPane.getInsets();
            Insets theTabAreaInsets = getTabAreaInsets(tabPlacement);
            int fontHeight    = metrics.getHeight();
            int selectedIndex = tabPane.getSelectedIndex();
            int theTabRunOverlay;
            int i, j;
            int x, y;
            int returnAt;
            boolean verticalTabRuns = tabPlacement == LEFT || tabPlacement == RIGHT;
            boolean leftToRight = PlasticUtils.isLeftToRight(tabPane);

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

            theTabRunOverlay = getTabRunOverlay(tabPlacement);

            runCount = 0;
            selectedRun = -1;
            //keeps track of where we are in the current run.
            //this helps not to rely on fragile positioning
            //informaion to find out wheter the active Tab
            //is the first in run
            int tabInRun = -1;
            // make a copy of returnAt for the current run and modify
            // that so returnAt may still be used later on
            int runReturnAt = returnAt;

            if (tabCount == 0) {
                return;
            }

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

                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;
                        //  tabInRun = 0;
                    }
                    rect.width  = calculateTabWidth(tabPlacement, i, metrics);
                    maxTabWidth = Math.max(maxTabWidth, rect.width);

                    // Never move a TAB down a run if it is the first in run.
                    // 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) {
                    // Never rely on phisical position information to determine
                    // logical position (if you can avoid it)
                    if (tabInRun != 0 && rect.x + rect.width > runReturnAt) {
                        if (runCount > tabRuns.length - 1) {
                            expandTabRunsArray();
                        }
                        // just created a new run, adjust some counters
                        tabInRun = 0;
                        tabRuns[runCount] = i;
                        runCount++;
                        rect.x = x;
                        runReturnAt = runReturnAt - 2 * getTabRunIndent(tabPlacement, runCount);
                    }
                    // 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;
                        //                        tabInRun = 0;
                    }
                    rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
                    maxTabHeight = Math.max(maxTabHeight, rect.height);

                    // Never move a TAB over a run if it is the first in run.
                    // Even if there isn't enough room, moving it to a fresh
                    // run won't help.
                    //                    if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
                    if (tabInRun != 0 && rect.y + rect.height > runReturnAt) {
                        if (runCount > tabRuns.length - 1) {
                            expandTabRunsArray();
                        }
                        tabRuns[runCount] = i;
                        runCount++;
                        rect.y = y;
                        tabInRun = 0;
                        runReturnAt -= 2 * getTabRunIndent(tabPlacement, runCount);
                    }
                    // 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
                //last line flush left is OK
                //                normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
                //don't need to recalculate selectedRun if not changed
                //                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--) {
                int start = tabRuns[i];
                int next  = tabRuns[i == runCount - 1 ? 0 : i + 1];
                int end   = next != 0 ? next - 1 : tabCount - 1;
                int indent = getTabRunIndent(tabPlacement, i);
                if (!verticalTabRuns) {
                    for (j = start; j <= end; j++) {
                        rect = rects[j];
                        rect.y = y;
                        rect.x += indent;
                        // try to make tabRunIndent symmetric
                        //	rect.width -= 2* indent + 20;
                    }
                    if (shouldPadTabRun(tabPlacement, i)) {
                        padTabRun(tabPlacement, start, end, returnAt - 2 * indent);
                    }
                    if (tabPlacement == BOTTOM) {
                        y -= maxTabHeight - theTabRunOverlay;
                    } else {
                        y += maxTabHeight - theTabRunOverlay;
                    }
                } else {
                    for (j = start; j <= end; j++) {
                        rect = rects[j];
                        rect.x = x;
                        rect.y += indent;
                    }
                    if (shouldPadTabRun(tabPlacement, i)) {
                        padTabRun(tabPlacement, start, end, returnAt - 2 * indent);
                    }
                    if (tabPlacement == RIGHT) {
                        x -= maxTabWidth - theTabRunOverlay;
                    } else {
                        x += maxTabWidth - theTabRunOverlay;
                    }
                }
            }

            // 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) {
                int rightMargin = size.width - (insets.right + theTabAreaInsets.right);
                for (i = 0; i < tabCount; i++) {
                    rects[i].x = rightMargin - rects[i].x - rects[i].width
                        + renderer.getTabsOverlay();
                }
            }
        }

        /**
         * Overridden to insure the same behavior in JDK 6.0 as in JDK 5.0.
         */
        @Override
        protected void padSelectedTab(int tabPlacement, int selectedIndex) {
            if (selectedIndex >= 0) {
                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;
            }
        }

    }


    private boolean requestFocusForVisibleComponent() {
        Component visibleComponent = getVisibleComponent();
        if (visibleComponent.isFocusable()) {
            visibleComponent.requestFocus();
            return true;
        }
        if (visibleComponent instanceof JComponent) {
            if (((JComponent) visibleComponent).requestDefaultFocus()) {
                return true;
            }
        }
        return false;
    }

    private static class ScrollTabsForwardAction extends AbstractAction {

        public void actionPerformed(ActionEvent e) {
            JTabbedPane pane = null;
            Object src = e.getSource();
            if (src instanceof JTabbedPane) {
                pane = (JTabbedPane) src;
            } else if (src instanceof PlasticArrowButton) {
                pane = (JTabbedPane) ((PlasticArrowButton) src).getParent();
            } else {
                return; // shouldn't happen
            }
            PlasticTabbedPaneUI ui = (PlasticTabbedPaneUI) pane.getUI();

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

    private static class ScrollTabsBackwardAction extends AbstractAction {

        public void actionPerformed(ActionEvent e) {
            JTabbedPane pane = null;
            Object src = e.getSource();
            if (src instanceof JTabbedPane) {
                pane = (JTabbedPane) src;
            } else if (src instanceof PlasticArrowButton) {
                pane = (JTabbedPane) ((PlasticArrowButton) src).getParent();
            } else {
                return; // shouldn't happen
            }
            PlasticTabbedPaneUI ui = (PlasticTabbedPaneUI) pane.getUI();

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

    private final class TabbedPaneScrollLayout extends TabbedPaneLayout {

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

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

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

            calculateLayoutInfo();

            if (selectedIndex < 0) {
                if (visibleComponent != null) {
                    // The last tab was removed, so remove the component
                    setVisibleComponent(null);
                }
            } else {
                Component selectedComponent = tabPane.getComponentAt(selectedIndex);
                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);
                }
                int tx, ty, tw, th; // tab area bounds
                int cx, cy, cw, ch; // content area bounds
                Insets contentInsets = getContentBorderInsets(tabPlacement);
                Rectangle bounds = tabPane.getBounds();
                int numChildren = tabPane.getComponentCount();

                if (numChildren > 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++) {
                        Component child = tabPane.getComponent(i);

                        if (tabScroller != null && child == tabScroller.viewport) {
                            JViewport viewport = (JViewport) child;
                            Rectangle viewRect = viewport.getViewRect();
                            int vw = tw;
                            int vh = th;
                            Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
                            switch (tabPlacement) {
                                case LEFT:
                                case RIGHT:
                                    int totalTabHeight = rects[tabCount - 1].y
                                            + rects[tabCount - 1].height;
                                    if (totalTabHeight > th) {
                                        // Allow space for scrollbuttons
                                        vh = th > 2 * butSize.height ? th - 2
                                                * butSize.height : 0;
                                        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:
                                    int totalTabWidth = rects[tabCount - 1].x
                                            + rects[tabCount - 1].width + renderer.getTabsOverlay();
                                    if (totalTabWidth > tw) {
                                        // Need to allow space for scrollbuttons
                                        vw = tw > 2 * butSize.width ? tw - 2
                                                * butSize.width : 0;
                                        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 (tabScroller != null &&
                                   (child == tabScroller.scrollForwardButton ||
                                    child == tabScroller.scrollBackwardButton)) {
                            Component scrollbutton = child;
                            Dimension bsize = scrollbutton.getPreferredSize();
                            int bx = 0;
                            int by = 0;
                            int bw = bsize.width;
                            int bh = bsize.height;
                            boolean visible = false;

                            switch (tabPlacement) {
                                case LEFT:
                                case RIGHT:
                                    int totalTabHeight = rects[tabCount - 1].y
                                            + rects[tabCount - 1].height
                                            + renderer.getTabsOverlay();
                                    if (totalTabHeight > th) {
                                        visible = true;
                                        bx = tabPlacement == LEFT ? tx + tw
                                                - bsize.width : tx;
                                        by = child == tabScroller.scrollForwardButton ? bounds.height
                                                - insets.bottom - bsize.height
                                                : bounds.height - insets.bottom - 2
                                                        * bsize.height;
                                    }
                                    break;

                                case BOTTOM:
                                case TOP:
                                default:
                                    int totalTabWidth = rects[tabCount - 1].x
                                            + rects[tabCount - 1].width
                                            + renderer.getTabsOverlay();

                                    if (totalTabWidth > tw) {
                                        visible = true;
                                        bx = child == tabScroller.scrollForwardButton ? bounds.width
                                                - insets.left - bsize.width
                                                : bounds.width - insets.left - 2
                                                        * bsize.width;
                                        by = tabPlacement == TOP ? ty + th
                                                - bsize.height : ty;
                                    }
                            }
                            child.setVisible(visible);
                            if (visible) {
                                child.setBounds(bx, by, bw, bh);
                            }

                        } else {
                            // All content children...
                            child.setBounds(cx, cy, cw, ch);
                        }
                    }
                    if (shouldChangeFocus) {
                        if (!requestFocusForVisibleComponent()) {
                           tabPane.requestFocus();
                        }
                    }
                }
            }
        }

        @Override
        protected void calculateTabRects(int tabPlacement, int tabCount) {
            FontMetrics metrics = getFontMetrics();
            Dimension size = tabPane.getSize();
            Insets insets = tabPane.getInsets();
            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
            int fontHeight = metrics.getHeight();
            int selectedIndex = tabPane.getSelectedIndex();
            boolean verticalTabRuns = tabPlacement == LEFT || tabPlacement == RIGHT;
            boolean leftToRight = PlasticUtils.isLeftToRight(tabPane);
            int x = tabAreaInsets.left;
            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, metrics);
                    totalWidth = rect.x + rect.width + renderer.getTabsOverlay();
                    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*/;

                }
            }

            // 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) {
                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;
                }
            }
            tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
        }
    }

    private final class ScrollableTabSupport implements ActionListener,
            ChangeListener {

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

        ScrollableTabSupport(int tabPlacement) {
            viewport = new ScrollableTabViewport();
            tabPanel = new ScrollableTabPanel();
            viewport.setView(tabPanel);
            viewport.addChangeListener(this);
            createButtons();
        }

        /**
         * Recreates the scroll buttons and adds them to the TabbedPane.
         */
        void createButtons() {
            if (scrollForwardButton != null) {
                tabPane.remove(scrollForwardButton);
                scrollForwardButton.removeActionListener(this);
                tabPane.remove(scrollBackwardButton);
                scrollBackwardButton.removeActionListener(this);
            }
            int tabPlacement = tabPane.getTabPlacement();
            int width = UIManager.getInt("ScrollBar.width");
            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                scrollForwardButton = new ArrowButton(EAST, width);
                scrollBackwardButton = new ArrowButton(WEST, width);
            } else { // tabPlacement = LEFT || RIGHT
                scrollForwardButton = new ArrowButton(SOUTH, width);
                scrollBackwardButton = new ArrowButton(NORTH, width);
            }
            scrollForwardButton.addActionListener(this);
            scrollBackwardButton.addActionListener(this);
            tabPane.add(scrollForwardButton);
            tabPane.add(scrollBackwardButton);
        }

        public void scrollForward(int tabPlacement) {
            Dimension viewSize = viewport.getViewSize();
            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);
        }

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

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

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

                    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
                        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
                        Dimension extentSize = new Dimension(viewRect.width,
                                viewSize.height - tabViewPosition.y);
                        viewport.setExtentSize(extentSize);
                    }
            }
            viewport.setViewPosition(tabViewPosition);
        }

        public void stateChanged(ChangeEvent e) {
            JViewport viewport = (JViewport) e.getSource();
            int tabPlacement = tabPane.getTabPlacement();
            int tabCount = tabPane.getTabCount();
            Rectangle vpRect = viewport.getBounds();
            Dimension viewSize = viewport.getViewSize();
            Rectangle viewRect = viewport.getViewRect();

            leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);

            // If the tab isn't right aligned, adjust it.
            if (leadingTabIndex + 1 < tabCount) {
                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;
                }
            }
            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
                            && leadingTabIndex > 0);
                    scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 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
                            && leadingTabIndex > 0);
                    scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 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
                            && leadingTabIndex > 0);
                    scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 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
                            && leadingTabIndex > 0);
                    scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1
                            && viewSize.width - viewRect.x > viewRect.width);
            }
        }

        /**
         * ActionListener for the scroll buttons.
         */
        public void actionPerformed(ActionEvent e) {
            ActionMap map = tabPane.getActionMap();

            if (map != null) {
                String actionKey;

                if (e.getSource() == scrollForwardButton) {
                    actionKey = "scrollTabsForwardAction";
                } else {
                    actionKey = "scrollTabsBackwardAction";
                }
                Action action = map.get(actionKey);

                if (action != null && action.isEnabled()) {
                    action.actionPerformed(new ActionEvent(tabPane,
                            ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e
                                    .getModifiers()));
                }
            }
        }

    }

    private final class ScrollableTabViewport extends JViewport implements UIResource {

        public ScrollableTabViewport() {
            super();
            setName("TabbedPane.scrollableViewport");
            setScrollMode(SIMPLE_SCROLL_MODE);
            setOpaque(tabPane.isOpaque());
            Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
            if (bgColor == null) {
                bgColor = tabPane.getBackground();
            }
            setBackground(bgColor);
        }
    }

    private final class ScrollableTabPanel extends JPanel implements UIResource {

        public ScrollableTabPanel() {
            super(null);
            setOpaque(tabPane.isOpaque());
            Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
            if (bgColor == null) {
                bgColor = tabPane.getBackground();
            }
            setBackground(bgColor);
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            PlasticTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(),
                    tabPane.getSelectedIndex());

        }
    }

    private static final class ArrowButton extends JButton implements UIResource {

        private final int buttonWidth;
        private final int direction;
        private boolean mouseIsOver;

        ArrowButton(int direction, int buttonWidth) {
            this.direction = direction;
            this.buttonWidth = buttonWidth;
            setRequestFocusEnabled(false);
        }

        @Override
        protected void processMouseEvent(MouseEvent e) {
            super.processMouseEvent(e);
            switch (e.getID()) {
                case MouseEvent.MOUSE_ENTERED:
                    mouseIsOver = true;
                    revalidate();
                    repaint();
                    break;
                case MouseEvent.MOUSE_EXITED:
                    mouseIsOver = false;
                    revalidate();
                    repaint();
                    break;
            }
        }

        @Override
        protected void paintBorder(Graphics g) {
            if (mouseIsOver && isEnabled()) {
                super.paintBorder(g);
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            if (mouseIsOver) {
                super.paintComponent(g);
            } else {
                g.setColor(getBackground());
                g.fillRect(0, 0, getWidth(), getHeight());
            }
            paintArrow(g);
        }

        private void paintArrow(Graphics g) {
            Color oldColor = g.getColor();

            boolean isEnabled = isEnabled();
            g.setColor(isEnabled ? PlasticLookAndFeel.getControlInfo()
                                 : PlasticLookAndFeel.getControlDisabled());

            int arrowWidth, arrowHeight;
            switch (direction) {
                case NORTH:
                case SOUTH:
                    arrowWidth = 9;
                    arrowHeight = 5;
                    break;
                case WEST:
                case EAST:
                default:
                    arrowWidth = 5;
                    arrowHeight = 9;
                    break;
            }
            int x = (getWidth()  - arrowWidth ) / 2;
            int y = (getHeight() - arrowHeight) / 2;
            g.translate(x, y);

            boolean paintShadow = !mouseIsOver || !isEnabled;
            Color shadow = isEnabled ? PlasticLookAndFeel.getControlShadow()
                                     : UIManager.getColor("ScrollBar.highlight");

            switch (direction) {
                case NORTH:
                    g.fillRect(0, 4, 9, 1);
                    g.fillRect(1, 3, 7, 1);
                    g.fillRect(2, 2, 5, 1);
                    g.fillRect(3, 1, 3, 1);
                    g.fillRect(4, 0, 1, 1);
                    if (paintShadow) {
                        g.setColor(shadow);
                        g.fillRect(1, 5, 9, 1);
                    }
                    break;
                case SOUTH:
                    g.fillRect(0, 0, 9, 1);
                    g.fillRect(1, 1, 7, 1);
                    g.fillRect(2, 2, 5, 1);
                    g.fillRect(3, 3, 3, 1);
                    g.fillRect(4, 4, 1, 1);
                    if (paintShadow) {
                        g.setColor(shadow);
                        g.drawLine(5, 4, 8, 1);
                        g.drawLine(5, 5, 9, 1);
                    }
                    break;
                case WEST:
                    g.fillRect(0, 4, 1, 1);
                    g.fillRect(1, 3, 1, 3);
                    g.fillRect(2, 2, 1, 5);
                    g.fillRect(3, 1, 1, 7);
                    g.fillRect(4, 0, 1, 9);
                    if (paintShadow) {
                        g.setColor(shadow);
                        g.fillRect(5, 1, 1, 9);
                    }
                    break;
                case EAST:
                    g.fillRect(0, 0, 1, 9);
                    g.fillRect(1, 1, 1, 7);
                    g.fillRect(2, 2, 1, 5);
                    g.fillRect(3, 3, 1, 3);
                    g.fillRect(4, 4, 1, 1);
                    if (paintShadow) {
                        g.setColor(shadow);
                        g.drawLine(1, 8, 4, 5);
                        g.drawLine(1, 9, 5, 5);
                    }
                    break;
            }

            g.translate(-x, -y);
            g.setColor(oldColor);
        }

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

        @Override
        public Dimension getMinimumSize() {
            return getPreferredSize();
        }

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

    /**
     * This is the abstract superclass for all TabbedPane renderers.
     * Those will be defined in the rest of this file
     */
    private abstract static class AbstractRenderer {

        protected static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
        protected static final Insets NORTH_INSETS = new Insets(1, 0, 0, 0);
        protected static final Insets WEST_INSETS  = new Insets(0, 1, 0, 0);
        protected static final Insets SOUTH_INSETS = new Insets(0, 0, 1, 0);
        protected static final Insets EAST_INSETS  = new Insets(0, 0, 0, 1);

        protected final JTabbedPane tabPane;
        protected final int tabPlacement;
        protected Color shadowColor;
        protected Color darkShadow;
        protected Color selectColor;
        protected Color selectLight;
        protected Color selectHighlight;
        protected Color lightHighlight;
        protected Color focus;

        private AbstractRenderer(JTabbedPane tabPane) {
            initColors();
            this.tabPane = tabPane;
            this.tabPlacement = tabPane.getTabPlacement();
        }

        private static AbstractRenderer createRenderer(JTabbedPane tabPane) {
            switch (tabPane.getTabPlacement()) {
                case SwingConstants.TOP :
                    return new TopRenderer(tabPane);
                case SwingConstants.BOTTOM :
                    return new BottomRenderer(tabPane);
                case SwingConstants.LEFT :
                    return new LeftRenderer(tabPane);
                case SwingConstants.RIGHT :
                    return new RightRenderer(tabPane);
                default :
                    return new TopRenderer(tabPane);
            }
        }

        private static AbstractRenderer createEmbeddedRenderer(JTabbedPane tabPane) {
            switch (tabPane.getTabPlacement()) {
                case SwingConstants.TOP :
                    return new TopEmbeddedRenderer(tabPane);
                case SwingConstants.BOTTOM :
                    return new BottomEmbeddedRenderer(tabPane);
                case SwingConstants.LEFT :
                    return new LeftEmbeddedRenderer(tabPane);
                case SwingConstants.RIGHT :
                    return new RightEmbeddedRenderer(tabPane);
                default :
                    return new TopEmbeddedRenderer(tabPane);
            }
        }

        private void initColors() {
            shadowColor     = UIManager.getColor("TabbedPane.shadow");
            darkShadow      = UIManager.getColor("TabbedPane.darkShadow");
            selectColor     = UIManager.getColor("TabbedPane.selected");
            focus           = UIManager.getColor("TabbedPane.focus");
            selectHighlight = UIManager.getColor("TabbedPane.selectHighlight");
            lightHighlight  = UIManager.getColor("TabbedPane.highlight");
            selectLight =
                new Color(
                    (2 * selectColor.getRed()   + selectHighlight.getRed())   / 3,
                    (2 * selectColor.getGreen() + selectHighlight.getGreen()) / 3,
                    (2 * selectColor.getBlue()  + selectHighlight.getBlue())  / 3);
        }

        protected boolean isFirstDisplayedTab(int tabIndex, int position, int paneBorder) {
            return tabIndex == 0;
//            return (position - paneBorder) < 8;
        }

        protected Insets getTabAreaInsets(Insets defaultInsets) {
            return defaultInsets;
        }

        protected Insets getContentBorderInsets(Insets defaultInsets) {
            return defaultInsets;
        }

        /**
         * Returns the amount by which the label should be shifted horizontally.
         */
        protected int getTabLabelShiftX(int tabIndex, boolean isSelected) {
            return 0;
        }

        /**
         * Returns the amount by which the label should be shifted vertically.
         */
        protected int getTabLabelShiftY(int tabIndex, boolean isSelected) {
            return 0;
        }

        /**
         * Returns the amount of overlap for two Runs.
         */
        protected int getTabRunOverlay(int tabRunOverlay) {
            return tabRunOverlay;
        }

        /**
         * Returns if a run should be padded with empty space
         * to take up as much room as the others.
         */
        protected boolean shouldPadTabRun(int run, boolean aPriori) {
            return aPriori;
        }

        /**
         * Returns the amount by which the run number {@code run}
         * should be indented. Add a few pixels for every run to make
         * diagonal lines align.
         */
        protected int getTabRunIndent(int run) {
            return 0;
        }

        /**
         * Returns the insets for the given tab.
         */
        protected abstract Insets getTabInsets(int tabIndex, Insets tabInsets);

        /**
         * Draws the rectancle around the Tab label which indicates keyboard focus.
         */
        protected abstract void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected);

        /**
         * Fills the background of the given tab to make sure overlap of
         * tabs is handled correctly.
         */
        protected abstract void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected);

        /**
         * Paints the border around the given tab.
         */
        protected abstract void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected);

        /**
         * Returns additional the insets for the selected tab. This allows to "raise"
         * The selected tab over the others
         */
        protected Insets getSelectedTabPadInsets() {
            return EMPTY_INSETS;
        }

        /**
         * Draws the top edge of the border around the content area.
         * Draw unbroken line for tabs are not on TOP
         * override where appropriate.
         */
        protected void paintContentBorderTopEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            if (isContentBorderPainted) {
                g.setColor(selectHighlight);
                g.fillRect(x, y, w - 1, 1);
            }
        }

        /**
         * Draws the bottom edge of the Border around the content area.
         * Draw broken line if selected tab is visible and adjacent to content
         * and TabPlacement is same as painted edge.
         */
        protected void paintContentBorderBottomEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            if (isContentBorderPainted) {
                g.setColor(darkShadow);
                g.fillRect(x, y + h - 1, w - 1, 1);
            }
        }

        /**
         * Draws the left edge of the Border around the content area.
         * Draw broken line if selected tab is visible and adjacent to content
         * and TabPlacement is same as painted edge
         */
        protected void paintContentBorderLeftEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            if (isContentBorderPainted) {
                g.setColor(selectHighlight);
                g.fillRect(x, y, 1, h - 1);
            }
        }

        /**
         * Draws the right edge of the Border around the content area.
         * Draw broken line if selected tab is visible and adjacent to content
         * and TabPlacement is same as painted edge
         */
        protected void paintContentBorderRightEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            if (isContentBorderPainted) {
                g.setColor(darkShadow);
               g.fillRect(x + w - 1, y, 1, h);
            }
        }

        /**
         * Returns the amount of overlap for two tabs.
         */
        protected int getTabsOverlay() {
            return 0;
        }
    }

    /**
     * The renderer for the case where tabs are displayed below the contents
     * and with minimal decoration.
     */
    private static final class BottomEmbeddedRenderer extends AbstractRenderer {

        private BottomEmbeddedRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected Insets getTabAreaInsets(Insets insets) {
            return EMPTY_INSETS;
        }

        @Override
        protected Insets getContentBorderInsets(Insets defaultInsets) {
            return SOUTH_INSETS;
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return EMPTY_INSETS;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top, tabInsets.left, tabInsets.bottom, tabInsets.right);
        }

        /**
         * Paints no focus: minimal decoration is really minimal.
         */
        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {
            // Embedded tabs paint no focus.
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            g.setColor(selectColor);
            g.fillRect(x, y, w + 1, h);
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            int bottom = h;
            int right = w + 1;

            g.translate(x, y);
            if (isFirstDisplayedTab(tabIndex, x, tabPane.getBounds().x)) {
                if (isSelected) {
                    // selected and first in line
                    g.setColor(shadowColor);
                    g.fillRect(right, 0, 1, bottom - 1);
                    g.fillRect(right - 1, bottom - 1, 1, 1);
                    // it is open to discussion if the outer border of the tab
                    // should be painted because in the primary case it won't
                    // be visible anyway. uncomment the following two lines if wanted
                    //                    g.fillRect(0,bottom, right, 1);
                    //                    g.fillRect(-1,0,1,bottom;
                    g.setColor(selectHighlight);
                    g.fillRect(0, 0, 1, bottom);
                    g.fillRect(right - 1, 0, 1, bottom - 1);
                    g.fillRect(1, bottom - 1, right - 2, 1);
                } else {
                    //not selected and first in line
                }
            } else {
                if (isSelected) {
                    //selected and not first in line
                    g.setColor(shadowColor);
                    g.fillRect(0, 0, 1, bottom - 1);
                    g.fillRect(1, bottom - 1, 1, 1);
                    g.fillRect(right, 0, 1, bottom - 1);
                    g.fillRect(right - 1, bottom - 1, 1, 1);
                    // outside line:
                    //                    g.fillRect(2,bottom, right-3, 1);
                    g.setColor(selectHighlight);
                    g.fillRect(1, 0, 1, bottom - 1);
                    g.fillRect(right - 1, 0, 1, bottom - 1);
                    g.fillRect(2, bottom - 1, right - 3, 1);
                } else {
                    g.setColor(shadowColor);
                    g.fillRect(1, h / 2, 1, h - h / 2);
                }
            }
            g.translate(-x, -y);
        }

        @Override
        protected void paintContentBorderBottomEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {

            g.setColor(shadowColor);
            g.fillRect(x, y + h - 1, w, 1);
        }

    }

    /**
     * The renderer for the case where Tabs are below the content and
     * decoration is standard.
     */
    private static final class BottomRenderer extends AbstractRenderer {

        private BottomRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected Insets getTabAreaInsets(Insets defaultInsets) {
            return new Insets(defaultInsets.top, defaultInsets.left + 5, defaultInsets.bottom, defaultInsets.right);
        }

		@Override
        protected int getTabLabelShiftY(int tabIndex, boolean isSelected) {
			return isSelected ? 0 : -1;
		}

        @Override
        protected int getTabRunOverlay(int tabRunOverlay) {
            return tabRunOverlay - 2;
        }

        @Override
        protected int getTabRunIndent(int run) {
            return 6 * run;
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return SOUTH_INSETS;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top, tabInsets.left - 2, tabInsets.bottom, tabInsets.right - 2);
        }

        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {

            if (!tabPane.hasFocus() || !isSelected) {
                return;
            }
            Rectangle tabRect = rects[tabIndex];
            int top = tabRect.y;
            int left = tabRect.x + 6;
            int height = tabRect.height - 3;
            int width = tabRect.width - 12;
            g.setColor(focus);
            g.drawRect(left, top, width, height);
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            g.setColor(selectColor);
            g.fillRect(x, y, w, h);
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            int bottom = h - 1;
            int right = w + 4;

            g.translate(x - 3, y);

            // Paint Border
            g.setColor(selectHighlight);

            // Paint left
            g.fillRect(0, 0, 1, 2);
            g.drawLine(0, 2, 4, bottom - 4);
            g.fillRect(5, bottom - 3, 1, 2);
            g.fillRect(6, bottom - 1, 1, 1);

            // Paint bootom
            g.fillRect(7, bottom, 1, 1);
            g.setColor(darkShadow);
            g.fillRect(8, bottom, right - 13, 1);

            // Paint right
            g.drawLine(right + 1, 0, right - 3, bottom - 4);
            g.fillRect(right - 4, bottom - 3, 1, 2);
            g.fillRect(right - 5, bottom - 1, 1, 1);

            g.translate(-x + 3, -y);
        }

        @Override
        protected void paintContentBorderBottomEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            int bottom = y + h - 1;
            int right = x + w - 1;
            g.translate(x, bottom);
            if (drawBroken && selRect.x >= x && selRect.x <= x + w) {
                // Break line to show visual connection to selected tab
                g.setColor(darkShadow);
                g.fillRect(0, 0, selRect.x - x - 2, 1);
                if (selRect.x + selRect.width < x + w - 2) {
                    g.setColor(darkShadow);
                   g.fillRect(selRect.x + selRect.width + 2 - x, 0, right - selRect.x - selRect.width - 2, 1);
                }
            } else {
                g.setColor(darkShadow);
                g.fillRect(0, 0, w - 1, 1);
            }
            g.translate(-x, -bottom);
        }

        @Override
        protected int getTabsOverlay() {
            return 4;
        }

    }

    /**
     * The renderer for tabs on the left with minimal decoration.
     */
    private static final class LeftEmbeddedRenderer extends AbstractRenderer {

        private LeftEmbeddedRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected Insets getTabAreaInsets(Insets insets) {
            return EMPTY_INSETS;
        }

        @Override
        protected Insets getContentBorderInsets(Insets defaultInsets) {
            return WEST_INSETS;
        }

        @Override
        protected int getTabRunOverlay(int tabRunOverlay) {
            return 0;
        }

        @Override
        protected boolean shouldPadTabRun(int run, boolean aPriori) {
            return false;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top, tabInsets.left, tabInsets.bottom, tabInsets.right);
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return EMPTY_INSETS;
        }

        /**
         * minimal decoration is really minimal: no focus.
         */
        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {
            // Embedded tabs paint no focus.
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
            g.setColor(selectColor);
            g.fillRect(x, y, w, h);
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            int bottom = h;
            int right  = w;

            g.translate(x, y);

            if (isFirstDisplayedTab(tabIndex, y, tabPane.getBounds().y)) {
                if (isSelected) {
                    //selected and first in line
                    g.setColor(selectHighlight);
                    g.fillRect(0, 0, right, 1);
                    g.fillRect(0, 0, 1, bottom - 1);
                    g.fillRect(1, bottom - 1, right - 1, 1);
                    g.setColor(shadowColor);
                    g.fillRect(0, bottom - 1, 1, 1);
                    g.fillRect(1, bottom, right - 1, 1);
                    // outside line:
                    //                    g.fillRect(-1,0,1,bottom-1)
                } else {
                    //not selected but first in line
                }
            } else {
                if (isSelected) {
                    //selected but not first in line
                    g.setColor(selectHighlight);
                    g.fillRect(1, 1, right - 1, 1);
                    g.fillRect(0, 2, 1, bottom - 2);
                    g.fillRect(1, bottom - 1, right - 1, 1);
                    g.setColor(shadowColor);
                    g.fillRect(1, 0, right - 1, 1);
                    g.fillRect(0, 1, 1, 1);
                    g.fillRect(0, bottom - 1, 1, 1);
                    g.fillRect(1, bottom, right - 1, 1);
                    // outside line:
                    //                    g.fillRect(-1,2,1,bottom-3)
                } else {
                    g.setColor(shadowColor);
                    g.fillRect(0, 0, right / 3, 1);
                }
            }

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

        @Override
        protected void paintContentBorderLeftEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            g.setColor(shadowColor);
            g.fillRect(x, y, 1, h);
        }
    }

    /**
     * Renderer for tabs on the left with normal decoration.
     */
    private static final class LeftRenderer extends AbstractRenderer {

        private LeftRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected Insets getTabAreaInsets(Insets defaultInsets) {
            return new Insets(defaultInsets.top + 4, defaultInsets.left, defaultInsets.bottom, defaultInsets.right);
        }

        @Override
        protected int getTabLabelShiftX(int tabIndex, boolean isSelected) {
            return 1;
        }

        @Override
        protected int getTabRunOverlay(int tabRunOverlay) {
            return 1;
        }

        @Override
        protected boolean shouldPadTabRun(int run, boolean aPriori) {
            return false;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top, tabInsets.left - 5, tabInsets.bottom + 1, tabInsets.right - 5);
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return WEST_INSETS;
        }

        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {

            if (!tabPane.hasFocus() || !isSelected) {
                return;
            }
            Rectangle tabRect = rects[tabIndex];
            int top = tabRect.y + 2;
            int left = tabRect.x + 3;
            int height = tabRect.height - 5;
            int width = tabRect.width - 6;
            g.setColor(focus);
            g.drawRect(left, top, width, height);
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
            if (!isSelected) {
                g.setColor(selectLight);
                g.fillRect(x + 1, y + 1, w - 1, h - 2);
            } else {
                g.setColor(selectColor);
                g.fillRect(x + 1, y + 1, w - 3, h - 2);
            }
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            int bottom = h - 1;
            int left = 0;
            g.translate(x, y);

            // Paint Border
            g.setColor(selectHighlight);
            // Paint top
            g.fillRect(left + 2, 0, w - 2 - left, 1);

            // Paint left
            g.fillRect(left + 1, 1, 1, 1);
            g.fillRect(left, 2, 1, bottom - 3);
            g.setColor(darkShadow);
            g.fillRect(left + 1, bottom - 1, 1, 1);

            // Paint bottom
            g.fillRect(left + 2, bottom, w - 2 - left, 1);

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

        @Override
        protected void paintContentBorderLeftEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            g.setColor(selectHighlight);
            if (drawBroken && selRect.y >= y && selRect.y <= y + h) {
                // Break line to show visual connection to selected tab
                g.fillRect(x, y, 1, selRect.y + 1 - y);
                if (selRect.y + selRect.height < y + h - 2) {
                    g.fillRect(x, selRect.y + selRect.height - 1, 1, y + h - selRect.y - selRect.height);
                }
            } else {
                g.fillRect(x, y, 1, h - 1);
            }
        }

    }

    /**
     * The renderer for tabs on the right with minimal decoration.
     */
    private static final class RightEmbeddedRenderer extends AbstractRenderer {

        private RightEmbeddedRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected Insets getTabAreaInsets(Insets insets) {
            return EMPTY_INSETS;
        }

        @Override
        protected Insets getContentBorderInsets(Insets defaultInsets) {
            return EAST_INSETS;
        }

        @Override
        protected int getTabRunIndent(int run) {
            return 4 * run;
        }

        @Override
        protected int getTabRunOverlay(int tabRunOverlay) {
            return 0;
        }

        @Override
        protected boolean shouldPadTabRun(int run, boolean aPriori) {
            return false;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top, tabInsets.left, tabInsets.bottom, tabInsets.right);
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return EMPTY_INSETS;
        }

        /**
         * Minimal decoration: no focus.
         */
        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {
            // Embedded tabs paint no focus.
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            g.setColor(selectColor);
            g.fillRect(x, y, w, h);
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            int bottom = h;
            int right  = w - 1;

            g.translate(x + 1, y);

            if (isFirstDisplayedTab(tabIndex, y, tabPane.getBounds().y)) {
                if (isSelected) {
                    //selected and first in line
                    g.setColor(shadowColor);
                    //outside lines:
                    //                    g.fillRect(0,-1,right,1);
                    //                    g.fillRect(right,-1,1,bottom);
                    g.fillRect(right - 1, bottom - 1, 1, 1);
                    g.fillRect(0, bottom, right - 1, 1);
                    g.setColor(selectHighlight);
                    g.fillRect(0, 0, right - 1, 1);
                    g.fillRect(right - 1, 0, 1, bottom - 1);
                    g.fillRect(0, bottom - 1, right - 1, 1);
                }
            } else {
                if (isSelected) {
                    //selected but not first in line
                    g.setColor(shadowColor);
                    g.fillRect(0, -1, right - 1, 1);
                    g.fillRect(right - 1, 0, 1, 1);
                    //outside line:
                    //                    g.fillRect(right,0,1,bottom);
                    g.fillRect(right - 1, bottom - 1, 1, 1);
                    g.fillRect(0, bottom, right - 1, 1);
                    g.setColor(selectHighlight);
                    g.fillRect(0, 0, right - 1, 1);
                    g.fillRect(right - 1, 1, 1, bottom - 2);
                    g.fillRect(0, bottom - 1, right - 1, 1);
                } else {
                    //not selected and not first in line
                    g.setColor(shadowColor);
                    g.fillRect(2 * right / 3, 0, right / 3, 1);
                }
            }
            g.translate(-x - 1, -y);
        }

        @Override
        protected void paintContentBorderRightEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            g.setColor(shadowColor);
            g.fillRect(x + w - 1, y, 1, h);
        }

    }

    /**
     * Renderer for tabs on the right with normal decoration.
     */
    private static final class RightRenderer extends AbstractRenderer {

        private RightRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected int getTabLabelShiftX(int tabIndex, boolean isSelected) {
            return 1;
        }

        @Override
        protected int getTabRunOverlay(int tabRunOverlay) {
            return 1;
        }

        @Override
        protected boolean shouldPadTabRun(int run, boolean aPriori) {
            return false;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top, tabInsets.left - 5, tabInsets.bottom + 1, tabInsets.right - 5);
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return EAST_INSETS;
        }

        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {

            if (!tabPane.hasFocus() || !isSelected) {
                return;
            }
            Rectangle tabRect = rects[tabIndex];
            int top = tabRect.y + 2;
            int left = tabRect.x + 3;
            int height = tabRect.height - 5;
            int width = tabRect.width - 6;
            g.setColor(focus);
            g.drawRect(left, top, width, height);
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
            if (!isSelected) {
                g.setColor(selectLight);
                g.fillRect(x, y, w, h);
            } else {
                g.setColor(selectColor);
                g.fillRect(x + 2, y, w - 2, h);
            }
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            int bottom = h - 1;
            int right  = w;

            g.translate(x, y);

            // Paint Border

            g.setColor(selectHighlight);
            g.fillRect(0, 0, right - 1, 1);
            // Paint right
            g.setColor(darkShadow);
            g.fillRect(right - 1, 1, 1, 1);
            g.fillRect(right, 2, 1, bottom - 3);
            // Paint bottom
            g.fillRect(right - 1, bottom - 1, 1, 1);
            g.fillRect(0, bottom, right - 1, 1);

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

        @Override
        protected void paintContentBorderRightEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            g.setColor(darkShadow);
            if (drawBroken && selRect.y >= y && selRect.y <= y + h) {
                // Break line to show visual connection to selected tab
                g.fillRect(x + w - 1, y, 1, selRect.y - y);
                if (selRect.y + selRect.height < y + h - 2) {
                    g.fillRect(x + w - 1, selRect.y + selRect.height, 1, y + h - selRect.y - selRect.height);
                }
            } else {
                g.fillRect(x + w - 1, y, 1, h - 1);
            }
        }
    }

    /**
     * Renderer for tabs on top with minimal decoration.
     */
    private static final class TopEmbeddedRenderer extends AbstractRenderer {

        private TopEmbeddedRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected Insets getTabAreaInsets(Insets insets) {
            return EMPTY_INSETS;
        }

        @Override
        protected Insets getContentBorderInsets(Insets defaultInsets) {
            return NORTH_INSETS;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top, tabInsets.left + 1, tabInsets.bottom, tabInsets.right);
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return EMPTY_INSETS;
        }

        /**
         * Minimal decoration: no focus.
         */
        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {
            // Embedded tabs paint no focus.
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            g.setColor(selectColor);
            g.fillRect(x, y, w, h);
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            g.translate(x, y);

            int right  = w;
            int bottom = h;

            if (isFirstDisplayedTab(tabIndex, x, tabPane.getBounds().x)) {
                if (isSelected) {
                    g.setColor(selectHighlight);
                    //left
                    g.fillRect(0, 0, 1, bottom);
                    //top
                    g.fillRect(0, 0, right - 1, 1);
                    //right
                    g.fillRect(right - 1, 0, 1, bottom);
                    g.setColor(shadowColor);
                    //topright corner
                    g.fillRect(right - 1, 0, 1, 1);
                    //right
                    g.fillRect(right, 1, 1, bottom);
                }
            } else {
                if (isSelected) {
                    g.setColor(selectHighlight);
                    //left
                    g.fillRect(1, 1, 1, bottom - 1);
                    //top
                    g.fillRect(2, 0, right - 3, 1);
                    //right
                    g.fillRect(right - 1, 1, 1, bottom - 1);
                    g.setColor(shadowColor);
                    //left
                    g.fillRect(0, 1, 1, bottom - 1);
                    //topleft corner
                    g.fillRect(1, 0, 1, 1);
                    //topright corner
                    g.fillRect(right - 1, 0, 1, 1);
                    //right
                    g.fillRect(right, 1, 1, bottom);
                } else {
                    g.setColor(shadowColor);
                    g.fillRect(0, 0, 1, bottom +2 - bottom / 2);
                }
            }
            g.translate(-x, -y);
        }

        @Override
        protected void paintContentBorderTopEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            g.setColor(shadowColor);
            g.fillRect(x, y, w, 1);
        }

    }

    /**
     * Renderer for tabs on top with normal decoration.
     */
    private static final class TopRenderer extends AbstractRenderer {

        private TopRenderer(JTabbedPane tabPane) {
            super(tabPane);
        }

        @Override
        protected Insets getTabAreaInsets(Insets defaultInsets) {
            return new Insets(defaultInsets.top, defaultInsets.left + 4, defaultInsets.bottom, defaultInsets.right);
        }

		@Override
        protected int getTabLabelShiftY(int tabIndex, boolean isSelected) {
			return isSelected ? -1 : 0;
		}

        @Override
        protected int getTabRunOverlay(int tabRunOverlay) {
            return tabRunOverlay - 2;
        }

        @Override
        protected int getTabRunIndent(int run) {
            return 6 * run;
        }

        @Override
        protected Insets getSelectedTabPadInsets() {
            return NORTH_INSETS;
        }

        @Override
        protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
            return new Insets(tabInsets.top-1, tabInsets.left - 4, tabInsets.bottom, tabInsets.right - 4);
        }

        @Override
        protected void paintFocusIndicator(
            Graphics g,
            Rectangle[] rects,
            int tabIndex,
            Rectangle iconRect,
            Rectangle textRect,
            boolean isSelected) {

            if (!tabPane.hasFocus() || !isSelected) {
                return;
            }
            Rectangle tabRect = rects[tabIndex];
            int top = tabRect.y +1 ;
            int left = tabRect.x + 4;
            int height = tabRect.height - 3;
            int width = tabRect.width - 9;
            g.setColor(focus);
            g.drawRect(left, top, width, height);
        }

        @Override
        protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            int sel = isSelected ? 0 : 1;
            g.setColor(selectColor);
            g.fillRect(x, y + sel, w, h / 2);
            g.fillRect(x - 1, y + sel + h / 2, w + 2, h - h / 2);
        }

        @Override
        protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {

            g.translate(x - 4, y);

            int top = 0;
            int right = w + 6;

            // Paint Border
            g.setColor(selectHighlight);

            // Paint left
            g.drawLine(1, h - 1, 4, top + 4);
            g.fillRect(5, top + 2, 1, 2);
            g.fillRect(6, top + 1, 1, 1);

            // Paint top
            g.fillRect(7, top, right - 12, 1);

            // Paint right
            g.setColor(darkShadow);
            g.drawLine(right, h - 1, right - 3, top + 4);
            g.fillRect(right - 4, top + 2, 1, 2);
            g.fillRect(right - 5, top + 1, 1, 1);

            g.translate(-x + 4, -y);
        }

        @Override
        protected void paintContentBorderTopEdge(
            Graphics g,
            int x,
            int y,
            int w,
            int h,
            boolean drawBroken,
            Rectangle selRect,
            boolean isContentBorderPainted) {
            int right = x + w - 1;
            int top = y;
            g.setColor(selectHighlight);

            if (drawBroken && selRect.x >= x && selRect.x <= x + w) {
                // Break line to show visual connection to selected tab
                g.fillRect(x, top, selRect.x - 2 - x, 1);
                if (selRect.x + selRect.width < x + w - 2) {
                    g.fillRect(selRect.x + selRect.width + 2, top, right - 2 - selRect.x - selRect.width, 1);
                } else {
                    g.fillRect(x + w - 2, top, 1, 1);
                }
            } else {
                g.fillRect(x, top, w - 1, 1);
            }
        }

        @Override
        protected int getTabsOverlay() {
            return 6;
        }
    }

}