jdk/src/macosx/classes/com/apple/laf/AquaTabbedPaneUI.java
author alexsch
Thu, 17 May 2012 14:27:11 +0400
changeset 12657 77b051227740
parent 12047 320a714614e9
child 19173 5c179adfb12b
permissions -rw-r--r--
7148281: [macosx] JTabbedPane tabs with HTML text do not render correctly Reviewed-by: kizune

/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.apple.laf;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.beans.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.text.View;

import sun.swing.SwingUtilities2;
import apple.laf.*;
import apple.laf.JRSUIConstants.*;

public class AquaTabbedPaneUI extends AquaTabbedPaneCopyFromBasicUI {
    private static final int kSmallTabHeight = 20; // height of a small tab
    private static final int kLargeTabHeight = 23; // height of a large tab
    private static final int kMaxIconSize = kLargeTabHeight - 7;

    private static final double kNinetyDegrees = (Math.PI / 2.0); // used for rotation

    protected final Insets currentContentDrawingInsets = new Insets(0, 0, 0, 0);
    protected final Insets currentContentBorderInsets = new Insets(0, 0, 0, 0);
    protected final Insets contentDrawingInsets = new Insets(0, 0, 0, 0);

    protected int pressedTab = -3; // -2 is right scroller, -1 is left scroller
    protected boolean popupSelectionChanged;

    protected Boolean isDefaultFocusReceiver = null;
    protected boolean hasAvoidedFirstFocus = false;

    // Create PLAF
    public static ComponentUI createUI(final JComponent c) {
        return new AquaTabbedPaneUI();
    }

    protected final AquaTabbedPaneTabState visibleTabState = new AquaTabbedPaneTabState(this);
    protected final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getTab());

    public AquaTabbedPaneUI() { }

    protected void installListeners() {
        super.installListeners();

        // We're not just a mouseListener, we're a mouseMotionListener
        if (mouseListener != null) {
            tabPane.addMouseMotionListener((MouseMotionListener)mouseListener);
        }
    }

    protected void installDefaults() {
        super.installDefaults();

        if (tabPane.getFont() instanceof UIResource) {
            final Boolean b = (Boolean)UIManager.get("TabbedPane.useSmallLayout");
            if (b != null && b == Boolean.TRUE) {
                tabPane.setFont(UIManager.getFont("TabbedPane.smallFont"));
                painter.state.set(Size.SMALL);
            }
        }

        contentDrawingInsets.set(0, 11, 13, 10);
        tabPane.setOpaque(false);
    }

    protected void assureRectsCreated(final int tabCount) {
        visibleTabState.init(tabCount);
        super.assureRectsCreated(tabCount);
    }

    protected void uninstallDefaults() {
        contentDrawingInsets.set(0, 0, 0, 0);
    }

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

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

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

    protected LayoutManager createLayoutManager() {
        return new AquaTruncatingTabbedPaneLayout();
    }

    protected boolean shouldRepaintSelectedTabOnMouseDown() {
        return false;
    }

    // Paint Methods
    // Cache for performance
    final Rectangle fContentRect = new Rectangle();
    final Rectangle fIconRect = new Rectangle();
    final Rectangle fTextRect = new Rectangle();

    // UI Rendering
    public void paint(final Graphics g, final JComponent c) {
        painter.state.set(getDirection());

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

        // we want to call ensureCurrentLayout, but it's private
        ensureCurrentLayout();
        final Rectangle clipRect = g.getClipBounds();

        final boolean active = tabPane.isEnabled();
        final boolean frameActive = AquaFocusHandler.isActive(tabPane);
        final boolean isLeftToRight = tabPane.getComponentOrientation().isLeftToRight() || tabPlacement == LEFT || tabPlacement == RIGHT;

        // Paint tabRuns of tabs from back to front
        if (visibleTabState.needsScrollTabs()) {
            paintScrollingTabs(g, clipRect, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
            return;
        }

        // old way
        paintAllTabs(g, clipRect, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
    }

    protected void paintAllTabs(final Graphics g, final Rectangle clipRect, final int tabPlacement, final int selectedIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) {
        boolean drawSelectedLast = false;
        for (int i = 0; i < rects.length; i++) {
            if (i == selectedIndex) {
                drawSelectedLast = true;
            } else {
                if (rects[i].intersects(clipRect)) {
                    paintTabNormal(g, tabPlacement, i, active, frameActive, isLeftToRight);
                }
            }
        }

        // paint the selected tab last.
        if (drawSelectedLast && rects[selectedIndex].intersects(clipRect)) {
            paintTabNormal(g, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
        }
    }

    protected void paintScrollingTabs(final Graphics g, final Rectangle clipRect, final int tabPlacement, final int selectedIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) {
//        final Graphics g2 = g.create();
//        g2.setColor(Color.cyan);
//        Rectangle r = new Rectangle();
//        for (int i = 0; i < visibleTabState.getTotal(); i++) {
//            r.add(rects[visibleTabState.getIndex(i)]);
//        }
//        g2.fillRect(r.x, r.y, r.width, r.height);
//        g2.dispose();
//        System.out.println(r);

        // for each visible tab, except the selected one
        for (int i = 0; i < visibleTabState.getTotal(); i++) {
            final int realIndex = visibleTabState.getIndex(i);
            if (realIndex != selectedIndex) {
                if (rects[realIndex].intersects(clipRect)) {
                    paintTabNormal(g, tabPlacement, realIndex, active, frameActive, isLeftToRight);
                }
            }
        }

        final Rectangle leftScrollTabRect = visibleTabState.getLeftScrollTabRect();
        if (visibleTabState.needsLeftScrollTab() && leftScrollTabRect.intersects(clipRect)) {
            paintTabNormalFromRect(g, tabPlacement, leftScrollTabRect, -2, fIconRect, fTextRect, visibleTabState.needsLeftScrollTab(), frameActive, isLeftToRight);
        }

        final Rectangle rightScrollTabRect = visibleTabState.getRightScrollTabRect();
        if (visibleTabState.needsRightScrollTab() && rightScrollTabRect.intersects(clipRect)) {
            paintTabNormalFromRect(g, tabPlacement, rightScrollTabRect, -1, fIconRect, fTextRect, visibleTabState.needsRightScrollTab(), frameActive, isLeftToRight);
        }

        if (selectedIndex >= 0) { // && rects[selectedIndex].intersects(clipRect)) {
            paintTabNormal(g, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
        }
    }

    private static boolean isScrollTabIndex(final int index) {
        return index == -1 || index == -2;
    }

    protected static void transposeRect(final Rectangle r) {
        int temp = r.y;
        r.y = r.x;
        r.x = temp;
        temp = r.width;
        r.width = r.height;
        r.height = temp;
    }

    protected int getTabLabelShiftX(final int tabPlacement, final int tabIndex, final boolean isSelected) {
        final Rectangle tabRect = (tabIndex >= 0 ? rects[tabIndex] : visibleTabState.getRightScrollTabRect());
        int nudge = 0;
        switch (tabPlacement) {
            case LEFT:
            case RIGHT:
                nudge = tabRect.height % 2;
                break;
            case BOTTOM:
            case TOP:
            default:
                nudge = tabRect.width % 2;
        }
        return nudge;
    }

    protected int getTabLabelShiftY(final int tabPlacement, final int tabIndex, final boolean isSelected) {
        switch (tabPlacement) {
            case RIGHT:
            case LEFT:
            case BOTTOM:
                return -1;
            case TOP:
            default:
                return 0;
        }
    }

    protected Icon getIconForScrollTab(final int tabPlacement, final int tabIndex, final boolean enabled) {
        boolean shouldFlip = !AquaUtils.isLeftToRight(tabPane);
        if (tabPlacement == RIGHT) shouldFlip = false;
        if (tabPlacement == LEFT) shouldFlip = true;

        int direction = tabIndex == -1 ? EAST : WEST;
        if (shouldFlip) {
            if (direction == EAST) {
                direction = WEST;
            } else if (direction == WEST) {
                direction = EAST;
            }
        }

        if (enabled) return AquaImageFactory.getArrowIconForDirection(direction);

        final Image icon = AquaImageFactory.getArrowImageForDirection(direction);
        return new ImageIcon(AquaUtils.generateDisabledImage(icon));
    }

    protected void paintContents(final Graphics g, final int tabPlacement, final int tabIndex, final Rectangle tabRect, final Rectangle iconRect, final Rectangle textRect, final boolean isSelected) {
        final Shape temp = g.getClip();
        g.clipRect(fContentRect.x, fContentRect.y, fContentRect.width, fContentRect.height);

        final Component component;
        final String title;
        final Icon icon;
        if (isScrollTabIndex(tabIndex)) {
            component = null;
            title = null;
            icon = getIconForScrollTab(tabPlacement, tabIndex, true);
        } else {
            component = getTabComponentAt(tabIndex);
            if (component == null) {
                title = tabPane.getTitleAt(tabIndex);
                icon = getIconForTab(tabIndex);
            } else {
                title = null;
                icon = null;
            }
        }

        final boolean isVertical = tabPlacement == RIGHT || tabPlacement == LEFT;
        if (isVertical) {
            transposeRect(fContentRect);
        }

        final Font font = tabPane.getFont();
        final FontMetrics metrics = g.getFontMetrics(font);

        // our scrolling tabs
        layoutLabel(tabPlacement, metrics, tabIndex < 0 ? 0 : tabIndex, title, icon, fContentRect, iconRect, textRect, false); // Never give it "isSelected" - ApprMgr handles this
        if (isVertical) {
            transposeRect(fContentRect);
            transposeRect(iconRect);
            transposeRect(textRect);
        }

        // from super.paintText - its normal text painting is totally wrong for the Mac
        if (!(g instanceof Graphics2D)) {
            g.setClip(temp);
            return;
        }
        final Graphics2D g2d = (Graphics2D) g;

        AffineTransform savedAT = null;
        if (isVertical) {
            savedAT = g2d.getTransform();
            rotateGraphics(g2d, tabRect, textRect, iconRect, tabPlacement);
        }

        // not for the scrolling tabs
        if (component == null && tabIndex >= 0) {
            paintTitle(g2d, font, metrics, textRect, tabIndex, title);
        }

        if (icon != null) {
            paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
        }

        if (savedAT != null) {
            g2d.setTransform(savedAT);
        }

        g.setClip(temp);
    }

    protected void paintTitle(final Graphics2D g2d, final Font font, final FontMetrics metrics, final Rectangle textRect, final int tabIndex, final String title) {
        final View v = getTextViewForTab(tabIndex);
        if (v != null) {
            v.paint(g2d, textRect);
            return;
        }

        if (title == null) return;

        final Color color = tabPane.getForegroundAt(tabIndex);
        if (color instanceof UIResource) {
            // sja fix getTheme().setThemeTextColor(g, isSelected, isPressed && tracking, tabPane.isEnabledAt(tabIndex));
            if (tabPane.isEnabledAt(tabIndex)) {
                g2d.setColor(Color.black);
            } else {
                g2d.setColor(Color.gray);
            }
        } else {
            g2d.setColor(color);
        }

        g2d.setFont(font);
        SwingUtilities2.drawString(tabPane, g2d, title, textRect.x, textRect.y + metrics.getAscent());
    }

    protected void rotateGraphics(final Graphics2D g2d, final Rectangle tabRect, final Rectangle textRect, final Rectangle iconRect, final int tabPlacement) {
        int yDiff = 0; // textRect.y - tabRect.y;
        int xDiff = 0; // (tabRect.x+tabRect.width) - (textRect.x+textRect.width);
        int yIconDiff = 0; // iconRect.y - tabRect.y;
        int xIconDiff = 0; // (tabRect.x+tabRect.width) - (iconRect.x + iconRect.width);

        final double rotateAmount = (tabPlacement == LEFT ? -kNinetyDegrees : kNinetyDegrees);
        g2d.transform(AffineTransform.getRotateInstance(rotateAmount, tabRect.x, tabRect.y));

        // x and y diffs are named weirdly.
        // I will rename them, but what they mean now is
        // original x offset which will be used to adjust the y coordinate for the
        // rotated context
        if (tabPlacement == LEFT) {
            g2d.translate(-tabRect.height - 1, 1);
            xDiff = textRect.x - tabRect.x;
            yDiff = tabRect.height + tabRect.y - (textRect.y + textRect.height);
            xIconDiff = iconRect.x - tabRect.x;
            yIconDiff = tabRect.height + tabRect.y - (iconRect.y + iconRect.height);
        } else {
            g2d.translate(0, -tabRect.width - 1);
            yDiff = textRect.y - tabRect.y;
            xDiff = (tabRect.x + tabRect.width) - (textRect.x + textRect.width);
            yIconDiff = iconRect.y - tabRect.y;
            xIconDiff = (tabRect.x + tabRect.width) - (iconRect.x + iconRect.width);
        }

        // rotation changes needed for the rendering
        // we are rotating so we can't just use the rects wholesale.
        textRect.x = tabRect.x + yDiff;
        textRect.y = tabRect.y + xDiff;

        int tempVal = textRect.height;
        textRect.height = textRect.width;
        textRect.width = tempVal;
    // g.setColor(Color.red);
    // g.drawLine(textRect.x, textRect.y, textRect.x+textRect.height, textRect.y+textRect.width);
    // g.drawLine(textRect.x+textRect.height, textRect.y, textRect.x, textRect.y+textRect.width);

        iconRect.x = tabRect.x + yIconDiff;
        iconRect.y = tabRect.y + xIconDiff;

        tempVal = iconRect.height;
        iconRect.height = iconRect.width;
        iconRect.width = tempVal;
    }

    protected void paintTabNormal(final Graphics g, final int tabPlacement, final int tabIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) {
        paintTabNormalFromRect(g, tabPlacement, rects[tabIndex], tabIndex, fIconRect, fTextRect, active, frameActive, isLeftToRight);
    }

    protected void paintTabNormalFromRect(final Graphics g, final int tabPlacement, final Rectangle tabRect, final int nonRectIndex, final Rectangle iconRect, final Rectangle textRect, final boolean active, final boolean frameActive, final boolean isLeftToRight) {
        final int selectedIndex = tabPane.getSelectedIndex();
        final boolean isSelected = selectedIndex == nonRectIndex;

        paintCUITab(g, tabPlacement, tabRect, isSelected, frameActive, isLeftToRight, nonRectIndex);

        textRect.setBounds(tabRect);
        fContentRect.setBounds(tabRect);
        paintContents(g, tabPlacement, nonRectIndex, tabRect, iconRect, textRect, isSelected);
    }

    protected void paintCUITab(final Graphics g, final int tabPlacement, final Rectangle tabRect, final boolean isSelected, final boolean frameActive, final boolean isLeftToRight, final int nonRectIndex) {
        final int tabCount = tabPane.getTabCount();

        final boolean needsLeftScrollTab = visibleTabState.needsLeftScrollTab();
        final boolean needsRightScrollTab = visibleTabState.needsRightScrollTab();

        // first or last
        boolean first = nonRectIndex == 0;
        boolean last = nonRectIndex == tabCount - 1;
        if (needsLeftScrollTab || needsRightScrollTab) {
            if (nonRectIndex == -1) {
                first = false;
                last = true;
            } else if (nonRectIndex == -2) {
                first = true;
                last = false;
            } else {
                if (needsLeftScrollTab) first = false;
                if (needsRightScrollTab) last = false;
            }
        }

        if (tabPlacement == LEFT || tabPlacement == RIGHT) {
            final boolean tempSwap = last;
            last = first;
            first = tempSwap;
        }

        final State state = getState(nonRectIndex, frameActive, isSelected);
        painter.state.set(state);
        painter.state.set(isSelected || (state == State.INACTIVE && frameActive) ? BooleanValue.YES : BooleanValue.NO);
        painter.state.set(getSegmentPosition(first, last, isLeftToRight));
        final int selectedIndex = tabPane.getSelectedIndex();
        painter.state.set(getSegmentTrailingSeparator(nonRectIndex, selectedIndex, isLeftToRight));
        painter.state.set(getSegmentLeadingSeparator(nonRectIndex, selectedIndex, isLeftToRight));
        painter.state.set(tabPane.hasFocus() && isSelected ? Focused.YES : Focused.NO);
        painter.paint(g, tabPane, tabRect.x, tabRect.y, tabRect.width, tabRect.height);

        if (isScrollTabIndex(nonRectIndex)) return;

        final Color color = tabPane.getBackgroundAt(nonRectIndex);
        if (color == null || (color instanceof UIResource)) return;

        if (!isLeftToRight && (tabPlacement == TOP || tabPlacement == BOTTOM)) {
            final boolean tempSwap = last;
            last = first;
            first = tempSwap;
        }

        fillTabWithBackground(g, tabRect, tabPlacement, first, last, color);
    }

    protected Direction getDirection() {
        switch (tabPane.getTabPlacement()) {
            case SwingConstants.BOTTOM: return Direction.SOUTH;
            case SwingConstants.LEFT: return Direction.WEST;
            case SwingConstants.RIGHT: return Direction.EAST;
        }
        return Direction.NORTH;
    }

    protected static SegmentPosition getSegmentPosition(final boolean first, final boolean last, final boolean isLeftToRight) {
        if (first && last) return SegmentPosition.ONLY;
        if (first) return isLeftToRight ? SegmentPosition.FIRST : SegmentPosition.LAST;
        if (last) return isLeftToRight ? SegmentPosition.LAST : SegmentPosition.FIRST;
        return SegmentPosition.MIDDLE;
    }

    protected SegmentTrailingSeparator getSegmentTrailingSeparator(final int index, final int selectedIndex, final boolean isLeftToRight) {
        return SegmentTrailingSeparator.YES;
    }

    protected SegmentLeadingSeparator getSegmentLeadingSeparator(final int index, final int selectedIndex, final boolean isLeftToRight) {
        return SegmentLeadingSeparator.NO;
    }

    protected boolean isTabBeforeSelectedTab(final int index, final int selectedIndex, final boolean isLeftToRight) {
        if (index == -2 && visibleTabState.getIndex(0) == selectedIndex) return true;
        int indexBeforeSelectedIndex = isLeftToRight ? selectedIndex - 1 : selectedIndex + 1;
        return index == indexBeforeSelectedIndex ? true : false;
    }

    protected State getState(final int index, final boolean frameActive, final boolean isSelected) {
        if (!frameActive) return State.INACTIVE;
        if (!tabPane.isEnabled()) return State.DISABLED;
        if (JRSUIUtils.TabbedPane.useLegacyTabs()) {
            if (isSelected) return State.PRESSED;
            if (pressedTab == index) return State.INACTIVE;
        } else {
            if (isSelected) return State.ACTIVE;
            if (pressedTab == index) return State.PRESSED;
        }
        return State.ACTIVE;
    }

    /**
     * This routine adjusts the background fill rect so it just fits inside a tab, allowing for
     * whether we're talking about a first tab or last tab.  NOTE that this code is very much
     * Aqua 2 dependent!
     */
    static class AlterRects {
        Rectangle standard, first, last;
        AlterRects(final int x, final int y, final int w, final int h) { standard = new Rectangle(x, y, w, h); }
        AlterRects start(final int x, final int y, final int w, final int h) { first = new Rectangle(x, y, w, h); return this; }
        AlterRects end(final int x, final int y, final int w, final int h) { last = new Rectangle(x, y, w, h); return this; }

        static Rectangle alter(final Rectangle r, final Rectangle o) {
            // r = new Rectangle(r);
            r.x += o.x;
            r.y += o.y;
            r.width += o.width;
            r.height += o.height;
            return r;
        }
    }

    static AlterRects[] alterRects = new AlterRects[5];

    protected static AlterRects getAlterationFor(final int tabPlacement) {
        if (alterRects[tabPlacement] != null) return alterRects[tabPlacement];

        switch (tabPlacement) {
            case LEFT: return alterRects[LEFT] = new AlterRects(2, 0, -4, 1).start(0, 0, 0, -4).end(0, 3, 0, -3);
            case RIGHT: return alterRects[RIGHT] = new AlterRects(1, 0, -4, 1).start(0, 0, 0, -4).end(0, 3, 0, -3);
            case BOTTOM: return alterRects[BOTTOM] = new AlterRects(0, 1, 0, -4).start(3, 0, -3, 0).end(0, 0, -3, 0);
            case TOP:
            default: return alterRects[TOP] = new AlterRects(0, 2, 0, -4).start(3, 0, -3, 0).end(0, 0, -3, 0);
        }
    }

    protected void fillTabWithBackground(final Graphics g, final Rectangle rect, final int tabPlacement, final boolean first, final boolean last, final Color color) {
        final Rectangle fillRect = new Rectangle(rect);

        final AlterRects alteration = getAlterationFor(tabPlacement);
        AlterRects.alter(fillRect, alteration.standard);
        if (first) AlterRects.alter(fillRect, alteration.first);
        if (last) AlterRects.alter(fillRect, alteration.last);

        g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), (int)(color.getAlpha() * 0.25)));
        g.fillRoundRect(fillRect.x, fillRect.y, fillRect.width, fillRect.height, 3, 1);
    }

    protected Insets getContentBorderInsets(final int tabPlacement) {
        final Insets draw = getContentDrawingInsets(tabPlacement); // will be rotated

        rotateInsets(contentBorderInsets, currentContentBorderInsets, tabPlacement);

        currentContentBorderInsets.left += draw.left;
        currentContentBorderInsets.right += draw.right;
        currentContentBorderInsets.top += draw.top;
        currentContentBorderInsets.bottom += draw.bottom;

        return currentContentBorderInsets;
    }

    protected static void rotateInsets(final Insets topInsets, final Insets targetInsets, final int targetPlacement) {
        switch (targetPlacement) {
            case LEFT:
                targetInsets.top = topInsets.left;
                targetInsets.left = topInsets.top;
                targetInsets.bottom = topInsets.right;
                targetInsets.right = topInsets.bottom;
                break;
            case BOTTOM:
                targetInsets.top = topInsets.bottom;
                targetInsets.left = topInsets.left;
                targetInsets.bottom = topInsets.top;
                targetInsets.right = topInsets.right;
                break;
            case RIGHT:
                targetInsets.top = topInsets.right;
                targetInsets.left = topInsets.bottom;
                targetInsets.bottom = topInsets.left;
                targetInsets.right = topInsets.top;
                break;
            case TOP:
            default:
                targetInsets.top = topInsets.top;
                targetInsets.left = topInsets.left;
                targetInsets.bottom = topInsets.bottom;
                targetInsets.right = topInsets.right;
        }
    }

    protected Insets getContentDrawingInsets(final int tabPlacement) {
        rotateInsets(contentDrawingInsets, currentContentDrawingInsets, tabPlacement);
        return currentContentDrawingInsets;
    }

    protected Icon getIconForTab(final int tabIndex) {
        final Icon mainIcon = super.getIconForTab(tabIndex);
        if (mainIcon == null) return null;

        final int iconHeight = mainIcon.getIconHeight();
        if (iconHeight <= kMaxIconSize) return mainIcon;
        final float ratio = (float)kMaxIconSize / (float)iconHeight;

        final int iconWidth = mainIcon.getIconWidth();
        return new AquaIcon.CachingScalingIcon((int)(iconWidth * ratio), kMaxIconSize) {
            Image createImage() {
                return AquaIcon.getImageForIcon(mainIcon);
            }
        };
    }

    private static final int TAB_BORDER_INSET = 9;
    protected void paintContentBorder(final Graphics g, final int tabPlacement, final int selectedIndex) {
        final int width = tabPane.getWidth();
        final int height = tabPane.getHeight();
        final 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 TOP:
                y += TAB_BORDER_INSET;
                h -= TAB_BORDER_INSET;
                break;
            case BOTTOM:
                h -= TAB_BORDER_INSET;// - 2;
                break;
            case LEFT:
                x += TAB_BORDER_INSET;// - 5;
                w -= TAB_BORDER_INSET;// + 1;
                break;
            case RIGHT:
                w -= TAB_BORDER_INSET;// + 1;
                break;
        }

        if (tabPane.isOpaque()) {
            g.setColor(tabPane.getBackground());
            g.fillRect(0, 0, width, height);
        }

        AquaGroupBorder.getTabbedPaneGroupBorder().paintBorder(tabPane, g, x, y, w, h);
    }

    // see paintContentBorder
    protected void repaintContentBorderEdge() {
        final int width = tabPane.getWidth();
        final int height = tabPane.getHeight();
        final Insets insets = tabPane.getInsets();
        final int tabPlacement = tabPane.getTabPlacement();
        final Insets localContentBorderInsets = getContentBorderInsets(tabPlacement);

        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 = localContentBorderInsets.left;
                break;
            case RIGHT:
                w = localContentBorderInsets.right;
                break;
            case BOTTOM:
                h = localContentBorderInsets.bottom;
                break;
            case TOP:
            default:
                y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                h = localContentBorderInsets.top;
        }
        tabPane.repaint(x, y, w, h);
    }

    public boolean isTabVisible(final int index) {
        if (index == -1 || index == -2) return true;
        for (int i = 0; i < visibleTabState.getTotal(); i++) {
            if (visibleTabState.getIndex(i) == index) return true;
        }
        return false;
    }

    /**
     * Returns the tab index which intersects the specified point
     * in the JTabbedPane's coordinate space.
     */
    public int tabForCoordinate(final JTabbedPane pane, final int x, final int y) {
        ensureCurrentLayout();
        final Point p = new Point(x, y);
        if (visibleTabState.needsScrollTabs()) {
            for (int i = 0; i < visibleTabState.getTotal(); i++) {
                final int realOffset = visibleTabState.getIndex(i);
                if (rects[realOffset].contains(p.x, p.y)) return realOffset;
            }
            if (visibleTabState.getRightScrollTabRect().contains(p.x, p.y)) return -1; //tabPane.getTabCount();
        } else {
            //old way
            final int tabCount = tabPane.getTabCount();
            for (int i = 0; i < tabCount; i++) {
                if (rects[i].contains(p.x, p.y)) return i;
            }
        }
        return -1;
    }

    protected Insets getTabInsets(final int tabPlacement, final int tabIndex) {
        switch (tabPlacement) {
            case LEFT: return UIManager.getInsets("TabbedPane.leftTabInsets");
            case RIGHT: return UIManager.getInsets("TabbedPane.rightTabInsets");
        }
        return tabInsets;
    }

    // This is the preferred size - the layout manager will ignore if it has to
    protected int calculateTabHeight(final int tabPlacement, final int tabIndex, final int fontHeight) {
        // Constrain to what the Mac allows
        final int result = super.calculateTabHeight(tabPlacement, tabIndex, fontHeight);

        // force tabs to have a max height for aqua
        if (result <= kSmallTabHeight) return kSmallTabHeight;
        return kLargeTabHeight;
    }

    // JBuilder requested this - it's against HI, but then so are multiple rows
    protected boolean shouldRotateTabRuns(final int tabPlacement) {
        return false;
    }

    protected class TabbedPanePropertyChangeHandler extends PropertyChangeHandler {
        public void propertyChange(final PropertyChangeEvent e) {
            final String prop = e.getPropertyName();

            if (!AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) {
                super.propertyChange(e);
                return;
            }

            final JTabbedPane comp = (JTabbedPane)e.getSource();
            comp.repaint();

            // Repaint the "front" tab and the border
            final int selected = tabPane.getSelectedIndex();
            final Rectangle[] theRects = rects;
            if (selected >= 0 && selected < theRects.length) comp.repaint(theRects[selected]);
            repaintContentBorderEdge();
        }
    }

    protected ChangeListener createChangeListener() {
        return new ChangeListener() {
            public void stateChanged(final ChangeEvent e) {
                if (!isTabVisible(tabPane.getSelectedIndex())) popupSelectionChanged = true;
                tabPane.revalidate();
                tabPane.repaint();
            }
        };
    }

    protected class FocusHandler extends FocusAdapter {
        Rectangle sWorkingRect = new Rectangle();

        public void focusGained(final FocusEvent e) {
            if (isDefaultFocusReceiver(tabPane) && !hasAvoidedFirstFocus) {
                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
                hasAvoidedFirstFocus = true;
            }
            adjustPaintingRectForFocusRing(e);
        }

        public void focusLost(final FocusEvent e) {
            adjustPaintingRectForFocusRing(e);
        }

        void adjustPaintingRectForFocusRing(final FocusEvent e) {
            final JTabbedPane pane = (JTabbedPane)e.getSource();
            final int tabCount = pane.getTabCount();
            final int selectedIndex = pane.getSelectedIndex();

            if (selectedIndex != -1 && tabCount > 0 && tabCount == rects.length) {
                sWorkingRect.setBounds(rects[selectedIndex]);
                sWorkingRect.grow(4, 4);
                pane.repaint(sWorkingRect);
            }
        }

        boolean isDefaultFocusReceiver(final JComponent component) {
            if (isDefaultFocusReceiver == null) {
                Component defaultFocusReceiver = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy().getDefaultComponent(getTopLevelFocusCycleRootAncestor(component));
                isDefaultFocusReceiver = new Boolean(defaultFocusReceiver != null && defaultFocusReceiver.equals(component));
            }
            return isDefaultFocusReceiver.booleanValue();
        }

        Container getTopLevelFocusCycleRootAncestor(Container container) {
            Container ancestor;
            while ((ancestor = container.getFocusCycleRootAncestor()) != null) {
                container = ancestor;
            }
            return container;
        }
    }

    public class MouseHandler extends MouseInputAdapter implements ActionListener {
        protected int trackingTab = -3;
        protected Timer popupTimer = new Timer(500, this);

        public MouseHandler() {
            popupTimer.setRepeats(false);
        }

        public void mousePressed(final MouseEvent e) {
            final JTabbedPane pane = (JTabbedPane)e.getSource();
            if (!pane.isEnabled()) {
                trackingTab = -3;
                return;
            }

            final Point p = e.getPoint();
            trackingTab = getCurrentTab(pane, p);
            if (trackingTab == -3 || (!shouldRepaintSelectedTabOnMouseDown() && trackingTab == pane.getSelectedIndex())) {
                trackingTab = -3;
                return;
            }

            if (trackingTab < 0 && trackingTab > -3) {
                popupTimer.start();
            }

            pressedTab = trackingTab;
            repaint(pane, pressedTab);
        }

        public void mouseDragged(final MouseEvent e) {
            if (trackingTab < -2) return;

            final JTabbedPane pane = (JTabbedPane)e.getSource();
            final int currentTab = getCurrentTab(pane, e.getPoint());

            if (currentTab != trackingTab) {
                pressedTab = -3;
            } else {
                pressedTab = trackingTab;
            }

            if (trackingTab < 0 && trackingTab > -3) {
                popupTimer.start();
            }

            repaint(pane, trackingTab);
        }

        public void mouseReleased(final MouseEvent e) {
            if (trackingTab < -2) return;

            popupTimer.stop();

            final JTabbedPane pane = (JTabbedPane)e.getSource();
            final Point p = e.getPoint();
            final int currentTab = getCurrentTab(pane, p);

            if (trackingTab == -1 && currentTab == -1) {
                pane.setSelectedIndex(pane.getSelectedIndex() + 1);
            }

            if (trackingTab == -2 && currentTab == -2) {
                pane.setSelectedIndex(pane.getSelectedIndex() - 1);
            }

            if (trackingTab >= 0 && currentTab == trackingTab) {
                pane.setSelectedIndex(trackingTab);
            }

            repaint(pane, trackingTab);

            pressedTab = -3;
            trackingTab = -3;
        }

        public void actionPerformed(final ActionEvent e) {
            if (trackingTab != pressedTab) {
                return;
            }

            if (trackingTab == -1) {
                showFullPopup(false);
                trackingTab = -3;
            }

            if (trackingTab == -2) {
                showFullPopup(true);
                trackingTab = -3;
            }
        }

        int getCurrentTab(final JTabbedPane pane, final Point p) {
            final int tabIndex = tabForCoordinate(pane, p.x, p.y);
            if (tabIndex >= 0 && pane.isEnabledAt(tabIndex)) return tabIndex;

            if (visibleTabState.needsLeftScrollTab() && visibleTabState.getLeftScrollTabRect().contains(p)) return -2;
            if (visibleTabState.needsRightScrollTab() && visibleTabState.getRightScrollTabRect().contains(p)) return -1;

            return -3;
        }

        void repaint(final JTabbedPane pane, final int tab) {
            switch (tab) {
                case -1:
                    pane.repaint(visibleTabState.getRightScrollTabRect());
                    return;
                case -2:
                    pane.repaint(visibleTabState.getLeftScrollTabRect());
                    return;
                default:
                    if (trackingTab >= 0) pane.repaint(rects[trackingTab]);
                    return;
            }
        }

        void showFullPopup(final boolean firstTab) {
            final JPopupMenu popup = new JPopupMenu();

            for (int i = 0; i < tabPane.getTabCount(); i++) {
                if (firstTab ? visibleTabState.isBefore(i) : visibleTabState.isAfter(i)) {
                    popup.add(createMenuItem(i));
                }
            }

            if (firstTab) {
                final Rectangle leftScrollTabRect = visibleTabState.getLeftScrollTabRect();
                final Dimension popupRect = popup.getPreferredSize();
                popup.show(tabPane, leftScrollTabRect.x - popupRect.width, leftScrollTabRect.y + 7);
            } else {
                final Rectangle rightScrollTabRect = visibleTabState.getRightScrollTabRect();
                popup.show(tabPane, rightScrollTabRect.x + rightScrollTabRect.width, rightScrollTabRect.y + 7);
            }

            popup.addPopupMenuListener(new PopupMenuListener() {
                public void popupMenuCanceled(final PopupMenuEvent e) { }
                public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { }

                public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
                    pressedTab = -3;
                    tabPane.repaint(visibleTabState.getLeftScrollTabRect());
                    tabPane.repaint(visibleTabState.getRightScrollTabRect());
                }
            });
        }

        JMenuItem createMenuItem(final int i) {
            final Component component = getTabComponentAt(i);
            final JMenuItem menuItem;
            if (component == null) {
                menuItem = new JMenuItem(tabPane.getTitleAt(i), tabPane.getIconAt(i));
            } else {
                menuItem = new JMenuItem() {
                    public void paintComponent(final Graphics g) {
                        super.paintComponent(g);
                        final Dimension size = component.getSize();
                        component.setSize(getSize());
                        component.validate();
                        component.paint(g);
                        component.setSize(size);
                    }

                    public Dimension getPreferredSize() {
                        return component.getPreferredSize();
                    }
                };
            }

            final Color background = tabPane.getBackgroundAt(i);
            if (!(background instanceof UIResource)) {
                menuItem.setBackground(background);
            }

            menuItem.setForeground(tabPane.getForegroundAt(i));
            // for <rdar://problem/3520267> make sure to disable items that are disabled in the tab.
            if (!tabPane.isEnabledAt(i)) menuItem.setEnabled(false);

            final int fOffset = i;
            menuItem.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent ae) {
                    boolean visible = isTabVisible(fOffset);
                    tabPane.setSelectedIndex(fOffset);
                    if (!visible) {
                        popupSelectionChanged = true;
                        tabPane.invalidate();
                        tabPane.repaint();
                    }
                }
            });

            return menuItem;
        }
    }

    protected class AquaTruncatingTabbedPaneLayout extends AquaTabbedPaneCopyFromBasicUI.TabbedPaneLayout {
        // fix for Radar #3346131
        protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
            // Our superclass wants to stack tabs, but we rotate them,
            // so when tabs are on the left or right we know that
            // our width is actually the "height" of a tab which is then
            // rotated.
            if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) {
                return super.preferredTabAreaHeight(tabPlacement, height);
            }

            return super.preferredTabAreaWidth(tabPlacement, height);
        }

        protected int preferredTabAreaHeight(final int tabPlacement, final int width) {
            if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) {
                return super.preferredTabAreaWidth(tabPlacement, width);
            }

            return super.preferredTabAreaHeight(tabPlacement, width);
        }

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

            superCalculateTabRects(tabPlacement, tabCount); // does most of the hard work

            // If they haven't been padded (which they only do when there are multiple rows) we should center them
            if (rects.length <= 0) return;

            visibleTabState.alignRectsRunFor(rects, tabPane.getSize(), tabPlacement, AquaUtils.isLeftToRight(tabPane));
        }

        protected void padTabRun(final int tabPlacement, final int start, final int end, final int max) {
            if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) {
                super.padTabRun(tabPlacement, start, end, max);
                return;
            }

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

        /**
         * This is a massive routine and I left it like this because the bulk of the code comes
         * from the BasicTabbedPaneUI class. Here is what it does:
         * 1. Calculate rects for the tabs - we have to play tricks here because our right and left tabs
         *    should get widths calculated the same way as top and bottom, but they will be rotated so the
         *    calculated width is stored as the rect height.
         * 2. Decide if we can fit all the tabs.
         * 3. When we cannot fit all the tabs we create a tab popup, and then layout the new tabs until
         *    we can't fit them anymore. Laying them out is a matter of adding them into the visible list
         *    and shifting them horizontally to the correct location.
         */
        protected synchronized void superCalculateTabRects(final int tabPlacement, final int tabCount) {
            final Dimension size = tabPane.getSize();
            final Insets insets = tabPane.getInsets();
            final Insets localTabAreaInsets = getTabAreaInsets(tabPlacement);

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

            tabRunOverlay = getTabRunOverlay(tabPlacement);

            runCount = 0;
            selectedRun = 0;

            if (tabCount == 0) return;

            final FontMetrics metrics = getFontMetrics();
            final boolean verticalTabRuns = (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT);
            final int selectedIndex = tabPane.getSelectedIndex();

            // calculate all the widths
            // if they all fit we are done, if not
            // we have to do the dance of figuring out which ones to show.
            visibleTabState.setNeedsScrollers(false);
            for (int i = 0; i < tabCount; i++) {
                final Rectangle rect = rects[i];

                if (verticalTabRuns) {
                    calculateVerticalTabRunRect(rect, metrics, tabPlacement, returnAt, i, x, y);

                    // test if we need to scroll!
                    if (rect.y + rect.height > returnAt) {
                        visibleTabState.setNeedsScrollers(true);
                    }
                } else {
                    calculateHorizontalTabRunRect(rect, metrics, tabPlacement, returnAt, i, x, y);

                    // test if we need to scroll!
                    if (rect.x + rect.width > returnAt) {
                        visibleTabState.setNeedsScrollers(true);
                    }
                }
            }

            visibleTabState.relayoutForScrolling(rects, x, y, returnAt, selectedIndex, verticalTabRuns, tabCount, AquaUtils.isLeftToRight(tabPane));
            // Pad the selected tab so that it appears raised in front

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

        private void calculateHorizontalTabRunRect(final Rectangle rect, final FontMetrics metrics, final int tabPlacement, final int returnAt, final int i, final int x, final int y) {
            // Tabs on TOP or BOTTOM....
            if (i > 0) {
                rect.x = rects[i - 1].x + rects[i - 1].width;
            } else {
                tabRuns[0] = 0;
                runCount = 1;
                maxTabWidth = 0;
                rect.x = x;
            }

            rect.width = calculateTabWidth(tabPlacement, i, metrics);
            maxTabWidth = Math.max(maxTabWidth, rect.width);

            rect.y = y;
            rect.height = maxTabHeight;
        }

        private void calculateVerticalTabRunRect(final Rectangle rect, final FontMetrics metrics, final int tabPlacement, final int returnAt, final int i, final int x, final int y) {
            // Tabs on LEFT or RIGHT...
            if (i > 0) {
                rect.y = rects[i - 1].y + rects[i - 1].height;
            } else {
                tabRuns[0] = 0;
                runCount = 1;
                maxTabHeight = 0;
                rect.y = y;
            }

            rect.height = calculateTabWidth(tabPlacement, i, metrics);
            maxTabHeight = Math.max(maxTabHeight, rect.height);

            rect.x = x;
            rect.width = maxTabWidth;
        }

        protected void layoutTabComponents() {
            final Container tabContainer = getTabContainer();
            if (tabContainer == null) return;

            final int placement = tabPane.getTabPlacement();
            final Rectangle rect = new Rectangle();
            final Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());

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

                getTabBounds(i, rect);
                final Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
                final boolean isSeleceted = i == tabPane.getSelectedIndex();

                if (placement == SwingConstants.TOP || placement == SwingConstants.BOTTOM) {
                    rect.x += insets.left + delta.x + getTabLabelShiftX(placement, i, isSeleceted);
                    rect.y += insets.top + delta.y + getTabLabelShiftY(placement, i, isSeleceted) + 1;
                    rect.width -= insets.left + insets.right;
                    rect.height -= insets.top + insets.bottom - 1;
                } else {
                    rect.x += insets.top + delta.x + getTabLabelShiftY(placement, i, isSeleceted) + (placement == SwingConstants.LEFT ? 2 : 1);
                    rect.y += insets.left + delta.y + getTabLabelShiftX(placement, i, isSeleceted);
                    rect.width -= insets.top + insets.bottom - 1;
                    rect.height -= insets.left + insets.right;
                }

                c.setBounds(rect);
            }
        }
    }
}