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