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