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
26package com.apple.laf;
27
28import java.awt.*;
29import java.awt.event.*;
30import java.awt.geom.AffineTransform;
31import java.beans.*;
32
33import javax.swing.*;
34import javax.swing.event.*;
35import javax.swing.plaf.*;
36import javax.swing.text.View;
37
38import sun.swing.SwingUtilities2;
39import apple.laf.*;
40import apple.laf.JRSUIConstants.*;
41
42public 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}
1281