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