Package com.seaglasslookandfeel.ui

Source Code of com.seaglasslookandfeel.ui.SeaGlassTabbedPaneUI

/*
* Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
*
* This file is part of the SeaGlass Pluggable Look and Feel.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: SeaGlassTabbedPaneUI.java 1595 2011-08-09 20:33:48Z rosstauscher@gmx.de $
*/
package com.seaglasslookandfeel.ui;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.synth.ColorType;
import javax.swing.plaf.synth.Region;
import javax.swing.plaf.synth.SynthConstants;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthStyle;
import javax.swing.text.View;

import sun.swing.SwingUtilities2;

import com.seaglasslookandfeel.SeaGlassContext;
import com.seaglasslookandfeel.SeaGlassLookAndFeel;
import com.seaglasslookandfeel.SeaGlassRegion;
import com.seaglasslookandfeel.SeaGlassSynthPainterImpl;
import com.seaglasslookandfeel.component.SeaGlassArrowButton;
import com.seaglasslookandfeel.util.ControlOrientation;
import com.seaglasslookandfeel.util.SeaGlassTabCloseListener;

/**
* Sea Glass's TabbedPane UI delegate.
*
* <p>Based on SynthTabbedPaneUI.</p>
*/
public class SeaGlassTabbedPaneUI extends BasicTabbedPaneUI implements SeaglassUI, PropertyChangeListener {

    private SeaGlassContext tabAreaContext;
    private SeaGlassContext tabContext;
    private SeaGlassContext tabCloseContext;
    private SeaGlassContext tabContentContext;

    private SynthStyle style;
    private SynthStyle tabStyle;
    private SynthStyle tabCloseStyle;
    private SynthStyle tabAreaStyle;
    private SynthStyle tabContentStyle;

    private Rectangle textRect;
    private Rectangle iconRect;
    private Rectangle tabAreaRect;
    private Rectangle contentRect;

    private boolean selectedTabIsPressed = false;

    /**
     * Actions to be performed when tab close button is pressed and when tab is
     * actually closed.
     */
    protected SeaGlassTabCloseListener tabCloseListener;

    /** The scroll forward button. This may or may not be visible. */
    protected SynthScrollableTabButton scrollForwardButton;

    /** The scroll backward button. This may or may not be visible. */
    protected SynthScrollableTabButton scrollBackwardButton;

    /** Margin for the close button. */
    protected Insets closeButtonInsets;

    /** The size of the close button. */
    protected int closeButtonSize;

    /** Index of initial displayed tab. */
    protected int leadingTabIndex = 0;

    /** Index of final displayed tab. */
    protected int trailingTabIndex = 0;

    /** Side the tabs are on. */
    protected int tabPlacement;

    /** Horizontal/vertical abstraction. */
    protected ControlOrientation orientation;

    /**
     * Placement of the tab close button: LEFT, RIGHT, or CENTER (the default)
     * for none.
     */
    protected int tabCloseButtonPlacement;

    /** Index of the armed close button, or -1 if none is armed. */
    protected int closeButtonArmedIndex;

    /**
     * Index of the close button the mouse is hovering over, or -1 if none is
     * being hovered over.
     */
    protected int closeButtonHoverIndex;

    /**
     * Creates a new SeaGlassTabbedPaneUI object.
     */
    SeaGlassTabbedPaneUI() {
        textRect    = new Rectangle();
        iconRect    = new Rectangle();
        tabAreaRect = new Rectangle();
        contentRect = new Rectangle();
    }

    /**
     * Create the UI delegate.
     *
     * @param  c the component (not used).
     *
     * @return the UI delegate.
     */
    public static ComponentUI createUI(JComponent c) {
        return new SeaGlassTabbedPaneUI();
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installUI(javax.swing.JComponent)
     */
    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installComponents()
     */
    @Override
    protected void installComponents() {
        super.installComponents();

        scrollBackwardButton = (SynthScrollableTabButton) createScrollButton(WEST);
        scrollForwardButton  = (SynthScrollableTabButton) createScrollButton(EAST);
        tabPane.add(scrollBackwardButton);
        tabPane.add(scrollForwardButton);
        scrollBackwardButton.setVisible(false);
        scrollForwardButton.setVisible(false);
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallUI(javax.swing.JComponent)
     */
    @Override
    public void uninstallUI(JComponent c) {
        super.uninstallUI(c);
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallComponents()
     */
    @Override
    protected void uninstallComponents() {
        super.uninstallComponents();

        if (scrollBackwardButton != null) {
            tabPane.remove(scrollBackwardButton);
            scrollBackwardButton = null;
        }

        if (scrollForwardButton != null) {
            tabPane.remove(scrollForwardButton);
            scrollForwardButton = null;
        }
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installDefaults()
     */
    protected void installDefaults() {
        leadingTabIndex  = 0;
        trailingTabIndex = 0;

        updateStyle(tabPane);
    }

    /**
     * Update the Synth styles when something changes.
     *
     * @param c the component.
     */
    private void updateStyle(JTabbedPane c) {
        SeaGlassContext context  = getContext(c, ENABLED);
        SynthStyle      oldStyle = style;

        style = SeaGlassLookAndFeel.updateStyle(context, this);

        tabPlacement = tabPane.getTabPlacement();
        orientation  = ControlOrientation.getOrientation(tabPlacement == LEFT || tabPlacement == RIGHT ? VERTICAL : HORIZONTAL);

        closeButtonArmedIndex = -1;
        Object o = c.getClientProperty("JTabbedPane.closeButton");

        if (o != null && "left".equals(o)) {
            tabCloseButtonPlacement = LEFT;
        } else if (o != null && "right".equals(o)) {
            tabCloseButtonPlacement = RIGHT;
        } else {
            tabCloseButtonPlacement = CENTER;
        }

        closeButtonSize   = style.getInt(context, "closeButtonSize", 6);
        closeButtonInsets = (Insets) style.get(context, "closeButtonInsets");
        if (closeButtonInsets == null) {
            closeButtonInsets = new Insets(2, 2, 2, 2);
        }

        o = c.getClientProperty("JTabbedPane.closeListener");
        if (o != null && o instanceof SeaGlassTabCloseListener) {
            if (tabCloseListener == null) {
                tabCloseListener = (SeaGlassTabCloseListener) o;
            }
        }

        // Add properties other than JComponent colors, Borders and
        // opacity settings here:
        if (style != oldStyle) {

            tabRunOverlay        = 0;
            textIconGap          = style.getInt(context, "TabbedPane.textIconGap", 0);
            selectedTabPadInsets = (Insets) style.get(context, "TabbedPane.selectedTabPadInsets");

            if (selectedTabPadInsets == null) {
                selectedTabPadInsets = new Insets(0, 0, 0, 0);
            }

            if (oldStyle != null) {
                uninstallKeyboardActions();
                installKeyboardActions();
            }
        }

        context.dispose();

        if (tabContext != null) {
            tabContext.dispose();
        }

        tabContext    = getContext(c, Region.TABBED_PANE_TAB, ENABLED);
        this.tabStyle = SeaGlassLookAndFeel.updateStyle(tabContext, this);
        tabInsets     = tabStyle.getInsets(tabContext, null);

        if (tabCloseContext != null) {
            tabCloseContext.dispose();
        }

        tabCloseContext    = getContext(c, SeaGlassRegion.TABBED_PANE_TAB_CLOSE_BUTTON, ENABLED);
        this.tabCloseStyle = SeaGlassLookAndFeel.updateStyle(tabCloseContext, this);

        if (tabAreaContext != null) {
            tabAreaContext.dispose();
        }

        tabAreaContext    = getContext(c, Region.TABBED_PANE_TAB_AREA, ENABLED);
        this.tabAreaStyle = SeaGlassLookAndFeel.updateStyle(tabAreaContext, this);
        tabAreaInsets     = tabAreaStyle.getInsets(tabAreaContext, null);

        if (tabContentContext != null) {
            tabContentContext.dispose();
        }

        tabContentContext    = getContext(c, Region.TABBED_PANE_CONTENT, ENABLED);
        this.tabContentStyle = SeaGlassLookAndFeel.updateStyle(tabContentContext, this);
        contentBorderInsets  = tabContentStyle.getInsets(tabContentContext, null);
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installListeners()
     */
    protected void installListeners() {
        super.installListeners();
        tabPane.addMouseMotionListener((MouseAdapter) mouseListener);
        tabPane.addPropertyChangeListener(this);

        scrollBackwardButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    scrollBackward();
                }
            });

        scrollForwardButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    scrollForward();
                }
            });
    }

    /**
     * Scroll the tab buttons backwards.
     */
    protected void scrollBackward() {
        int selectedIndex = tabPane.getSelectedIndex();

        if (--selectedIndex < 0) {
            tabPane.setSelectedIndex(tabPane.getTabCount() == 0 ? -1 : 0);
        } else {
            tabPane.setSelectedIndex(selectedIndex);
        }

        tabPane.repaint();
    }

    /**
     * Scroll the tab buttons forwards.
     */
    protected void scrollForward() {
        int selectedIndex = tabPane.getSelectedIndex();

        if (++selectedIndex >= tabPane.getTabCount()) {
            tabPane.setSelectedIndex(tabPane.getTabCount() - 1);
        } else {
            tabPane.setSelectedIndex(selectedIndex);
        }

        tabPane.repaint();
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallListeners()
     */
    protected void uninstallListeners() {
        if (mouseListener != null) {
            tabPane.removeMouseMotionListener((MouseAdapter) mouseListener);
        }

        tabPane.removePropertyChangeListener(this);
        super.uninstallListeners();
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallDefaults()
     */
    protected void uninstallDefaults() {
        SeaGlassContext context = getContext(tabPane, ENABLED);

        style.uninstallDefaults(context);
        context.dispose();
        style = null;

        tabStyle.uninstallDefaults(tabContext);
        tabContext.dispose();
        tabContext = null;
        tabStyle   = null;

        tabAreaStyle.uninstallDefaults(tabAreaContext);
        tabAreaContext.dispose();
        tabAreaContext = null;
        tabAreaStyle   = null;

        tabContentStyle.uninstallDefaults(tabContentContext);
        tabContentContext.dispose();
        tabContentContext = null;
        tabContentStyle   = null;
    }

    /**
     * @see SeaglassUI#getContext(javax.swing.JComponent)
     */
    public SeaGlassContext getContext(JComponent c) {
        return getContext(c, getComponentState(c));
    }

    /**
     * Create a SynthContext for the component and state. Use the default
     * region.
     *
     * @param  c     the component.
     * @param  state the state.
     *
     * @return the newly created SynthContext.
     */
    public SeaGlassContext getContext(JComponent c, int state) {
        return SeaGlassContext.getContext(SeaGlassContext.class, c, SeaGlassLookAndFeel.getRegion(c), style, state);
    }

    /**
     * Create a SynthContext for the component and subregion. Use the current
     * state.
     *
     * @param  c         the component.
     * @param  subregion the subregion.
     *
     * @return the newly created SynthContext.
     */
    public SeaGlassContext getContext(JComponent c, Region subregion) {
        return getContext(c, subregion, getComponentState(c));
    }

    /**
     * Create a SynthContext for the component, subregion, and state.
     *
     * @param  c         the component.
     * @param  subregion the subregion.
     * @param  state     the state.
     *
     * @return the newly created SynthContext.
     */
    private SeaGlassContext getContext(JComponent c, Region subregion, int state) {
        SynthStyle style = null;
        Class      klass = SeaGlassContext.class;

        if (subregion == Region.TABBED_PANE_TAB) {
            style = tabStyle;
        } else if (subregion == Region.TABBED_PANE_TAB_AREA) {
            style = tabAreaStyle;
        } else if (subregion == Region.TABBED_PANE_CONTENT) {
            style = tabContentStyle;
        } else if (subregion == SeaGlassRegion.TABBED_PANE_TAB_CLOSE_BUTTON) {
            style = tabCloseStyle;
        }

        return SeaGlassContext.getContext(klass, c, subregion, style, state);
    }

    /**
     * Get the current state for the component.
     *
     * @param  c the component.
     *
     * @return the component's state.
     */
    private int getComponentState(JComponent c) {
        return SeaGlassLookAndFeel.getComponentState(c);
    }

    /**
     * Get the state for the specified tab's close button.
     *
     * @param  c               the tabbed pane.
     * @param  tabIndex        the index of the tab.
     * @param  tabIsMousedOver TODO
     *
     * @return the close button state.
     */
    public int getCloseButtonState(JComponent c, int tabIndex, boolean tabIsMousedOver) {
        if (!c.isEnabled()) {
            return DISABLED;
        } else if (tabIndex == closeButtonArmedIndex) {
            return PRESSED;
        } else if (tabIndex == closeButtonHoverIndex) {
            return FOCUSED;
        } else if (tabIsMousedOver) {
            return MOUSE_OVER;
        }

        return ENABLED;
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#createScrollButton(int)
     */
    protected JButton createScrollButton(int direction) {
        SynthScrollableTabButton b = new SynthScrollableTabButton(direction);
        b.setName("TabbedPaneTabArea.button" + direction);
        return b;
    }

    /**
     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
     */
    public void propertyChange(PropertyChangeEvent e) {
        if (SeaGlassLookAndFeel.shouldUpdateStyle(e)) {
            updateStyle(tabPane);
        } else if (e.getPropertyName() == "tabPlacement") {
            updateStyle(tabPane);
        }
    }

    /**
     * Create the mouse listener.
     *
     * <p>Overridden to keep track of whether the selected tab is also
     * pressed.</p>
     *
     * @return the mouse listener.
     *
     * @see    javax.swing.plaf.basic.BasicTabbedPaneUI#createMouseListener()
     */
    @Override
    protected MouseListener createMouseListener() {
        return new SeaGlassTabbedPaneMouseHandler(super.createMouseListener());
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#createLayoutManager()
     */
    @Override
    protected LayoutManager createLayoutManager() {
        return new SeaGlassTabbedPaneLayout();
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftX(int, int,
     *      boolean)
     */
    @Override
    protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
        return 0;
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftY(int, int,
     *      boolean)
     */
    @Override
    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
        return 0;
    }

    /**
     * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics, javax.swing.JComponent)
     */
    public void update(Graphics g, JComponent c) {
        SeaGlassContext context = getContext(c);

        SeaGlassLookAndFeel.update(context, g);
        context.getPainter().paintTabbedPaneBackground(context, g, tabAreaRect.x, tabAreaRect.y, tabAreaRect.width, tabAreaRect.height);
        paint(context, g);
        context.dispose();
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getBaseline(int)
     */
    protected int getBaseline(int tab) {
        if (tabPane.getTabComponentAt(tab) != null || getTextViewForTab(tab) != null) {
            return super.getBaseline(tab);
        }

        String      title   = tabPane.getTitleAt(tab);
        Font        font    = tabContext.getStyle().getFont(tabContext);
        FontMetrics metrics = getFontMetrics(font);
        Icon        icon    = getIconForTab(tab);

        textRect.setBounds(0, 0, 0, 0);
        iconRect.setBounds(0, 0, 0, 0);
        calcRect.setBounds(0, 0, Short.MAX_VALUE, maxTabHeight);
        tabContext.getStyle().getGraphicsUtils(tabContext).layoutText(tabContext, metrics, title, icon, SwingUtilities.CENTER,
                                                                      SwingUtilities.CENTER, SwingUtilities.LEADING,
                                                                      SwingUtilities.TRAILING, calcRect, iconRect, textRect, textIconGap);

        return textRect.y + metrics.getAscent() + getBaselineOffset();
    }

    /**
     * @see SeaglassUI#paintBorder(javax.swing.plaf.synth.SynthContext,
     *      java.awt.Graphics, int, int, int, int)
     */
    public void paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h) {
        ((SeaGlassContext) context).getPainter().paintTabbedPaneBorder(context, g, x, y, w, h);
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#paint(java.awt.Graphics, javax.swing.JComponent)
     */
    public void paint(Graphics g, JComponent c) {
        SeaGlassContext context = getContext(c);

        paint(context, g);
        context.dispose();
    }

    /**
     * Paint the tabbed pane.
     *
     * @param context the SynthContext describing the control.
     * @param g       the Graphics context to paint with.
     */
    protected void paint(SeaGlassContext context, Graphics g) {
        int selectedIndex = tabPane.getSelectedIndex();

        ensureCurrentLayout();

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

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabArea(java.awt.Graphics,
     *      int, int)
     */
    protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
        paintTabArea(tabAreaContext, g, tabPlacement, selectedIndex, tabAreaRect);
    }

    /**
     * Paint the tab area, including the tabs.
     *
     * @param ss            the SynthContext.
     * @param g             the Graphics context.
     * @param tabPlacement  the side the tabs are on.
     * @param selectedIndex the current selected tab index.
     * @param tabAreaBounds the bounds of the tab area.
     */
    protected void paintTabArea(SeaGlassContext ss, Graphics g, int tabPlacement, int selectedIndex, Rectangle tabAreaBounds) {
        Rectangle clipRect = g.getClipBounds();

        ss.setComponentState(SynthConstants.ENABLED);

        // Paint the tab area.
        SeaGlassLookAndFeel.updateSubregion(ss, g, tabAreaBounds);
        ss.getPainter().paintTabbedPaneTabAreaBackground(ss, g, tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width,
                                                         tabAreaBounds.height, tabPlacement);
        ss.getPainter().paintTabbedPaneTabAreaBorder(ss, g, tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width, tabAreaBounds.height,
                                                     tabPlacement);

        iconRect.setBounds(0, 0, 0, 0);
        textRect.setBounds(0, 0, 0, 0);

        if (runCount == 0) {
            return;
        }

        if (scrollBackwardButton.isVisible()) {
            paintScrollButtonBackground(ss, g, scrollBackwardButton);
        }

        if (scrollForwardButton.isVisible()) {
            paintScrollButtonBackground(ss, g, scrollForwardButton);
        }

        for (int i = leadingTabIndex; i <= trailingTabIndex; i++) {
            if (rects[i].intersects(clipRect) && selectedIndex != i) {
                paintTab(tabContext, g, rects, i, iconRect, textRect);
            }
        }

        if (selectedIndex >= 0) {
            if (rects[selectedIndex].intersects(clipRect)) {
                paintTab(tabContext, g, rects, selectedIndex, iconRect, textRect);
            }
        }
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#setRolloverTab(int)
     */
    protected void setRolloverTab(int index) {
        int oldRolloverTab = getRolloverTab();

        super.setRolloverTab(index);

        Rectangle r = null;

        if ((oldRolloverTab >= 0) && (oldRolloverTab < tabPane.getTabCount())) {
            r = getTabBounds(tabPane, oldRolloverTab);

            if (r != null) {
                tabPane.repaint(r);
            }
        }

        if (index >= 0) {
            r = getTabBounds(tabPane, index);

            if (r != null) {
                tabPane.repaint(r);
            }
        }
    }

    /**
     * Paint a tab.
     *
     * @param ss       the SynthContext.
     * @param g        the Graphics context.
     * @param rects    the array containing the bounds for the tabs.
     * @param tabIndex the tab index to paint.
     * @param iconRect the bounds in which to paint the tab icon, if any.
     * @param textRect the bounds in which to paint the tab text, if any.
     */
    protected void paintTab(SeaGlassContext ss, Graphics g, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect) {
        Rectangle  tabRect       = rects[tabIndex];
        int        selectedIndex = tabPane.getSelectedIndex();
        boolean    isSelected    = selectedIndex == tabIndex;
        JComponent b             = ss.getComponent();

        boolean flipSegments    = (orientation == ControlOrientation.HORIZONTAL && !tabPane.getComponentOrientation().isLeftToRight());
        String  segmentPosition = "only";

        if (tabPane.getTabCount() > 1) {
            if (tabIndex == 0 && tabIndex == leadingTabIndex) {
                segmentPosition = flipSegments ? "last" : "first";
            } else if (tabIndex == tabPane.getTabCount() - 1 && tabIndex == trailingTabIndex) {
                segmentPosition = flipSegments ? "first" : "last";
            } else {
                segmentPosition = "middle";
            }
        }

        b.putClientProperty("JTabbedPane.Tab.segmentPosition", segmentPosition);
        updateTabContext(tabIndex, isSelected, isSelected && selectedTabIsPressed, getRolloverTab() == tabIndex,
                         getFocusIndex() == tabIndex);

        SeaGlassLookAndFeel.updateSubregion(ss, g, tabRect);

        int x      = tabRect.x;
        int y      = tabRect.y;
        int height = tabRect.height;
        int width  = tabRect.width;

        tabContext.getPainter().paintTabbedPaneTabBackground(tabContext, g, x, y, width, height, tabIndex, tabPlacement);
        tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g, x, y, width, height, tabIndex, tabPlacement);

        if (tabCloseButtonPlacement != CENTER) {
            tabRect = paintCloseButton(g, tabContext, tabIndex);
        }

        if (tabPane.getTabComponentAt(tabIndex) == null) {
            String      title   = tabPane.getTitleAt(tabIndex);
            Font        font    = ss.getStyle().getFont(ss);
            FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
            Icon        icon    = getIconForTab(tabIndex);

            layoutLabel(ss, tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected);
            paintText(ss, g, tabPlacement, font, metrics, tabIndex, title, textRect, isSelected);
            paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
        }
    }

    /**
     * Paint the close button for a tab.
     *
     * @param  g          the Graphics context.
     * @param  tabContext the SynthContext for the tab itself.
     * @param  tabIndex   the tab index to paint.
     *
     * @return the new tab bounds.
     */
    protected Rectangle paintCloseButton(Graphics g, SynthContext tabContext, int tabIndex) {
        Rectangle tabRect = new Rectangle(rects[tabIndex]);

        Rectangle bounds = getCloseButtonBounds(tabIndex);
        int       offset = bounds.width + textIconGap;
        boolean   onLeft = isCloseButtonOnLeft();

        if (onLeft) {
            tabRect.x     += offset;
            tabRect.width -= offset;
        } else {
            tabRect.width -= offset;
        }

        SeaGlassContext subcontext = getContext(tabPane, SeaGlassRegion.TABBED_PANE_TAB_CLOSE_BUTTON,
                                                getCloseButtonState(tabPane, tabIndex, (tabContext.getComponentState() & MOUSE_OVER) != 0));

        SeaGlassLookAndFeel.updateSubregion(subcontext, g, bounds);

        SeaGlassSynthPainterImpl painter = (SeaGlassSynthPainterImpl) subcontext.getPainter();

        painter.paintSearchButtonForeground(subcontext, g, bounds.x, bounds.y, bounds.width, bounds.height);

        subcontext.dispose();

        return tabRect;
    }

    /**
     * Paint the background for a tab scroll button.
     *
     * @param ss           the tab subregion SynthContext.
     * @param g            the Graphics context.
     * @param scrollButton the button to paint.
     */
    protected void paintScrollButtonBackground(SeaGlassContext ss, Graphics g, JButton scrollButton) {
        Rectangle tabRect = scrollButton.getBounds();
        int       x       = tabRect.x;
        int       y       = tabRect.y;
        int       height  = tabRect.height;
        int       width   = tabRect.width;

        boolean flipSegments = (orientation == ControlOrientation.HORIZONTAL && !tabPane.getComponentOrientation().isLeftToRight());

        SeaGlassLookAndFeel.updateSubregion(ss, g, tabRect);

        tabPane.putClientProperty("JTabbedPane.Tab.segmentPosition",
                                  ((scrollButton == scrollBackwardButton) ^ flipSegments) ? "first" : "last");

        int         oldState    = tabContext.getComponentState();
        ButtonModel model       = scrollButton.getModel();
        int         isPressed   = model.isPressed() && model.isArmed() ? PRESSED : 0;
        int         buttonState = SeaGlassLookAndFeel.getComponentState(scrollButton) | isPressed;

        tabContext.setComponentState(buttonState);
        tabContext.getPainter().paintTabbedPaneTabBackground(tabContext, g, x, y, width, height, -1, tabPlacement);
        tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g, x, y, width, height, -1, tabPlacement);
        tabContext.setComponentState(oldState);
    }

    /**
     * Layout label text for a tab.
     *
     * @param ss           the SynthContext.
     * @param tabPlacement the side the tabs are on.
     * @param metrics      the font metrics.
     * @param tabIndex     the index of the tab to lay out.
     * @param title        the text for the label, if any.
     * @param icon         the icon for the label, if any.
     * @param tabRect      Rectangle to layout text and icon in.
     * @param iconRect     Rectangle to place icon bounds in
     * @param textRect     Rectangle to place text in
     * @param isSelected   is the tab selected?
     */
    protected void layoutLabel(SeaGlassContext ss, int tabPlacement, FontMetrics metrics, int tabIndex, String title, Icon icon,
            Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected) {
        View v = getTextViewForTab(tabIndex);

        if (v != null) {
            tabPane.putClientProperty("html", v);
        }

        textRect.x = textRect.y = iconRect.x = iconRect.y = 0;

        ss.getStyle().getGraphicsUtils(ss).layoutText(ss, metrics, title, icon, SwingUtilities.CENTER, SwingUtilities.CENTER,
                                                      SwingUtilities.LEADING, SwingUtilities.TRAILING, tabRect, iconRect, textRect,
                                                      textIconGap);

        tabPane.putClientProperty("html", null);

        int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
        int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);

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

    /**
     * Paint the label text for a tab.
     *
     * @param ss           the SynthContext.
     * @param g            the Graphics context.
     * @param tabPlacement the side the tabs are on.
     * @param font         the font to use.
     * @param metrics      the font metrics.
     * @param tabIndex     the index of the tab to lay out.
     * @param title        the text for the label, if any.
     * @param textRect     Rectangle to place text in
     * @param isSelected   is the tab selected?
     */
    protected void paintText(SeaGlassContext ss, Graphics g, int tabPlacement, Font font, FontMetrics metrics, int tabIndex, String title,
            Rectangle textRect, boolean isSelected) {
        g.setFont(font);

        View v = getTextViewForTab(tabIndex);

        if (v != null) {
            // html
            v.paint(g, textRect);
        } else {
            // plain text
            int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
            FontMetrics    fm = SwingUtilities2.getFontMetrics(tabPane, g);
            title = SwingUtilities2.clipStringIfNecessary(tabPane, fm, title, textRect.width);
            g.setColor(ss.getStyle().getColor(ss, ColorType.TEXT_FOREGROUND));
            ss.getStyle().getGraphicsUtils(ss).paintText(ss, g, title, textRect, mnemIndex);
        }
    }

    /**
     * Paint the content pane's border.
     *
     * @param ss            the SynthContext.
     * @param g             the Graphics context.
     * @param tabPlacement  the side the tabs are on.
     * @param selectedIndex the current selected tab index.
     */
    protected void paintContentBorder(SeaGlassContext ss, 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);
        }

        SeaGlassLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w, h));
        ss.getPainter().paintTabbedPaneContentBackground(ss, g, x, y, w, h);
        ss.getPainter().paintTabbedPaneContentBorder(ss, g, x, y, w, h);
    }

    /**
     * Make sure we have laid out the pane with the current layout.
     */
    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();
        }
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateMaxTabHeight(int)
     */
    public int calculateMaxTabHeight(int tabPlacement) {
        FontMetrics metrics    = getFontMetrics(tabContext.getStyle().getFont(tabContext));
        int         tabCount   = tabPane.getTabCount();
        int         result     = 0;
        int         fontHeight = metrics.getHeight();

        for (int i = 0; i < tabCount; i++) {
            result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
        }

        return result;
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateTabWidth(int, int,
     *      java.awt.FontMetrics)
     */
    protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
        Icon      icon         = getIconForTab(tabIndex);
        Insets    tabInsets    = getTabInsets(tabPlacement, tabIndex);
        int       width        = tabInsets.left + tabInsets.right + 3;
        Component tabComponent = tabPane.getTabComponentAt(tabIndex);

        if (tabComponent != null) {
            width += tabComponent.getPreferredSize().width;
            if (tabIndex < rects.length && tabCloseButtonPlacement != CENTER) {
                width += getCloseButtonBounds(tabIndex).width + textIconGap;
            }
        } else {
            if (icon != null) {
                width += icon.getIconWidth() + textIconGap;
            }

            // Hack to prevent array index out of bounds before the size has been set and the rectangles created.
            if (tabIndex < rects.length && tabCloseButtonPlacement != CENTER) {
                width += getCloseButtonBounds(tabIndex).width + textIconGap;
            }

            View v = getTextViewForTab(tabIndex);

            if (v != null) {
                // html
                width += (int) v.getPreferredSpan(View.X_AXIS);
            } else {
                // plain text
                String title = tabPane.getTitleAt(tabIndex);

                width += tabContext.getStyle().getGraphicsUtils(tabContext).computeStringWidth(tabContext, metrics.getFont(), metrics,
                                                                                               title);
            }
        }

        return width;
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateMaxTabWidth(int)
     */
    public int calculateMaxTabWidth(int tabPlacement) {
        FontMetrics metrics  = getFontMetrics(tabContext.getStyle().getFont(tabContext));
        int         tabCount = tabPane.getTabCount();
        int         result   = 0;

        for (int i = 0; i < tabCount; i++) {
            result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
        }

        return result;
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabInsets(int, int)
     */
    protected Insets getTabInsets(int tabPlacement, int tabIndex) {
        updateTabContext(tabIndex, false, false, false, (getFocusIndex() == tabIndex));

        return tabInsets;
    }

    /**
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getFontMetrics()
     */
    protected FontMetrics getFontMetrics() {
        return getFontMetrics(tabContext.getStyle().getFont(tabContext));
    }

    /**
     * Get the font metrics for the font.
     *
     * @param  font the font.
     *
     * @return the metrics for the font.
     */
    protected FontMetrics getFontMetrics(Font font) {
        return tabPane.getFontMetrics(font);
    }

    /**
     * Update the SynthContext for the tab area for a specified tab.
     *
     * @param index       the tab to update for.
     * @param selected    is the tab selected?
     * @param isMouseDown is the mouse down?
     * @param isMouseOver is the mouse over the tab?
     * @param hasFocus    do we have focus?
     */
    private void updateTabContext(int index, boolean selected, boolean isMouseDown, boolean isMouseOver, boolean hasFocus) {
        int state = 0;

        if (!tabPane.isEnabled() || !tabPane.isEnabledAt(index)) {
            state |= SynthConstants.DISABLED;

            if (selected) {
                state |= SynthConstants.SELECTED;
            }
        } else if (selected) {
            state |= (SynthConstants.ENABLED | SynthConstants.SELECTED);

            if (isMouseOver && UIManager.getBoolean("TabbedPane.isTabRollover")) {
                state |= SynthConstants.MOUSE_OVER;
            }
        } else if (isMouseOver) {
            state |= (SynthConstants.ENABLED | SynthConstants.MOUSE_OVER);
        } else {
            state =  SeaGlassLookAndFeel.getComponentState(tabPane);
            state &= ~SynthConstants.FOCUSED; // Don't use tabbedpane focus state.
        }

        if (hasFocus && tabPane.hasFocus()) {
            state |= SynthConstants.FOCUSED; // individual tab has focus
        }

        if (isMouseDown) {
            state |= SynthConstants.PRESSED;
        }

        tabContext.setComponentState(state);
    }

    /**
     * Determine whether the mouse is over a tab close button, and if so, set
     * the hover index.
     *
     * @param  x the current mouse x coordinate.
     * @param  y the current mouse y coordinate.
     *
     * @return {@code true} if the mouse is over a close button, {@code false}
     *         otherwise.
     */
    protected boolean isOverCloseButton(int x, int y) {
        if (tabCloseButtonPlacement != CENTER) {
            int tabCount = tabPane.getTabCount();

            for (int i = 0; i < tabCount; i++) {
                if (getCloseButtonBounds(i).contains(x, y)) {
                    closeButtonHoverIndex = i;
                    return true;
                }
            }
        }

        closeButtonHoverIndex = -1;
        return false;
    }

    /**
     * Get the bounds for a tab close button.
     *
     * @param  tabIndex the tab index.
     *
     * @return the bounds.
     */
    protected Rectangle getCloseButtonBounds(int tabIndex) {
        Rectangle bounds = new Rectangle(rects[tabIndex]);

        bounds.width  = closeButtonSize;
        bounds.height = closeButtonSize;

        bounds.y += (rects[tabIndex].height - closeButtonSize - closeButtonInsets.top - closeButtonInsets.bottom) / 2
            + closeButtonInsets.top;

        boolean onLeft = isCloseButtonOnLeft();

        if (onLeft) {
            int offset = orientation == ControlOrientation.VERTICAL || tabIndex == 0 ? 6 : 3;

            bounds.x += offset;
        } else {
            int offset = orientation == ControlOrientation.VERTICAL || tabIndex == tabPane.getTabCount() - 1 ? 6 : 4;

            bounds.x += rects[tabIndex].width - bounds.width - offset;
        }

        return bounds;
    }

    /**
     * Return {@code true} if the tab close button should be placed on the left,
     * {@code false} otherwise.
     *
     * @return {@code true} if the tab close button should be placed on the
     *         left, {@code false} otherwise.
     */
    private boolean isCloseButtonOnLeft() {
        return (tabCloseButtonPlacement == LEFT) == tabPane.getComponentOrientation().isLeftToRight();
    }

    /**
     * Called when a close tab button is pressed.
     *
     * @param tabIndex TODO
     */
    protected void doClose(int tabIndex) {
        if (tabCloseListener == null || tabCloseListener.tabAboutToBeClosed(tabIndex)) {
            String    title     = tabPane.getTitleAt(tabIndex);
            Component component = tabPane.getComponentAt(tabIndex);

            tabPane.removeTabAt(tabIndex);

            if (tabCloseListener != null) {
                tabCloseListener.tabClosed(title, component);
            }
        }
    }

    /**
     * The scrollable tab button.
     */
    private class SynthScrollableTabButton extends SeaGlassArrowButton implements UIResource {
        private static final long serialVersionUID = -3983149584304630486L;

        /**
         * Creates a new SynthScrollableTabButton object.
         *
         * @param direction the arrow direction.
         */
        public SynthScrollableTabButton(int direction) {
            super(direction);
            putClientProperty("__arrow_scale__", 0.6);
        }

        /**
         * @see javax.swing.JComponent#getPreferredSize()
         */
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(20, 20);
        }
    }

    /**
     * The layout manager.
     */
    protected class SeaGlassTabbedPaneLayout extends TabbedPaneLayout {

        /**
         * @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#preferredTabAreaHeight(int,
         *      int)
         */
        protected int preferredTabAreaHeight(int tabPlacement, int width) {
            return calculateMaxTabHeight(tabPlacement);
        }

        /**
         * @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#preferredTabAreaWidth(int,
         *      int)
         */
        protected int preferredTabAreaWidth(int tabPlacement, int height) {
            return calculateMaxTabWidth(tabPlacement);
        }

        /**
         * @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#layoutContainer(java.awt.Container)
         */
        public void layoutContainer(Container parent) {
            setRolloverTab(-1);

            setScrollButtonDirections();
            calculateLayoutInfo();

            boolean shouldChangeFocus = verifyFocus(tabPane.getSelectedIndex());

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

            calcContentRect();
           
            for (int i = 0; i < tabPane.getComponentCount(); i++) {
                Component child = tabPane.getComponent(i);
                // Ignore the scroll buttons. They have already been positioned in
                // calculateTabRects, which will have been called by calculateLayoutInfo,
                // which is called above.
                if (child != scrollBackwardButton && child != scrollForwardButton) {
                    child.setBounds(contentRect);
                }
            }
            setTabContainerBounds();
            layoutTabComponents();

            if (shouldChangeFocus && !SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent())) {
                tabPane.requestFocus();
            }
        }
       
        /**
         * Check if we have a custom tab component container.
         * @return the container used to hold custom tab components.
         */
        private Component getTabContainer() {
            int tabCount = tabPane.getTabCount();
            for (int i = 0; i < tabCount; i++) {
                Component tabComponent = tabPane.getTabComponentAt(i);
                if (tabComponent != null) {
                    return tabComponent.getParent();
                }
            }
            return null;
        }

        /**
         * Set the position of the tab container that is used to layout custom tab components.
         */
        private void setTabContainerBounds() {
            Component tabContainer = getTabContainer();
            if (tabContainer != null) {
                int tabContainerX = tabPlacement == RIGHT ? contentRect.width : 0;
                int tabContainerY =  tabPlacement == BOTTOM ? contentRect.height : 0;
                int tabContainerWidth = tabPlacement == LEFT || tabPlacement == RIGHT ? tabPane.getWidth() - contentRect.width : contentRect.width;
                int tabContainerHeight = tabPlacement == TOP || tabPlacement == BOTTOM ? tabPane.getHeight() - contentRect.height : contentRect.height;
                tabContainer.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
            }
        }

        /**
         * Set the directions of the arrows in the scroll buttons if necessary.
         */
        private void setScrollButtonDirections() {
            if (tabPlacement == LEFT || tabPlacement == RIGHT) {
                if (scrollForwardButton.getDirection() != SOUTH) {
                    scrollForwardButton.setDirection(SOUTH);
                }

                if (scrollBackwardButton.getDirection() != NORTH) {
                    scrollBackwardButton.setDirection(NORTH);
                }
            } else if (tabPane.getComponentOrientation().isLeftToRight()) {
                if (scrollForwardButton.getDirection() != EAST) {
                    scrollForwardButton.setDirection(EAST);
                }

                if (scrollBackwardButton.getDirection() != WEST) {
                    scrollBackwardButton.setDirection(WEST);
                }
            } else {
                if (scrollForwardButton.getDirection() != WEST) {
                    scrollForwardButton.setDirection(WEST);
                }

                if (scrollBackwardButton.getDirection() != EAST) {
                    scrollBackwardButton.setDirection(EAST);
                }
            }
        }

        /**
         * Verify that the currently focused element exists. Reset the focus to
         * none if it doesn't. Return whether focus needs to be changed.
         *
         * @param  selectedIndex the current selected index.
         *
         * @return {@code true} if the focus needs to be changed, {@code false}
         *         otherwise.
         */
        private boolean verifyFocus(int selectedIndex) {
            Component visibleComponent  = getVisibleComponent();
            Component selectedComponent = null;

            if (selectedIndex < 0) {
                if (visibleComponent != null) {
                    // The last tab was removed, so remove the component.
                    setVisibleComponent(null);
                }
            } else {
                selectedComponent = tabPane.getComponentAt(selectedIndex);
            }

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

            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 (findFocusOwner(visibleComponent) != null) {
                        shouldChangeFocus = true;
                    }
                }

                setVisibleComponent(selectedComponent);
            }

            return shouldChangeFocus;
        }

        /**
         * Calculate the bounds Rectangle for the content panes.
         */
        private void calcContentRect() {
            Insets    contentInsets = getContentBorderInsets(tabPlacement);
            Rectangle bounds        = tabPane.getBounds();
            Insets    insets        = tabPane.getInsets();

            int cx;
            int cy;
            int cw;
            int ch;

            switch (tabPlacement) {

            case LEFT:
                cx = tabAreaRect.x + tabAreaRect.width + contentInsets.left;
                cy = tabAreaRect.y + contentInsets.top;
                cw = bounds.width - insets.left - insets.right - tabAreaRect.width - contentInsets.left - contentInsets.right;
                ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
                break;

            case RIGHT:
                cx = insets.left + contentInsets.left;
                cy = insets.top + contentInsets.top;
                cw = bounds.width - insets.left - insets.right - tabAreaRect.width - contentInsets.left - contentInsets.right;
                ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
                break;

            case BOTTOM:
                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 - tabAreaRect.height - contentInsets.top - contentInsets.bottom;
                break;

            case TOP:
            default:
                cx = tabAreaRect.x + contentInsets.left;
                cy = tabAreaRect.y + tabAreaRect.height + contentInsets.top;
                cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
                ch = bounds.height - insets.top - insets.bottom - tabAreaRect.height - contentInsets.top - contentInsets.bottom;
            }

            contentRect.setBounds(cx, cy, cw, ch);
        }

        /**
         * Layout the tab components.
         */
        private void layoutTabComponents() {
            Rectangle rect = new Rectangle();

            for (int i = 0; i < tabPane.getTabCount(); i++) {
                Component c = tabPane.getTabComponentAt(i);

                if (c == null) {
                    continue;
                }

                getTabBounds(i, rect);

                Dimension preferredSize = c.getPreferredSize();
                Insets    insets        = getTabInsets(tabPlacement, i);
                int       outerX        = (rect.x-c.getParent().getX()) + insets.left;
                int       outerY        = (rect.y-c.getParent().getY()) + insets.top;
                int       outerWidth    = rect.width - insets.left - insets.right;
                int       outerHeight   = rect.height - insets.top - insets.bottom;

                // centralize component
                // TODO rossi 30.06.2011 this may need adjustment to respect the optional close buttons
                int       x             = outerX + (outerWidth - preferredSize.width) / 2;
                int       y             = outerY + (outerHeight - preferredSize.height) / 2;
                boolean   isSelected    = i == tabPane.getSelectedIndex();

                c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSelected),
                            y + getTabLabelShiftY(tabPlacement, i, isSelected),
                            preferredSize.width, preferredSize.height);
            }
        }

        /**
         * @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#calculateTabRects(int,
         *      int)
         */
        protected void calculateTabRects(int tabPlacement, int tabCount) {
            if (orientation == ControlOrientation.HORIZONTAL) {
                maxTabHeight = calculateMaxTabHeight(tabPlacement);
            } else {
                maxTabWidth = calculateMaxTabWidth(tabPlacement);
            }

            // Calculate the tab area itself.
            calcTabAreaRect();

            if (tabCount == 0) {
                scrollBackwardButton.setVisible(false);
                scrollForwardButton.setVisible(false);
                runCount    = 0;
                selectedRun = -1;
                return;
            }

            selectedRun = 0;
            runCount    = 1;

            int selectedIndex = tabPane.getSelectedIndex();

            if (leadingTabIndex > selectedIndex) {
                leadingTabIndex = selectedIndex;
            }

            Insets    tabAreaInsets = getTabAreaInsets(tabPlacement);
            Dimension size          = new Dimension(tabAreaRect.width - tabAreaInsets.left - tabAreaInsets.right,
                                                    tabAreaRect.height - tabAreaInsets.top - tabAreaInsets.bottom);
            int       tabAreaLength = orientation.getLength(size);
            int       buttonLength  = orientation.getLength(scrollForwardButton.getPreferredSize());

            determineVisibleTabIndices(tabCount, selectedIndex, tabAreaLength, buttonLength);
            resetTabPositionsToLeadingTabIndex(tabCount);
            int totalLength = orientation.getPosition(rects[trailingTabIndex].x + rects[trailingTabIndex].width,
                                                      rects[trailingTabIndex].y + rects[trailingTabIndex].height);

            if (leadingTabIndex > 0 || trailingTabIndex < tabCount - 1) {
                resizeTabs(tabCount, totalLength, buttonLength, tabAreaLength);
            } else {
                centerTabs(tabCount, totalLength, tabAreaLength);
            }

            // Set the positions and visibility of the scroll buttons.
            setScrollButtonPositions(scrollBackwardButton, (leadingTabIndex > 0),
                                     orientation.getPosition(rects[leadingTabIndex]) - buttonLength);
            setScrollButtonPositions(scrollForwardButton, (trailingTabIndex < tabCount - 1),
                                     orientation.getPosition(rects[trailingTabIndex]) + orientation.getLength(rects[trailingTabIndex]));

            // If component orientation right to left and tab placement is on the top or the bottom,
            // flip x positions and adjust by widths.
            if (!tabPane.getComponentOrientation().isLeftToRight() && orientation == ControlOrientation.HORIZONTAL) {
                flipRightToLeft(tabCount, tabPane.getSize());
            }
        }

        /**
         * Calculate the rectangle into which the tabs will be drawn. This does
         * not include the tab area insets, but does include the tab pane
         * insets.
         *
         * <p>This is used for painting the background as well as for laying out
         * the tabs.</p>
         */
        private void calcTabAreaRect() {
            Insets    insets        = tabPane.getInsets();
            Insets    tabAreaInsets = getTabAreaInsets(tabPlacement);
            Rectangle bounds        = tabPane.getBounds();

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

            // Calculate bounds within which a tab run must fit.
            int position;
            int offset;
            int length;
            int thickness;

            if (orientation == ControlOrientation.HORIZONTAL) {
                length    = bounds.width - insets.left - insets.right;
                position  = insets.left;
                thickness = maxTabHeight + tabAreaInsets.top + tabAreaInsets.bottom;
                offset    = (tabPlacement == BOTTOM) ? bounds.height - insets.bottom - thickness : insets.top;
            } else {
                length    = bounds.height - insets.top - insets.bottom;
                position  = insets.top;
                thickness = maxTabWidth + tabAreaInsets.left + tabAreaInsets.right;
                offset    = (tabPlacement == RIGHT) ? bounds.width - insets.right - thickness : insets.left;
            }

            tabAreaRect.setBounds(orientation.createBounds(position, offset, length, thickness));
        }

        /**
         * Calculate the leading and trailing tab indices that will fit in the
         * length, keeping the selected index visible.
         *
         * @param tabCount      the number of tabs.
         * @param selectedIndex the current tab.
         * @param tabAreaLength the length of the tab area. This takes the tab
         *                      area insets into account.
         * @param buttonLength  the length of a scroll button. They are both the
         *                      same length.
         */
        private void determineVisibleTabIndices(int tabCount, int selectedIndex, int tabAreaLength, int buttonLength) {
            int desiredMaximumLength = calcDesiredMaximumLength(tabCount);
            int leadingTabOffset     = orientation.getPosition(rects[leadingTabIndex]);
            int selectedTabEndOffset = orientation.getPosition(rects[selectedIndex].x + rects[selectedIndex].width,
                                                               rects[selectedIndex].y + rects[selectedIndex].height);

            if (desiredMaximumLength <= tabAreaLength) {
                // Fits with no scroll buttons.
                leadingTabIndex  = 0;
                trailingTabIndex = tabCount - 1;
            } else if (desiredMaximumLength - leadingTabOffset + buttonLength <= tabAreaLength) {
                // Fits from current leading tab index, with scroll backward button. Leave leadingTabIndex alone.
                trailingTabIndex = tabCount - 1;
            } else if ((leadingTabIndex == 0 && selectedTabEndOffset - leadingTabOffset + buttonLength <= tabAreaLength)
                    || (selectedTabEndOffset - leadingTabOffset + 2 * buttonLength <= tabAreaLength)) {
                // Selected index fits with current leading tab index and one or two scroll buttons. Leave leadingTabIndex alone.
                trailingTabIndex = -1;

                for (int i = tabCount - 1; i > selectedIndex; i--) {
                    int end = orientation.getPosition(rects[i].x + rects[i].width, rects[i].y + rects[i].height);

                    if (end - leadingTabOffset + 2 * buttonLength <= tabAreaLength) {
                        trailingTabIndex = i;
                        break;
                    }
                }

                if (trailingTabIndex == -1) {
                    trailingTabIndex = selectedIndex;
                }
            } else {
                // Selected index does not fit with current leading index and two scroll buttons.
                // Make selected index the trailing index and find the leading index that will fit.
                trailingTabIndex = selectedIndex;
                leadingTabIndex  = -1;

                for (int i = 0; i < selectedIndex; i++) {
                    int start = orientation.getPosition(rects[i]);

                    if (selectedTabEndOffset - start + 2 * buttonLength <= tabAreaLength) {
                        leadingTabIndex = i;
                        break;
                    }
                }

                if (leadingTabIndex == -1) {
                    leadingTabIndex = selectedIndex;
                }
            }

            tabRuns[0] = leadingTabIndex;
        }

        /**
         * Run through tabs and lay them all out in a single run, assigning
         * maxTabWidth and maxTabHeight. The offset and thickness are set to
         * zero in this method. They will be assigned good values later.
         *
         * @param  tabCount the number of tabs.
         *
         * @return the maximum width, if tabs run horizontall, otherwise the
         *         maximum height.
         */
        private int calcDesiredMaximumLength(int tabCount) {
            FontMetrics metrics       = getFontMetrics();
            int         fontHeight    = metrics.getHeight();
            Insets      tabAreaInsets = getTabAreaInsets(fontHeight);
            Point       corner        = new Point(tabAreaRect.x + tabAreaInsets.left,
                                                  tabAreaRect.y + tabAreaInsets.top);
            int         offset        = orientation.getOrthogonalOffset(corner);
            int         thickness     = (orientation == ControlOrientation.HORIZONTAL) ? maxTabWidth : maxTabHeight;
            int         position      = 0;
            int         maxTabLength  = 0;

            // Run through tabs and lay them out in a single long run.
            for (int i = 0; i < tabCount; i++) {
                int length = (orientation == ControlOrientation.HORIZONTAL) ? calculateTabWidth(TOP, i, metrics)
                                                                            : calculateTabHeight(LEFT, i, fontHeight);

                rects[i].setBounds(orientation.createBounds(position, offset, length, thickness));

                // Update the maximum length and the next tab position.
                maxTabLength =  Math.max(maxTabLength, length);
                position     += length;
            }

            // Update the BasicTabbedPaneUI length variable.
            if (orientation == ControlOrientation.HORIZONTAL) {
                maxTabWidth = maxTabLength;
            } else {
                maxTabHeight = maxTabLength;
            }

            return position;
        }

        /**
         * Reset the positions of the tabs between leadingTabIndex and
         * trailingTabIndex, inclusive, such that the leadingTabIndex is at
         * position zero.
         *
         * @param tabCount the number of tabs.
         */
        private void resetTabPositionsToLeadingTabIndex(int tabCount) {
            // Rebalance the layout such that the leading tab is at position 0.
            int leadingTabPosition = orientation.getPosition(rects[leadingTabIndex]);

            for (int i = 0; i < tabCount; i++) {
                if (i < leadingTabIndex || i > trailingTabIndex) {
                    rects[i].setBounds(-1, -1, 0, 0);
                } else {
                    orientation.updateBoundsPosition(rects[i], orientation.getPosition(rects[i]) - leadingTabPosition);
                }
            }
        }

        /**
         * Center the tabs in the tab area.
         *
         * @param tabCount      the number of tabs.
         * @param totalLength   the total length available of the tabs.
         * @param tabAreaLength the total length available.
         */
        private void centerTabs(int tabCount, int totalLength, int tabAreaLength) {
            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
            Point  corner        = new Point(tabAreaRect.x + tabAreaInsets.left, tabAreaRect.y + tabAreaInsets.top);
            int    startPosition = orientation.getPosition(corner);
            int    offset        = orientation.getOrthogonalOffset(corner);
            int    thickness     = (orientation == ControlOrientation.HORIZONTAL) ? maxTabHeight : maxTabWidth;
            int    delta         = -(tabAreaLength - totalLength) / 2 - startPosition;

            for (int i = leadingTabIndex; i <= trailingTabIndex; i++) {
                int position = orientation.getPosition(rects[i]) - delta;
                int length   = orientation.getLength(rects[i]);

                rects[i].setBounds(orientation.createBounds(position, offset, length, thickness));
            }
        }

        /**
         * Fill out the visible tabs and scroll buttons to fit the available
         * length.
         *
         * @param tabCount      the number of tabs.
         * @param totalLength   the total length available of the tabs.
         * @param buttonLength  the size of a scroll button.
         * @param tabAreaLength the total length available.
         */
        private void resizeTabs(int tabCount, int totalLength, int buttonLength, int tabAreaLength) {
            // Subtract off the button length from the available length.
            if (leadingTabIndex > 0) {
                tabAreaLength -= buttonLength;
            }

            if (trailingTabIndex < tabCount - 1) {
                tabAreaLength -= buttonLength;
            }

            Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
            Point  corner        = new Point(tabAreaRect.x + tabAreaInsets.left, tabAreaRect.y + tabAreaInsets.top);
            int    startPosition = orientation.getPosition(corner);
            int    offset        = orientation.getOrthogonalOffset(corner);
            int    thickness     = (orientation == ControlOrientation.HORIZONTAL) ? maxTabHeight : maxTabWidth;

            // Fill the tabs to the available width.
            float  multiplier    = ((float) tabAreaLength / totalLength);

            for (int i = leadingTabIndex; i <= trailingTabIndex; i++) {
                int position = (i == leadingTabIndex) ? startPosition + (leadingTabIndex > 0 ? buttonLength : 0)
                                                      : orientation.getPosition(rects[i - 1]) + orientation.getLength(rects[i - 1]);
                int length   = (int) (orientation.getLength(rects[i]) * multiplier);

                rects[i].setBounds(orientation.createBounds(position, offset, length, thickness));
            }
        }

        /**
         * Set the bounds Rectangle for a scroll button.
         *
         * @param child    the scroll button.
         * @param visible  whether the button is visible or not.
         * @param position the position from the start of the tab run.
         */
        private void setScrollButtonPositions(Component child, boolean visible, int position) {
            if (visible) {
                child.setBounds(orientation.createBounds(position,
                                                         orientation.getOrthogonalOffset(rects[leadingTabIndex]),
                                                         orientation.getLength(child.getPreferredSize()),
                                                         orientation.getThickness(rects[leadingTabIndex])));
            }

            child.setEnabled(tabPane.isEnabled());
            child.setVisible(visible);
        }

        /**
         * Flip the buttons right to left.
         *
         * @param tabCount the number of tabs.
         * @param size     the rectangle to fit them in.
         */
        private void flipRightToLeft(int tabCount, Dimension size) {
            int rightMargin = size.width;

            for (int i = 0; i < tabCount; i++) {
                rects[i].x = rightMargin - rects[i].x - rects[i].width;
            }

            if (scrollBackwardButton.isVisible()) {
                Rectangle b = scrollBackwardButton.getBounds();

                scrollBackwardButton.setLocation(rightMargin - b.x - b.width, b.y);
            }

            if (scrollForwardButton.isVisible()) {
                Rectangle b = scrollForwardButton.getBounds();

                scrollForwardButton.setLocation(rightMargin - b.x - b.width, b.y);
            }
        }

        /**
         * Find the focus owner of the component.
         *
         * @param  visibleComponent the component.
         *
         * @return the focus owner of the component.
         */
        private Component findFocusOwner(Component visibleComponent) {
            Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();

            // Verify that focusOwner is a descendant of visibleComponent.
            for (Component temp = focusOwner; temp != null; temp = (temp instanceof Window) ? null : temp.getParent()) {
                if (temp == visibleComponent) {
                    return focusOwner;
                }
            }

            return null;
        }
    }

    /**
     * Our Mouse Handler. Delegates to the basic UI's mouse handler if we don't
     * need to handle an event.
     */
    public class SeaGlassTabbedPaneMouseHandler extends MouseAdapter {

        /** Current mouse x coordinate. */
        protected transient int currentMouseX;

        /** Current mouse y coordinate. */
        protected transient int currentMouseY;

        private MouseListener       delegate;
        private MouseMotionListener delegate2;

        /**
         * Creates a new SeaGlassTabbedPaneMouseHandler object.
         *
         * @param originalMouseListener the original mouse handler.
         */
        public SeaGlassTabbedPaneMouseHandler(MouseListener originalMouseListener) {
            delegate  = originalMouseListener;
            delegate2 = (MouseMotionListener) originalMouseListener;

            closeButtonHoverIndex = -1;
            closeButtonArmedIndex = -1;
        }

        /**
         * @see java.awt.event.MouseAdapter#mouseClicked(java.awt.event.MouseEvent)
         */
        public void mouseClicked(MouseEvent e) {
            delegate.mouseClicked(e);
        }

        /**
         * @see java.awt.event.MouseAdapter#mouseEntered(java.awt.event.MouseEvent)
         */
        public void mouseEntered(MouseEvent e) {
            delegate.mouseEntered(e);
        }

        /**
         * @see java.awt.event.MouseAdapter#mouseExited(java.awt.event.MouseEvent)
         */
        public void mouseExited(MouseEvent e) {
            delegate.mouseExited(e);
        }

        /**
         * @see java.awt.event.MouseAdapter#mouseMoved(java.awt.event.MouseEvent)
         */
        public void mouseMoved(MouseEvent e) {
            int oldHoverIndex = closeButtonHoverIndex;

            // Test for mouse position and set hover index.
            currentMouseX = e.getX();
            currentMouseY = e.getY();

            isOverCloseButton(currentMouseX, currentMouseY);

            if (oldHoverIndex != closeButtonHoverIndex) {
                tabPane.repaint();
                return;
            }

            delegate2.mouseMoved(e);
        }

        /**
         * @see java.awt.event.MouseAdapter#mouseDragged(java.awt.event.MouseEvent)
         */
        public void mouseDragged(MouseEvent e) {
            currentMouseX = e.getX();
            currentMouseY = e.getY();

            if (closeButtonArmedIndex != -1 && !isOverCloseButton(currentMouseX, currentMouseY)) {
                // isOverCloseButton resets closeButtonArmedIndex.
                tabPane.repaint();
            }
        }

        /**
         * @see java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
         */
        public void mousePressed(MouseEvent e) {
            if (!tabPane.isEnabled()) {
                return;
            }

            if (!SwingUtilities.isLeftMouseButton(e) || !tabPane.isEnabled()) {
                return;
            }

            int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());

            currentMouseX = e.getX();
            currentMouseY = e.getY();

            if (isOverCloseButton(currentMouseX, currentMouseY)) {
                closeButtonArmedIndex = tabIndex;
                tabPane.repaint();
                return;
            } else if (closeButtonArmedIndex != -1) {
                // isOverCloseButton resets closeButtonArmedIndex.
                tabPane.repaint();
                return;
            }

            if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
                if (tabIndex == tabPane.getSelectedIndex()) {
                    // Clicking on selected tab
                    selectedTabIsPressed = true;

                    // TODO need to just repaint the tab area!
                    tabPane.repaint();
                }
            }

            // forward the event (this will set the selected index, or none
            // at all
            delegate.mousePressed(e);
        }

        /**
         * @see java.awt.event.MouseAdapter#mouseReleased(java.awt.event.MouseEvent)
         */
        public void mouseReleased(MouseEvent e) {
            if (closeButtonArmedIndex != -1) {
                if (isOverCloseButton(currentMouseX, currentMouseY)) {
                    doClose(closeButtonArmedIndex);
                }

                closeButtonArmedIndex = -1;

                tabPane.repaint();
            } else if (selectedTabIsPressed) {
                selectedTabIsPressed = false;

                // TODO need to just repaint the tab area!
                tabPane.repaint();
            }

            // forward the event
            delegate.mouseReleased(e);

            // hack: The super method *should* be setting the mouse-over
            // property correctly here, but it doesn't. That is, when the
            // mouse is released, whatever tab is below the released mouse
            // should be in rollover state. But, if you select a tab and
            // don't move the mouse, this doesn't happen. Hence, forwarding
            // the event.
            delegate2.mouseMoved(e);
        }
    }
}
TOP

Related Classes of com.seaglasslookandfeel.ui.SeaGlassTabbedPaneUI

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