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