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