1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.apple.laf;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.awt.geom.AffineTransform;
  31 import java.beans.*;
  32 
  33 import javax.swing.*;
  34 import javax.swing.event.*;
  35 import javax.swing.plaf.*;
  36 import javax.swing.text.View;
  37 
  38 import sun.swing.SwingUtilities2;
  39 import apple.laf.*;
  40 import apple.laf.JRSUIConstants.*;
  41 
  42 public class AquaTabbedPaneUI extends AquaTabbedPaneCopyFromBasicUI {
  43     private static final int kSmallTabHeight = 20; // height of a small tab
  44     private static final int kLargeTabHeight = 23; // height of a large tab
  45     private static final int kMaxIconSize = kLargeTabHeight - 7;
  46 
  47     private static final double kNinetyDegrees = (Math.PI / 2.0); // used for rotation
  48 
  49     protected final Insets currentContentDrawingInsets = new Insets(0, 0, 0, 0);
  50     protected final Insets currentContentBorderInsets = new Insets(0, 0, 0, 0);
  51     protected final Insets contentDrawingInsets = new Insets(0, 0, 0, 0);
  52 
  53     protected int pressedTab = -3; // -2 is right scroller, -1 is left scroller
  54     protected boolean popupSelectionChanged;
  55 
  56     protected Boolean isDefaultFocusReceiver = null;
  57     protected boolean hasAvoidedFirstFocus = false;
  58 
  59     // Create PLAF
  60     public static ComponentUI createUI(final JComponent c) {
  61         return new AquaTabbedPaneUI();
  62     }
  63 
  64     protected final AquaTabbedPaneTabState visibleTabState = new AquaTabbedPaneTabState(this);
  65     protected final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getTab());
  66 
  67     public AquaTabbedPaneUI() { }
  68 
  69     protected void installListeners() {
  70         super.installListeners();
  71 
  72         // We're not just a mouseListener, we're a mouseMotionListener
  73         if (mouseListener != null) {
  74             tabPane.addMouseMotionListener((MouseMotionListener)mouseListener);
  75         }
  76     }
  77 
  78     protected void installDefaults() {
  79         super.installDefaults();
  80 
  81         if (tabPane.getFont() instanceof UIResource) {
  82             final Boolean b = (Boolean)UIManager.get("TabbedPane.useSmallLayout");
  83             if (b != null && b == Boolean.TRUE) {
  84                 tabPane.setFont(UIManager.getFont("TabbedPane.smallFont"));
  85                 painter.state.set(Size.SMALL);
  86             }
  87         }
  88 
  89         contentDrawingInsets.set(0, 11, 13, 10);
  90         tabPane.setOpaque(false);
  91     }
  92 
  93     protected void assureRectsCreated(final int tabCount) {
  94         visibleTabState.init(tabCount);
  95         super.assureRectsCreated(tabCount);
  96     }
  97 
  98     protected void uninstallDefaults() {
  99         contentDrawingInsets.set(0, 0, 0, 0);
 100     }
 101 
 102     protected MouseListener createMouseListener() {
 103         return new MouseHandler();
 104     }
 105 
 106     protected FocusListener createFocusListener() {
 107         return new FocusHandler();
 108     }
 109 
 110     protected PropertyChangeListener createPropertyChangeListener() {
 111         return new TabbedPanePropertyChangeHandler();
 112     }
 113 
 114     protected LayoutManager createLayoutManager() {
 115         return new AquaTruncatingTabbedPaneLayout();
 116     }
 117 
 118     protected boolean shouldRepaintSelectedTabOnMouseDown() {
 119         return false;
 120     }
 121 
 122     // Paint Methods
 123     // Cache for performance
 124     final Rectangle fContentRect = new Rectangle();
 125     final Rectangle fIconRect = new Rectangle();
 126     final Rectangle fTextRect = new Rectangle();
 127 
 128     // UI Rendering
 129     public void paint(final Graphics g, final JComponent c) {
 130         painter.state.set(getDirection());
 131 
 132         final int tabPlacement = tabPane.getTabPlacement();
 133         final int selectedIndex = tabPane.getSelectedIndex();
 134         paintContentBorder(g, tabPlacement, selectedIndex);
 135 
 136         // we want to call ensureCurrentLayout, but it's private
 137         ensureCurrentLayout();
 138         final Rectangle clipRect = g.getClipBounds();
 139 
 140         final boolean active = tabPane.isEnabled();
 141         final boolean frameActive = AquaFocusHandler.isActive(tabPane);
 142         final boolean isLeftToRight = tabPane.getComponentOrientation().isLeftToRight() || tabPlacement == LEFT || tabPlacement == RIGHT;
 143 
 144         // Paint tabRuns of tabs from back to front
 145         if (visibleTabState.needsScrollTabs()) {
 146             paintScrollingTabs(g, clipRect, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
 147             return;
 148         }
 149 
 150         // old way
 151         paintAllTabs(g, clipRect, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
 152     }
 153 
 154     protected void paintAllTabs(final Graphics g, final Rectangle clipRect, final int tabPlacement, final int selectedIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) {
 155         boolean drawSelectedLast = false;
 156         for (int i = 0; i < rects.length; i++) {
 157             if (i == selectedIndex) {
 158                 drawSelectedLast = true;
 159             } else {
 160                 if (rects[i].intersects(clipRect)) {
 161                     paintTabNormal(g, tabPlacement, i, active, frameActive, isLeftToRight);
 162                 }
 163             }
 164         }
 165 
 166         // paint the selected tab last.
 167         if (drawSelectedLast && rects[selectedIndex].intersects(clipRect)) {
 168             paintTabNormal(g, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
 169         }
 170     }
 171 
 172     protected void paintScrollingTabs(final Graphics g, final Rectangle clipRect, final int tabPlacement, final int selectedIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) {
 173 //        final Graphics g2 = g.create();
 174 //        g2.setColor(Color.cyan);
 175 //        Rectangle r = new Rectangle();
 176 //        for (int i = 0; i < visibleTabState.getTotal(); i++) {
 177 //            r.add(rects[visibleTabState.getIndex(i)]);
 178 //        }
 179 //        g2.fillRect(r.x, r.y, r.width, r.height);
 180 //        g2.dispose();
 181 //        System.out.println(r);
 182 
 183         // for each visible tab, except the selected one
 184         for (int i = 0; i < visibleTabState.getTotal(); i++) {
 185             final int realIndex = visibleTabState.getIndex(i);
 186             if (realIndex != selectedIndex) {
 187                 if (rects[realIndex].intersects(clipRect)) {
 188                     paintTabNormal(g, tabPlacement, realIndex, active, frameActive, isLeftToRight);
 189                 }
 190             }
 191         }
 192 
 193         final Rectangle leftScrollTabRect = visibleTabState.getLeftScrollTabRect();
 194         if (visibleTabState.needsLeftScrollTab() && leftScrollTabRect.intersects(clipRect)) {
 195             paintTabNormalFromRect(g, tabPlacement, leftScrollTabRect, -2, fIconRect, fTextRect, visibleTabState.needsLeftScrollTab(), frameActive, isLeftToRight);
 196         }
 197 
 198         final Rectangle rightScrollTabRect = visibleTabState.getRightScrollTabRect();
 199         if (visibleTabState.needsRightScrollTab() && rightScrollTabRect.intersects(clipRect)) {
 200             paintTabNormalFromRect(g, tabPlacement, rightScrollTabRect, -1, fIconRect, fTextRect, visibleTabState.needsRightScrollTab(), frameActive, isLeftToRight);
 201         }
 202 
 203         if (selectedIndex >= 0) { // && rects[selectedIndex].intersects(clipRect)) {
 204             paintTabNormal(g, tabPlacement, selectedIndex, active, frameActive, isLeftToRight);
 205         }
 206     }
 207 
 208     private static boolean isScrollTabIndex(final int index) {
 209         return index == -1 || index == -2;
 210     }
 211 
 212     protected static void transposeRect(final Rectangle r) {
 213         int temp = r.y;
 214         r.y = r.x;
 215         r.x = temp;
 216         temp = r.width;
 217         r.width = r.height;
 218         r.height = temp;
 219     }
 220 
 221     protected int getTabLabelShiftX(final int tabPlacement, final int tabIndex, final boolean isSelected) {
 222         final Rectangle tabRect = (tabIndex >= 0 ? rects[tabIndex] : visibleTabState.getRightScrollTabRect());
 223         int nudge = 0;
 224         switch (tabPlacement) {
 225             case LEFT:
 226             case RIGHT:
 227                 nudge = tabRect.height % 2;
 228                 break;
 229             case BOTTOM:
 230             case TOP:
 231             default:
 232                 nudge = tabRect.width % 2;
 233         }
 234         return nudge;
 235     }
 236 
 237     protected int getTabLabelShiftY(final int tabPlacement, final int tabIndex, final boolean isSelected) {
 238         switch (tabPlacement) {
 239             case RIGHT:
 240             case LEFT:
 241             case BOTTOM:
 242                 return -1;
 243             case TOP:
 244             default:
 245                 return 0;
 246         }
 247     }
 248 
 249     protected Icon getIconForScrollTab(final int tabPlacement, final int tabIndex, final boolean enabled) {
 250         boolean shouldFlip = !AquaUtils.isLeftToRight(tabPane);
 251         if (tabPlacement == RIGHT) shouldFlip = false;
 252         if (tabPlacement == LEFT) shouldFlip = true;
 253 
 254         int direction = tabIndex == -1 ? EAST : WEST;
 255         if (shouldFlip) {
 256             if (direction == EAST) {
 257                 direction = WEST;
 258             } else if (direction == WEST) {
 259                 direction = EAST;
 260             }
 261         }
 262 
 263         if (enabled) return AquaImageFactory.getArrowIconForDirection(direction);
 264 
 265         final Image icon = AquaImageFactory.getArrowImageForDirection(direction);
 266         return new ImageIcon(AquaUtils.generateDisabledImage(icon));
 267     }
 268 
 269     protected void paintContents(final Graphics g, final int tabPlacement, final int tabIndex, final Rectangle tabRect, final Rectangle iconRect, final Rectangle textRect, final boolean isSelected) {
 270         final Shape temp = g.getClip();
 271         g.clipRect(fContentRect.x, fContentRect.y, fContentRect.width, fContentRect.height);
 272 
 273         final Component component;
 274         final String title;
 275         final Icon icon;
 276         if (isScrollTabIndex(tabIndex)) {
 277             component = null;
 278             title = null;
 279             icon = getIconForScrollTab(tabPlacement, tabIndex, true);
 280         } else {
 281             component = getTabComponentAt(tabIndex);
 282             if (component == null) {
 283                 title = tabPane.getTitleAt(tabIndex);
 284                 icon = getIconForTab(tabIndex);
 285             } else {
 286                 title = null;
 287                 icon = null;
 288             }
 289         }
 290 
 291         final boolean isVertical = tabPlacement == RIGHT || tabPlacement == LEFT;
 292         if (isVertical) {
 293             transposeRect(fContentRect);
 294         }
 295 
 296         final Font font = tabPane.getFont();
 297         final FontMetrics metrics = g.getFontMetrics(font);
 298 
 299         // our scrolling tabs
 300         layoutLabel(tabPlacement, metrics, tabIndex < 0 ? 0 : tabIndex, title, icon, fContentRect, iconRect, textRect, false); // Never give it "isSelected" - ApprMgr handles this
 301         if (isVertical) {
 302             transposeRect(fContentRect);
 303             transposeRect(iconRect);
 304             transposeRect(textRect);
 305         }
 306 
 307         // from super.paintText - its normal text painting is totally wrong for the Mac
 308         if (!(g instanceof Graphics2D)) {
 309             g.setClip(temp);
 310             return;
 311         }
 312         final Graphics2D g2d = (Graphics2D) g;
 313 
 314         AffineTransform savedAT = null;
 315         if (isVertical) {
 316             savedAT = g2d.getTransform();
 317             rotateGraphics(g2d, tabRect, textRect, iconRect, tabPlacement);
 318         }
 319 
 320         // not for the scrolling tabs
 321         if (component == null && tabIndex >= 0) {
 322             paintTitle(g2d, font, metrics, textRect, tabIndex, title);
 323         }
 324 
 325         if (icon != null) {
 326             paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
 327         }
 328 
 329         if (savedAT != null) {
 330             g2d.setTransform(savedAT);
 331         }
 332 
 333         g.setClip(temp);
 334     }
 335 
 336     protected void paintTitle(final Graphics2D g2d, final Font font, final FontMetrics metrics, final Rectangle textRect, final int tabIndex, final String title) {
 337         final View v = getTextViewForTab(tabIndex);
 338         if (v != null) {
 339             v.paint(g2d, textRect);
 340             return;
 341         }
 342 
 343         if (title == null) return;
 344 
 345         final Color color = tabPane.getForegroundAt(tabIndex);
 346         if (color instanceof UIResource) {
 347             // sja fix getTheme().setThemeTextColor(g, isSelected, isPressed && tracking, tabPane.isEnabledAt(tabIndex));
 348             if (tabPane.isEnabledAt(tabIndex)) {
 349                 g2d.setColor(Color.black);
 350             } else {
 351                 g2d.setColor(Color.gray);
 352             }
 353         } else {
 354             g2d.setColor(color);
 355         }
 356 
 357         g2d.setFont(font);
 358         SwingUtilities2.drawString(tabPane, g2d, title, textRect.x, textRect.y + metrics.getAscent());
 359     }
 360 
 361     protected void rotateGraphics(final Graphics2D g2d, final Rectangle tabRect, final Rectangle textRect, final Rectangle iconRect, final int tabPlacement) {
 362         int yDiff = 0; // textRect.y - tabRect.y;
 363         int xDiff = 0; // (tabRect.x+tabRect.width) - (textRect.x+textRect.width);
 364         int yIconDiff = 0; // iconRect.y - tabRect.y;
 365         int xIconDiff = 0; // (tabRect.x+tabRect.width) - (iconRect.x + iconRect.width);
 366 
 367         final double rotateAmount = (tabPlacement == LEFT ? -kNinetyDegrees : kNinetyDegrees);
 368         g2d.transform(AffineTransform.getRotateInstance(rotateAmount, tabRect.x, tabRect.y));
 369 
 370         // x and y diffs are named weirdly.
 371         // I will rename them, but what they mean now is
 372         // original x offset which will be used to adjust the y coordinate for the
 373         // rotated context
 374         if (tabPlacement == LEFT) {
 375             g2d.translate(-tabRect.height - 1, 1);
 376             xDiff = textRect.x - tabRect.x;
 377             yDiff = tabRect.height + tabRect.y - (textRect.y + textRect.height);
 378             xIconDiff = iconRect.x - tabRect.x;
 379             yIconDiff = tabRect.height + tabRect.y - (iconRect.y + iconRect.height);
 380         } else {
 381             g2d.translate(0, -tabRect.width - 1);
 382             yDiff = textRect.y - tabRect.y;
 383             xDiff = (tabRect.x + tabRect.width) - (textRect.x + textRect.width);
 384             yIconDiff = iconRect.y - tabRect.y;
 385             xIconDiff = (tabRect.x + tabRect.width) - (iconRect.x + iconRect.width);
 386         }
 387 
 388         // rotation changes needed for the rendering
 389         // we are rotating so we can't just use the rects wholesale.
 390         textRect.x = tabRect.x + yDiff;
 391         textRect.y = tabRect.y + xDiff;
 392 
 393         int tempVal = textRect.height;
 394         textRect.height = textRect.width;
 395         textRect.width = tempVal;
 396     // g.setColor(Color.red);
 397     // g.drawLine(textRect.x, textRect.y, textRect.x+textRect.height, textRect.y+textRect.width);
 398     // g.drawLine(textRect.x+textRect.height, textRect.y, textRect.x, textRect.y+textRect.width);
 399 
 400         iconRect.x = tabRect.x + yIconDiff;
 401         iconRect.y = tabRect.y + xIconDiff;
 402 
 403         tempVal = iconRect.height;
 404         iconRect.height = iconRect.width;
 405         iconRect.width = tempVal;
 406     }
 407 
 408     protected void paintTabNormal(final Graphics g, final int tabPlacement, final int tabIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) {
 409         paintTabNormalFromRect(g, tabPlacement, rects[tabIndex], tabIndex, fIconRect, fTextRect, active, frameActive, isLeftToRight);
 410     }
 411 
 412     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) {
 413         final int selectedIndex = tabPane.getSelectedIndex();
 414         final boolean isSelected = selectedIndex == nonRectIndex;
 415 
 416         paintCUITab(g, tabPlacement, tabRect, isSelected, frameActive, isLeftToRight, nonRectIndex);
 417 
 418         textRect.setBounds(tabRect);
 419         fContentRect.setBounds(tabRect);
 420         paintContents(g, tabPlacement, nonRectIndex, tabRect, iconRect, textRect, isSelected);
 421     }
 422 
 423     protected void paintCUITab(final Graphics g, final int tabPlacement, final Rectangle tabRect, final boolean isSelected, final boolean frameActive, final boolean isLeftToRight, final int nonRectIndex) {
 424         final int tabCount = tabPane.getTabCount();
 425 
 426         final boolean needsLeftScrollTab = visibleTabState.needsLeftScrollTab();
 427         final boolean needsRightScrollTab = visibleTabState.needsRightScrollTab();
 428 
 429         // first or last
 430         boolean first = nonRectIndex == 0;
 431         boolean last = nonRectIndex == tabCount - 1;
 432         if (needsLeftScrollTab || needsRightScrollTab) {
 433             if (nonRectIndex == -1) {
 434                 first = false;
 435                 last = true;
 436             } else if (nonRectIndex == -2) {
 437                 first = true;
 438                 last = false;
 439             } else {
 440                 if (needsLeftScrollTab) first = false;
 441                 if (needsRightScrollTab) last = false;
 442             }
 443         }
 444 
 445         if (tabPlacement == LEFT || tabPlacement == RIGHT) {
 446             final boolean tempSwap = last;
 447             last = first;
 448             first = tempSwap;
 449         }
 450 
 451         final State state = getState(nonRectIndex, frameActive, isSelected);
 452         painter.state.set(state);
 453         painter.state.set(isSelected || (state == State.INACTIVE && frameActive) ? BooleanValue.YES : BooleanValue.NO);
 454         painter.state.set(getSegmentPosition(first, last, isLeftToRight));
 455         final int selectedIndex = tabPane.getSelectedIndex();
 456         painter.state.set(getSegmentTrailingSeparator(nonRectIndex, selectedIndex, isLeftToRight));
 457         painter.state.set(getSegmentLeadingSeparator(nonRectIndex, selectedIndex, isLeftToRight));
 458         painter.state.set(tabPane.hasFocus() && isSelected ? Focused.YES : Focused.NO);
 459         painter.paint(g, tabPane, tabRect.x, tabRect.y, tabRect.width, tabRect.height);
 460 
 461         if (isScrollTabIndex(nonRectIndex)) return;
 462 
 463         final Color color = tabPane.getBackgroundAt(nonRectIndex);
 464         if (color == null || (color instanceof UIResource)) return;
 465 
 466         if (!isLeftToRight && (tabPlacement == TOP || tabPlacement == BOTTOM)) {
 467             final boolean tempSwap = last;
 468             last = first;
 469             first = tempSwap;
 470         }
 471 
 472         fillTabWithBackground(g, tabRect, tabPlacement, first, last, color);
 473     }
 474 
 475     protected Direction getDirection() {
 476         switch (tabPane.getTabPlacement()) {
 477             case SwingConstants.BOTTOM: return Direction.SOUTH;
 478             case SwingConstants.LEFT: return Direction.WEST;
 479             case SwingConstants.RIGHT: return Direction.EAST;
 480         }
 481         return Direction.NORTH;
 482     }
 483 
 484     protected static SegmentPosition getSegmentPosition(final boolean first, final boolean last, final boolean isLeftToRight) {
 485         if (first && last) return SegmentPosition.ONLY;
 486         if (first) return isLeftToRight ? SegmentPosition.FIRST : SegmentPosition.LAST;
 487         if (last) return isLeftToRight ? SegmentPosition.LAST : SegmentPosition.FIRST;
 488         return SegmentPosition.MIDDLE;
 489     }
 490 
 491     protected SegmentTrailingSeparator getSegmentTrailingSeparator(final int index, final int selectedIndex, final boolean isLeftToRight) {
 492         return SegmentTrailingSeparator.YES;
 493     }
 494 
 495     protected SegmentLeadingSeparator getSegmentLeadingSeparator(final int index, final int selectedIndex, final boolean isLeftToRight) {
 496         return SegmentLeadingSeparator.NO;
 497     }
 498 
 499     protected boolean isTabBeforeSelectedTab(final int index, final int selectedIndex, final boolean isLeftToRight) {
 500         if (index == -2 && visibleTabState.getIndex(0) == selectedIndex) return true;
 501         int indexBeforeSelectedIndex = isLeftToRight ? selectedIndex - 1 : selectedIndex + 1;
 502         return index == indexBeforeSelectedIndex ? true : false;
 503     }
 504 
 505     protected State getState(final int index, final boolean frameActive, final boolean isSelected) {
 506         if (!frameActive) return State.INACTIVE;
 507         if (!tabPane.isEnabled()) return State.DISABLED;
 508         if (JRSUIUtils.TabbedPane.useLegacyTabs()) {
 509             if (isSelected) return State.PRESSED;
 510             if (pressedTab == index) return State.INACTIVE;
 511         } else {
 512             if (isSelected) return State.ACTIVE;
 513             if (pressedTab == index) return State.PRESSED;
 514         }
 515         return State.ACTIVE;
 516     }
 517 
 518     /**
 519      * This routine adjusts the background fill rect so it just fits inside a tab, allowing for
 520      * whether we're talking about a first tab or last tab.  NOTE that this code is very much
 521      * Aqua 2 dependent!
 522      */
 523     static class AlterRects {
 524         Rectangle standard, first, last;
 525         AlterRects(final int x, final int y, final int w, final int h) { standard = new Rectangle(x, y, w, h); }
 526         AlterRects start(final int x, final int y, final int w, final int h) { first = new Rectangle(x, y, w, h); return this; }
 527         AlterRects end(final int x, final int y, final int w, final int h) { last = new Rectangle(x, y, w, h); return this; }
 528 
 529         static Rectangle alter(final Rectangle r, final Rectangle o) {
 530             // r = new Rectangle(r);
 531             r.x += o.x;
 532             r.y += o.y;
 533             r.width += o.width;
 534             r.height += o.height;
 535             return r;
 536         }
 537     }
 538 
 539     static AlterRects[] alterRects = new AlterRects[5];
 540 
 541     protected static AlterRects getAlterationFor(final int tabPlacement) {
 542         if (alterRects[tabPlacement] != null) return alterRects[tabPlacement];
 543 
 544         switch (tabPlacement) {
 545             case LEFT: return alterRects[LEFT] = new AlterRects(2, 0, -4, 1).start(0, 0, 0, -4).end(0, 3, 0, -3);
 546             case RIGHT: return alterRects[RIGHT] = new AlterRects(1, 0, -4, 1).start(0, 0, 0, -4).end(0, 3, 0, -3);
 547             case BOTTOM: return alterRects[BOTTOM] = new AlterRects(0, 1, 0, -4).start(3, 0, -3, 0).end(0, 0, -3, 0);
 548             case TOP:
 549             default: return alterRects[TOP] = new AlterRects(0, 2, 0, -4).start(3, 0, -3, 0).end(0, 0, -3, 0);
 550         }
 551     }
 552 
 553     protected void fillTabWithBackground(final Graphics g, final Rectangle rect, final int tabPlacement, final boolean first, final boolean last, final Color color) {
 554         final Rectangle fillRect = new Rectangle(rect);
 555 
 556         final AlterRects alteration = getAlterationFor(tabPlacement);
 557         AlterRects.alter(fillRect, alteration.standard);
 558         if (first) AlterRects.alter(fillRect, alteration.first);
 559         if (last) AlterRects.alter(fillRect, alteration.last);
 560 
 561         g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), (int)(color.getAlpha() * 0.25)));
 562         g.fillRoundRect(fillRect.x, fillRect.y, fillRect.width, fillRect.height, 3, 1);
 563     }
 564 
 565     protected Insets getContentBorderInsets(final int tabPlacement) {
 566         final Insets draw = getContentDrawingInsets(tabPlacement); // will be rotated
 567 
 568         rotateInsets(contentBorderInsets, currentContentBorderInsets, tabPlacement);
 569 
 570         currentContentBorderInsets.left += draw.left;
 571         currentContentBorderInsets.right += draw.right;
 572         currentContentBorderInsets.top += draw.top;
 573         currentContentBorderInsets.bottom += draw.bottom;
 574 
 575         return currentContentBorderInsets;
 576     }
 577 
 578     protected static void rotateInsets(final Insets topInsets, final Insets targetInsets, final int targetPlacement) {
 579         switch (targetPlacement) {
 580             case LEFT:
 581                 targetInsets.top = topInsets.left;
 582                 targetInsets.left = topInsets.top;
 583                 targetInsets.bottom = topInsets.right;
 584                 targetInsets.right = topInsets.bottom;
 585                 break;
 586             case BOTTOM:
 587                 targetInsets.top = topInsets.bottom;
 588                 targetInsets.left = topInsets.left;
 589                 targetInsets.bottom = topInsets.top;
 590                 targetInsets.right = topInsets.right;
 591                 break;
 592             case RIGHT:
 593                 targetInsets.top = topInsets.right;
 594                 targetInsets.left = topInsets.bottom;
 595                 targetInsets.bottom = topInsets.left;
 596                 targetInsets.right = topInsets.top;
 597                 break;
 598             case TOP:
 599             default:
 600                 targetInsets.top = topInsets.top;
 601                 targetInsets.left = topInsets.left;
 602                 targetInsets.bottom = topInsets.bottom;
 603                 targetInsets.right = topInsets.right;
 604         }
 605     }
 606 
 607     protected Insets getContentDrawingInsets(final int tabPlacement) {
 608         rotateInsets(contentDrawingInsets, currentContentDrawingInsets, tabPlacement);
 609         return currentContentDrawingInsets;
 610     }
 611 
 612     protected Icon getIconForTab(final int tabIndex) {
 613         final Icon mainIcon = super.getIconForTab(tabIndex);
 614         if (mainIcon == null) return null;
 615 
 616         final int iconHeight = mainIcon.getIconHeight();
 617         if (iconHeight <= kMaxIconSize) return mainIcon;
 618         final float ratio = (float)kMaxIconSize / (float)iconHeight;
 619 
 620         final int iconWidth = mainIcon.getIconWidth();
 621         return new AquaIcon.CachingScalingIcon((int)(iconWidth * ratio), kMaxIconSize) {
 622             Image createImage() {
 623                 return AquaIcon.getImageForIcon(mainIcon);
 624             }
 625         };
 626     }
 627 
 628     private static final int TAB_BORDER_INSET = 9;
 629     protected void paintContentBorder(final Graphics g, final int tabPlacement, final int selectedIndex) {
 630         final int width = tabPane.getWidth();
 631         final int height = tabPane.getHeight();
 632         final Insets insets = tabPane.getInsets();
 633 
 634         int x = insets.left;
 635         int y = insets.top;
 636         int w = width - insets.right - insets.left;
 637         int h = height - insets.top - insets.bottom;
 638 
 639         switch (tabPlacement) {
 640             case TOP:
 641                 y += TAB_BORDER_INSET;
 642                 h -= TAB_BORDER_INSET;
 643                 break;
 644             case BOTTOM:
 645                 h -= TAB_BORDER_INSET;// - 2;
 646                 break;
 647             case LEFT:
 648                 x += TAB_BORDER_INSET;// - 5;
 649                 w -= TAB_BORDER_INSET;// + 1;
 650                 break;
 651             case RIGHT:
 652                 w -= TAB_BORDER_INSET;// + 1;
 653                 break;
 654         }
 655 
 656         if (tabPane.isOpaque()) {
 657             g.setColor(tabPane.getBackground());
 658             g.fillRect(0, 0, width, height);
 659         }
 660 
 661         AquaGroupBorder.getTabbedPaneGroupBorder().paintBorder(tabPane, g, x, y, w, h);
 662     }
 663 
 664     // see paintContentBorder
 665     protected void repaintContentBorderEdge() {
 666         final int width = tabPane.getWidth();
 667         final int height = tabPane.getHeight();
 668         final Insets insets = tabPane.getInsets();
 669         final int tabPlacement = tabPane.getTabPlacement();
 670         final Insets localContentBorderInsets = getContentBorderInsets(tabPlacement);
 671 
 672         int x = insets.left;
 673         int y = insets.top;
 674         int w = width - insets.right - insets.left;
 675         int h = height - insets.top - insets.bottom;
 676 
 677         switch (tabPlacement) {
 678             case LEFT:
 679                 x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
 680                 w = localContentBorderInsets.left;
 681                 break;
 682             case RIGHT:
 683                 w = localContentBorderInsets.right;
 684                 break;
 685             case BOTTOM:
 686                 h = localContentBorderInsets.bottom;
 687                 break;
 688             case TOP:
 689             default:
 690                 y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
 691                 h = localContentBorderInsets.top;
 692         }
 693         tabPane.repaint(x, y, w, h);
 694     }
 695 
 696     public boolean isTabVisible(final int index) {
 697         if (index == -1 || index == -2) return true;
 698         for (int i = 0; i < visibleTabState.getTotal(); i++) {
 699             if (visibleTabState.getIndex(i) == index) return true;
 700         }
 701         return false;
 702     }
 703 
 704     /**
 705      * Returns the bounds of the specified tab index.  The bounds are
 706      * with respect to the JTabbedPane's coordinate space.  If the tab at this
 707      * index is not currently visible in the UI, then returns null.
 708      */
 709     @Override
 710     public Rectangle getTabBounds(final JTabbedPane pane, final int i) {
 711         if (visibleTabState.needsScrollTabs()
 712                 && (visibleTabState.isBefore(i) || visibleTabState.isAfter(i))) {
 713             return null;
 714         }
 715         return super.getTabBounds(pane, i);
 716     }
 717 
 718     /**
 719      * Returns the tab index which intersects the specified point
 720      * in the JTabbedPane's coordinate space.
 721      */
 722     public int tabForCoordinate(final JTabbedPane pane, final int x, final int y) {
 723         ensureCurrentLayout();
 724         final Point p = new Point(x, y);
 725         if (visibleTabState.needsScrollTabs()) {
 726             for (int i = 0; i < visibleTabState.getTotal(); i++) {
 727                 final int realOffset = visibleTabState.getIndex(i);
 728                 if (rects[realOffset].contains(p.x, p.y)) return realOffset;
 729             }
 730             if (visibleTabState.getRightScrollTabRect().contains(p.x, p.y)) return -1; //tabPane.getTabCount();
 731         } else {
 732             //old way
 733             final int tabCount = tabPane.getTabCount();
 734             for (int i = 0; i < tabCount; i++) {
 735                 if (rects[i].contains(p.x, p.y)) return i;
 736             }
 737         }
 738         return -1;
 739     }
 740 
 741     protected Insets getTabInsets(final int tabPlacement, final int tabIndex) {
 742         switch (tabPlacement) {
 743             case LEFT: return UIManager.getInsets("TabbedPane.leftTabInsets");
 744             case RIGHT: return UIManager.getInsets("TabbedPane.rightTabInsets");
 745         }
 746         return tabInsets;
 747     }
 748 
 749     // This is the preferred size - the layout manager will ignore if it has to
 750     protected int calculateTabHeight(final int tabPlacement, final int tabIndex, final int fontHeight) {
 751         // Constrain to what the Mac allows
 752         final int result = super.calculateTabHeight(tabPlacement, tabIndex, fontHeight);
 753 
 754         // force tabs to have a max height for aqua
 755         if (result <= kSmallTabHeight) return kSmallTabHeight;
 756         return kLargeTabHeight;
 757     }
 758 
 759     // JBuilder requested this - it's against HI, but then so are multiple rows
 760     protected boolean shouldRotateTabRuns(final int tabPlacement) {
 761         return false;
 762     }
 763 
 764     protected class TabbedPanePropertyChangeHandler extends PropertyChangeHandler {
 765         public void propertyChange(final PropertyChangeEvent e) {
 766             final String prop = e.getPropertyName();
 767 
 768             if (!AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) {
 769                 super.propertyChange(e);
 770                 return;
 771             }
 772 
 773             final JTabbedPane comp = (JTabbedPane)e.getSource();
 774             comp.repaint();
 775 
 776             // Repaint the "front" tab and the border
 777             final int selected = tabPane.getSelectedIndex();
 778             final Rectangle[] theRects = rects;
 779             if (selected >= 0 && selected < theRects.length) comp.repaint(theRects[selected]);
 780             repaintContentBorderEdge();
 781         }
 782     }
 783 
 784     protected ChangeListener createChangeListener() {
 785         return new ChangeListener() {
 786             public void stateChanged(final ChangeEvent e) {
 787                 if (!isTabVisible(tabPane.getSelectedIndex())) popupSelectionChanged = true;
 788                 tabPane.revalidate();
 789                 tabPane.repaint();
 790             }
 791         };
 792     }
 793 
 794     protected class FocusHandler extends FocusAdapter {
 795         Rectangle sWorkingRect = new Rectangle();
 796 
 797         public void focusGained(final FocusEvent e) {
 798             if (isDefaultFocusReceiver(tabPane) && !hasAvoidedFirstFocus) {
 799                 KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
 800                 hasAvoidedFirstFocus = true;
 801             }
 802             adjustPaintingRectForFocusRing(e);
 803         }
 804 
 805         public void focusLost(final FocusEvent e) {
 806             adjustPaintingRectForFocusRing(e);
 807         }
 808 
 809         void adjustPaintingRectForFocusRing(final FocusEvent e) {
 810             final JTabbedPane pane = (JTabbedPane)e.getSource();
 811             final int tabCount = pane.getTabCount();
 812             final int selectedIndex = pane.getSelectedIndex();
 813 
 814             if (selectedIndex != -1 && tabCount > 0 && tabCount == rects.length) {
 815                 sWorkingRect.setBounds(rects[selectedIndex]);
 816                 sWorkingRect.grow(4, 4);
 817                 pane.repaint(sWorkingRect);
 818             }
 819         }
 820 
 821         boolean isDefaultFocusReceiver(final JComponent component) {
 822             if (isDefaultFocusReceiver == null) {
 823                 Component defaultFocusReceiver = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy().getDefaultComponent(getTopLevelFocusCycleRootAncestor(component));
 824                 isDefaultFocusReceiver = new Boolean(defaultFocusReceiver != null && defaultFocusReceiver.equals(component));
 825             }
 826             return isDefaultFocusReceiver.booleanValue();
 827         }
 828 
 829         Container getTopLevelFocusCycleRootAncestor(Container container) {
 830             Container ancestor;
 831             while ((ancestor = container.getFocusCycleRootAncestor()) != null) {
 832                 container = ancestor;
 833             }
 834             return container;
 835         }
 836     }
 837 
 838     public class MouseHandler extends MouseInputAdapter implements ActionListener {
 839         protected int trackingTab = -3;
 840         protected Timer popupTimer = new Timer(500, this);
 841 
 842         public MouseHandler() {
 843             popupTimer.setRepeats(false);
 844         }
 845 
 846         public void mousePressed(final MouseEvent e) {
 847             final JTabbedPane pane = (JTabbedPane)e.getSource();
 848             if (!pane.isEnabled()) {
 849                 trackingTab = -3;
 850                 return;
 851             }
 852 
 853             final Point p = e.getPoint();
 854             trackingTab = getCurrentTab(pane, p);
 855             if (trackingTab == -3 || (!shouldRepaintSelectedTabOnMouseDown() && trackingTab == pane.getSelectedIndex())) {
 856                 trackingTab = -3;
 857                 return;
 858             }
 859 
 860             if (trackingTab < 0 && trackingTab > -3) {
 861                 popupTimer.start();
 862             }
 863 
 864             pressedTab = trackingTab;
 865             repaint(pane, pressedTab);
 866         }
 867 
 868         public void mouseDragged(final MouseEvent e) {
 869             if (trackingTab < -2) return;
 870 
 871             final JTabbedPane pane = (JTabbedPane)e.getSource();
 872             final int currentTab = getCurrentTab(pane, e.getPoint());
 873 
 874             if (currentTab != trackingTab) {
 875                 pressedTab = -3;
 876             } else {
 877                 pressedTab = trackingTab;
 878             }
 879 
 880             if (trackingTab < 0 && trackingTab > -3) {
 881                 popupTimer.start();
 882             }
 883 
 884             repaint(pane, trackingTab);
 885         }
 886 
 887         public void mouseReleased(final MouseEvent e) {
 888             if (trackingTab < -2) return;
 889 
 890             popupTimer.stop();
 891 
 892             final JTabbedPane pane = (JTabbedPane)e.getSource();
 893             final Point p = e.getPoint();
 894             final int currentTab = getCurrentTab(pane, p);
 895 
 896             if (trackingTab == -1 && currentTab == -1) {
 897                 pane.setSelectedIndex(pane.getSelectedIndex() + 1);
 898             }
 899 
 900             if (trackingTab == -2 && currentTab == -2) {
 901                 pane.setSelectedIndex(pane.getSelectedIndex() - 1);
 902             }
 903 
 904             if (trackingTab >= 0 && currentTab == trackingTab) {
 905                 pane.setSelectedIndex(trackingTab);
 906             }
 907 
 908             repaint(pane, trackingTab);
 909 
 910             pressedTab = -3;
 911             trackingTab = -3;
 912         }
 913 
 914         public void actionPerformed(final ActionEvent e) {
 915             if (trackingTab != pressedTab) {
 916                 return;
 917             }
 918 
 919             if (trackingTab == -1) {
 920                 showFullPopup(false);
 921                 trackingTab = -3;
 922             }
 923 
 924             if (trackingTab == -2) {
 925                 showFullPopup(true);
 926                 trackingTab = -3;
 927             }
 928         }
 929 
 930         int getCurrentTab(final JTabbedPane pane, final Point p) {
 931             final int tabIndex = tabForCoordinate(pane, p.x, p.y);
 932             if (tabIndex >= 0 && pane.isEnabledAt(tabIndex)) return tabIndex;
 933 
 934             if (visibleTabState.needsLeftScrollTab() && visibleTabState.getLeftScrollTabRect().contains(p)) return -2;
 935             if (visibleTabState.needsRightScrollTab() && visibleTabState.getRightScrollTabRect().contains(p)) return -1;
 936 
 937             return -3;
 938         }
 939 
 940         void repaint(final JTabbedPane pane, final int tab) {
 941             switch (tab) {
 942                 case -1:
 943                     pane.repaint(visibleTabState.getRightScrollTabRect());
 944                     return;
 945                 case -2:
 946                     pane.repaint(visibleTabState.getLeftScrollTabRect());
 947                     return;
 948                 default:
 949                     if (trackingTab >= 0) pane.repaint(rects[trackingTab]);
 950                     return;
 951             }
 952         }
 953 
 954         void showFullPopup(final boolean firstTab) {
 955             final JPopupMenu popup = new JPopupMenu();
 956 
 957             for (int i = 0; i < tabPane.getTabCount(); i++) {
 958                 if (firstTab ? visibleTabState.isBefore(i) : visibleTabState.isAfter(i)) {
 959                     popup.add(createMenuItem(i));
 960                 }
 961             }
 962 
 963             if (firstTab) {
 964                 final Rectangle leftScrollTabRect = visibleTabState.getLeftScrollTabRect();
 965                 final Dimension popupRect = popup.getPreferredSize();
 966                 popup.show(tabPane, leftScrollTabRect.x - popupRect.width, leftScrollTabRect.y + 7);
 967             } else {
 968                 final Rectangle rightScrollTabRect = visibleTabState.getRightScrollTabRect();
 969                 popup.show(tabPane, rightScrollTabRect.x + rightScrollTabRect.width, rightScrollTabRect.y + 7);
 970             }
 971 
 972             popup.addPopupMenuListener(new PopupMenuListener() {
 973                 public void popupMenuCanceled(final PopupMenuEvent e) { }
 974                 public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { }
 975 
 976                 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
 977                     pressedTab = -3;
 978                     tabPane.repaint(visibleTabState.getLeftScrollTabRect());
 979                     tabPane.repaint(visibleTabState.getRightScrollTabRect());
 980                 }
 981             });
 982         }
 983 
 984         JMenuItem createMenuItem(final int i) {
 985             final Component component = getTabComponentAt(i);
 986             final JMenuItem menuItem;
 987             if (component == null) {
 988                 menuItem = new JMenuItem(tabPane.getTitleAt(i), tabPane.getIconAt(i));
 989             } else {
 990                 @SuppressWarnings("serial") // anonymous class
 991                 JMenuItem tmp = new JMenuItem() {
 992                     public void paintComponent(final Graphics g) {
 993                         super.paintComponent(g);
 994                         final Dimension size = component.getSize();
 995                         component.setSize(getSize());
 996                         component.validate();
 997                         component.paint(g);
 998                         component.setSize(size);
 999                     }
1000 
1001                     public Dimension getPreferredSize() {
1002                         return component.getPreferredSize();
1003                     }
1004                 };
1005                 menuItem = tmp;
1006             }
1007 
1008             final Color background = tabPane.getBackgroundAt(i);
1009             if (!(background instanceof UIResource)) {
1010                 menuItem.setBackground(background);
1011             }
1012 
1013             menuItem.setForeground(tabPane.getForegroundAt(i));
1014             // for <rdar://problem/3520267> make sure to disable items that are disabled in the tab.
1015             if (!tabPane.isEnabledAt(i)) menuItem.setEnabled(false);
1016 
1017             final int fOffset = i;
1018             menuItem.addActionListener(new ActionListener() {
1019                 public void actionPerformed(final ActionEvent ae) {
1020                     boolean visible = isTabVisible(fOffset);
1021                     tabPane.setSelectedIndex(fOffset);
1022                     if (!visible) {
1023                         popupSelectionChanged = true;
1024                         tabPane.invalidate();
1025                         tabPane.repaint();
1026                     }
1027                 }
1028             });
1029 
1030             return menuItem;
1031         }
1032     }
1033 
1034     protected class AquaTruncatingTabbedPaneLayout extends AquaTabbedPaneCopyFromBasicUI.TabbedPaneLayout {
1035         // fix for Radar #3346131
1036         protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
1037             // Our superclass wants to stack tabs, but we rotate them,
1038             // so when tabs are on the left or right we know that
1039             // our width is actually the "height" of a tab which is then
1040             // rotated.
1041             if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) {
1042                 return super.preferredTabAreaHeight(tabPlacement, height);
1043             }
1044 
1045             return super.preferredTabAreaWidth(tabPlacement, height);
1046         }
1047 
1048         protected int preferredTabAreaHeight(final int tabPlacement, final int width) {
1049             if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) {
1050                 return super.preferredTabAreaWidth(tabPlacement, width);
1051             }
1052 
1053             return super.preferredTabAreaHeight(tabPlacement, width);
1054         }
1055 
1056         protected void calculateTabRects(final int tabPlacement, final int tabCount) {
1057             if (tabCount <= 0) return;
1058 
1059             superCalculateTabRects(tabPlacement, tabCount); // does most of the hard work
1060 
1061             // If they haven't been padded (which they only do when there are multiple rows) we should center them
1062             if (rects.length <= 0) return;
1063 
1064             visibleTabState.alignRectsRunFor(rects, tabPane.getSize(), tabPlacement, AquaUtils.isLeftToRight(tabPane));
1065         }
1066 
1067         protected void padTabRun(final int tabPlacement, final int start, final int end, final int max) {
1068             if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) {
1069                 super.padTabRun(tabPlacement, start, end, max);
1070                 return;
1071             }
1072 
1073             final Rectangle lastRect = rects[end];
1074             final int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
1075             final int deltaHeight = max - (lastRect.y + lastRect.height);
1076             final float factor = (float)deltaHeight / (float)runHeight;
1077             for (int i = start; i <= end; i++) {
1078                 final Rectangle pastRect = rects[i];
1079                 if (i > start) {
1080                     pastRect.y = rects[i - 1].y + rects[i - 1].height;
1081                 }
1082                 pastRect.height += Math.round(pastRect.height * factor);
1083             }
1084             lastRect.height = max - lastRect.y;
1085         }
1086 
1087         /**
1088          * This is a massive routine and I left it like this because the bulk of the code comes
1089          * from the BasicTabbedPaneUI class. Here is what it does:
1090          * 1. Calculate rects for the tabs - we have to play tricks here because our right and left tabs
1091          *    should get widths calculated the same way as top and bottom, but they will be rotated so the
1092          *    calculated width is stored as the rect height.
1093          * 2. Decide if we can fit all the tabs.
1094          * 3. When we cannot fit all the tabs we create a tab popup, and then layout the new tabs until
1095          *    we can't fit them anymore. Laying them out is a matter of adding them into the visible list
1096          *    and shifting them horizontally to the correct location.
1097          */
1098         protected synchronized void superCalculateTabRects(final int tabPlacement, final int tabCount) {
1099             final Dimension size = tabPane.getSize();
1100             final Insets insets = tabPane.getInsets();
1101             final Insets localTabAreaInsets = getTabAreaInsets(tabPlacement);
1102 
1103             // Calculate bounds within which a tab run must fit
1104             final int returnAt;
1105             final int x, y;
1106             switch (tabPlacement) {
1107                 case SwingConstants.LEFT:
1108                     maxTabWidth = calculateMaxTabHeight(tabPlacement);
1109                     x = insets.left + localTabAreaInsets.left;
1110                     y = insets.top + localTabAreaInsets.top;
1111                     returnAt = size.height - (insets.bottom + localTabAreaInsets.bottom);
1112                     break;
1113                 case SwingConstants.RIGHT:
1114                     maxTabWidth = calculateMaxTabHeight(tabPlacement);
1115                     x = size.width - insets.right - localTabAreaInsets.right - maxTabWidth - 1;
1116                     y = insets.top + localTabAreaInsets.top;
1117                     returnAt = size.height - (insets.bottom + localTabAreaInsets.bottom);
1118                     break;
1119                 case SwingConstants.BOTTOM:
1120                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
1121                     x = insets.left + localTabAreaInsets.left;
1122                     y = size.height - insets.bottom - localTabAreaInsets.bottom - maxTabHeight;
1123                     returnAt = size.width - (insets.right + localTabAreaInsets.right);
1124                     break;
1125                 case SwingConstants.TOP:
1126                 default:
1127                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
1128                     x = insets.left + localTabAreaInsets.left;
1129                     y = insets.top + localTabAreaInsets.top;
1130                     returnAt = size.width - (insets.right + localTabAreaInsets.right);
1131                     break;
1132             }
1133 
1134             tabRunOverlay = getTabRunOverlay(tabPlacement);
1135 
1136             runCount = 0;
1137             selectedRun = 0;
1138 
1139             if (tabCount == 0) return;
1140 
1141             final FontMetrics metrics = getFontMetrics();
1142             final boolean verticalTabRuns = (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT);
1143             final int selectedIndex = tabPane.getSelectedIndex();
1144 
1145             // calculate all the widths
1146             // if they all fit we are done, if not
1147             // we have to do the dance of figuring out which ones to show.
1148             visibleTabState.setNeedsScrollers(false);
1149             for (int i = 0; i < tabCount; i++) {
1150                 final Rectangle rect = rects[i];
1151 
1152                 if (verticalTabRuns) {
1153                     calculateVerticalTabRunRect(rect, metrics, tabPlacement, returnAt, i, x, y);
1154 
1155                     // test if we need to scroll!
1156                     if (rect.y + rect.height > returnAt) {
1157                         visibleTabState.setNeedsScrollers(true);
1158                     }
1159                 } else {
1160                     calculateHorizontalTabRunRect(rect, metrics, tabPlacement, returnAt, i, x, y);
1161 
1162                     // test if we need to scroll!
1163                     if (rect.x + rect.width > returnAt) {
1164                         visibleTabState.setNeedsScrollers(true);
1165                     }
1166                 }
1167             }
1168 
1169             visibleTabState.relayoutForScrolling(rects, x, y, returnAt, selectedIndex, verticalTabRuns, tabCount, AquaUtils.isLeftToRight(tabPane));
1170             // Pad the selected tab so that it appears raised in front
1171 
1172             // if right to left and tab placement on the top or
1173             // the bottom, flip x positions and adjust by widths
1174             if (!AquaUtils.isLeftToRight(tabPane) && !verticalTabRuns) {
1175                 final int rightMargin = size.width - (insets.right + localTabAreaInsets.right);
1176                 for (int i = 0; i < tabCount; i++) {
1177                     rects[i].x = rightMargin - rects[i].x - rects[i].width;
1178                 }
1179             }
1180         }
1181 
1182         private void calculateHorizontalTabRunRect(final Rectangle rect, final FontMetrics metrics, final int tabPlacement, final int returnAt, final int i, final int x, final int y) {
1183             // Tabs on TOP or BOTTOM....
1184             if (i > 0) {
1185                 rect.x = rects[i - 1].x + rects[i - 1].width;
1186             } else {
1187                 tabRuns[0] = 0;
1188                 runCount = 1;
1189                 maxTabWidth = 0;
1190                 rect.x = x;
1191             }
1192 
1193             rect.width = calculateTabWidth(tabPlacement, i, metrics);
1194             maxTabWidth = Math.max(maxTabWidth, rect.width);
1195 
1196             rect.y = y;
1197             rect.height = maxTabHeight;
1198         }
1199 
1200         private void calculateVerticalTabRunRect(final Rectangle rect, final FontMetrics metrics, final int tabPlacement, final int returnAt, final int i, final int x, final int y) {
1201             // Tabs on LEFT or RIGHT...
1202             if (i > 0) {
1203                 rect.y = rects[i - 1].y + rects[i - 1].height;
1204             } else {
1205                 tabRuns[0] = 0;
1206                 runCount = 1;
1207                 maxTabHeight = 0;
1208                 rect.y = y;
1209             }
1210 
1211             rect.height = calculateTabWidth(tabPlacement, i, metrics);
1212             maxTabHeight = Math.max(maxTabHeight, rect.height);
1213 
1214             rect.x = x;
1215             rect.width = maxTabWidth;
1216         }
1217 
1218         protected void layoutTabComponents() {
1219             final Container tabContainer = getTabContainer();
1220             if (tabContainer == null) return;
1221 
1222             final int placement = tabPane.getTabPlacement();
1223             final Rectangle rect = new Rectangle();
1224             final Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
1225 
1226             for (int i = 0; i < tabPane.getTabCount(); i++) {
1227                 final Component c = getTabComponentAt(i);
1228                 if (c == null) continue;
1229 
1230                 getTabBounds(i, rect);
1231                 final Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
1232                 final boolean isSeleceted = i == tabPane.getSelectedIndex();
1233 
1234                 if (placement == SwingConstants.TOP || placement == SwingConstants.BOTTOM) {
1235                     rect.x += insets.left + delta.x + getTabLabelShiftX(placement, i, isSeleceted);
1236                     rect.y += insets.top + delta.y + getTabLabelShiftY(placement, i, isSeleceted) + 1;
1237                     rect.width -= insets.left + insets.right;
1238                     rect.height -= insets.top + insets.bottom - 1;
1239                 } else {
1240                     rect.x += insets.top + delta.x + getTabLabelShiftY(placement, i, isSeleceted) + (placement == SwingConstants.LEFT ? 2 : 1);
1241                     rect.y += insets.left + delta.y + getTabLabelShiftX(placement, i, isSeleceted);
1242                     rect.width -= insets.top + insets.bottom - 1;
1243                     rect.height -= insets.left + insets.right;
1244                 }
1245 
1246                 c.setBounds(rect);
1247             }
1248         }
1249     }
1250 }