1 /* 2 * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.metal; 27 28 import javax.swing.*; 29 import javax.swing.event.*; 30 import java.awt.*; 31 import java.awt.event.*; 32 import javax.swing.plaf.*; 33 import java.io.Serializable; 34 import javax.swing.plaf.basic.BasicTabbedPaneUI; 35 36 /** 37 * The Metal subclass of BasicTabbedPaneUI. 38 * <p> 39 * <strong>Warning:</strong> 40 * Serialized objects of this class will not be compatible with 41 * future Swing releases. The current serialization support is 42 * appropriate for short term storage or RMI between applications running 43 * the same version of Swing. As of 1.4, support for long term storage 44 * of all JavaBeans 45 * has been added to the <code>java.beans</code> package. 46 * Please see {@link java.beans.XMLEncoder}. 47 * 48 * @author Tom Santos 49 */ 50 @SuppressWarnings("serial") // Same-version serialization only 51 public class MetalTabbedPaneUI extends BasicTabbedPaneUI { 52 53 /** 54 * The minimum width of a pane. 55 */ 56 protected int minTabWidth = 40; 57 // Background color for unselected tabs that don't have an explicitly 58 // set color. 59 private Color unselectedBackground; 60 61 /** 62 * The color of tab's background. 63 */ 64 protected Color tabAreaBackground; 65 66 /** 67 * The color of the selected pane. 68 */ 69 protected Color selectColor; 70 71 /** 72 * The color of the highlight. 73 */ 74 protected Color selectHighlight; 75 private boolean tabsOpaque = true; 76 77 // Whether or not we're using ocean. This is cached as it is used 78 // extensively during painting. 79 private boolean ocean; 80 // Selected border color for ocean. 81 private Color oceanSelectedBorderColor; 82 83 /** 84 * Constructs {@code MetalTabbedPaneUI}. 85 * 86 * @param x a component 87 * @return an instance of {@code MetalTabbedPaneUI} 88 */ 89 public static ComponentUI createUI( JComponent x ) { 90 return new MetalTabbedPaneUI(); 91 } 92 93 protected LayoutManager createLayoutManager() { 94 if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) { 95 return super.createLayoutManager(); 96 } 97 return new TabbedPaneLayout(); 98 } 99 100 protected void installDefaults() { 101 super.installDefaults(); 102 103 tabAreaBackground = UIManager.getColor("TabbedPane.tabAreaBackground"); 104 selectColor = UIManager.getColor("TabbedPane.selected"); 105 selectHighlight = UIManager.getColor("TabbedPane.selectHighlight"); 106 tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque"); 107 unselectedBackground = UIManager.getColor( 108 "TabbedPane.unselectedBackground"); 109 ocean = MetalLookAndFeel.usingOcean(); 110 if (ocean) { 111 oceanSelectedBorderColor = UIManager.getColor( 112 "TabbedPane.borderHightlightColor"); 113 } 114 } 115 116 117 protected void paintTabBorder( Graphics g, int tabPlacement, 118 int tabIndex, int x, int y, int w, int h, 119 boolean isSelected) { 120 int bottom = y + (h-1); 121 int right = x + (w-1); 122 123 switch ( tabPlacement ) { 124 case LEFT: 125 paintLeftTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected); 126 break; 127 case BOTTOM: 128 paintBottomTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected); 129 break; 130 case RIGHT: 131 paintRightTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected); 132 break; 133 case TOP: 134 default: 135 paintTopTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected); 136 } 137 } 138 139 140 /** 141 * Paints the top tab border. 142 * 143 * @param tabIndex a tab index 144 * @param g an instance of {@code Graphics} 145 * @param x an X coordinate 146 * @param y an Y coordinate 147 * @param w a width 148 * @param h a height 149 * @param btm bottom 150 * @param rght right 151 * @param isSelected a selection 152 */ 153 protected void paintTopTabBorder( int tabIndex, Graphics g, 154 int x, int y, int w, int h, 155 int btm, int rght, 156 boolean isSelected ) { 157 int currentRun = getRunForTab( tabPane.getTabCount(), tabIndex ); 158 int lastIndex = lastTabInRun( tabPane.getTabCount(), currentRun ); 159 int firstIndex = tabRuns[ currentRun ]; 160 boolean leftToRight = MetalUtils.isLeftToRight(tabPane); 161 int selectedIndex = tabPane.getSelectedIndex(); 162 int bottom = h - 1; 163 int right = w - 1; 164 165 // 166 // Paint Gap 167 // 168 169 if (shouldFillGap( currentRun, tabIndex, x, y ) ) { 170 g.translate( x, y ); 171 172 if ( leftToRight ) { 173 g.setColor( getColorForGap( currentRun, x, y + 1 ) ); 174 g.fillRect( 1, 0, 5, 3 ); 175 g.fillRect( 1, 3, 2, 2 ); 176 } else { 177 g.setColor( getColorForGap( currentRun, x + w - 1, y + 1 ) ); 178 g.fillRect( right - 5, 0, 5, 3 ); 179 g.fillRect( right - 2, 3, 2, 2 ); 180 } 181 182 g.translate( -x, -y ); 183 } 184 185 g.translate( x, y ); 186 187 // 188 // Paint Border 189 // 190 191 if (ocean && isSelected) { 192 g.setColor(oceanSelectedBorderColor); 193 } 194 else { 195 g.setColor( darkShadow ); 196 } 197 198 if ( leftToRight ) { 199 200 // Paint slant 201 g.drawLine( 1, 5, 6, 0 ); 202 203 // Paint top 204 g.drawLine( 6, 0, right, 0 ); 205 206 // Paint right 207 if ( tabIndex==lastIndex ) { 208 // last tab in run 209 g.drawLine( right, 1, right, bottom ); 210 } 211 212 if (ocean && tabIndex - 1 == selectedIndex && 213 currentRun == getRunForTab( 214 tabPane.getTabCount(), selectedIndex)) { 215 g.setColor(oceanSelectedBorderColor); 216 } 217 218 // Paint left 219 if ( tabIndex != tabRuns[ runCount - 1 ] ) { 220 // not the first tab in the last run 221 if (ocean && isSelected) { 222 g.drawLine(0, 6, 0, bottom); 223 g.setColor(darkShadow); 224 g.drawLine(0, 0, 0, 5); 225 } 226 else { 227 g.drawLine( 0, 0, 0, bottom ); 228 } 229 } else { 230 // the first tab in the last run 231 g.drawLine( 0, 6, 0, bottom ); 232 } 233 } else { 234 235 // Paint slant 236 g.drawLine( right - 1, 5, right - 6, 0 ); 237 238 // Paint top 239 g.drawLine( right - 6, 0, 0, 0 ); 240 241 // Paint left 242 if ( tabIndex==lastIndex ) { 243 // last tab in run 244 g.drawLine( 0, 1, 0, bottom ); 245 } 246 247 // Paint right 248 if (ocean && tabIndex - 1 == selectedIndex && 249 currentRun == getRunForTab( 250 tabPane.getTabCount(), selectedIndex)) { 251 g.setColor(oceanSelectedBorderColor); 252 g.drawLine(right, 0, right, bottom); 253 } 254 else if (ocean && isSelected) { 255 g.drawLine(right, 6, right, bottom); 256 if (tabIndex != 0) { 257 g.setColor(darkShadow); 258 g.drawLine(right, 0, right, 5); 259 } 260 } 261 else { 262 if ( tabIndex != tabRuns[ runCount - 1 ] ) { 263 // not the first tab in the last run 264 g.drawLine( right, 0, right, bottom ); 265 } else { 266 // the first tab in the last run 267 g.drawLine( right, 6, right, bottom ); 268 } 269 } 270 } 271 272 // 273 // Paint Highlight 274 // 275 276 g.setColor( isSelected ? selectHighlight : highlight ); 277 278 if ( leftToRight ) { 279 280 // Paint slant 281 g.drawLine( 1, 6, 6, 1 ); 282 283 // Paint top 284 g.drawLine( 6, 1, (tabIndex == lastIndex) ? right - 1 : right, 1 ); 285 286 // Paint left 287 g.drawLine( 1, 6, 1, bottom ); 288 289 // paint highlight in the gap on tab behind this one 290 // on the left end (where they all line up) 291 if ( tabIndex==firstIndex && tabIndex!=tabRuns[runCount - 1] ) { 292 // first tab in run but not first tab in last run 293 if (tabPane.getSelectedIndex()==tabRuns[currentRun+1]) { 294 // tab in front of selected tab 295 g.setColor( selectHighlight ); 296 } 297 else { 298 // tab in front of normal tab 299 g.setColor( highlight ); 300 } 301 g.drawLine( 1, 0, 1, 4 ); 302 } 303 } else { 304 305 // Paint slant 306 g.drawLine( right - 1, 6, right - 6, 1 ); 307 308 // Paint top 309 g.drawLine( right - 6, 1, 1, 1 ); 310 311 // Paint left 312 if ( tabIndex==lastIndex ) { 313 // last tab in run 314 g.drawLine( 1, 1, 1, bottom ); 315 } else { 316 g.drawLine( 0, 1, 0, bottom ); 317 } 318 } 319 320 g.translate( -x, -y ); 321 } 322 323 /** 324 * Returns {@code true} if the gap should be filled. 325 * 326 * @param currentRun the current run 327 * @param tabIndex the tab index 328 * @param x an X coordinate 329 * @param y an Y coordinate 330 * @return {@code true} if the gap should be filled 331 */ 332 protected boolean shouldFillGap( int currentRun, int tabIndex, int x, int y ) { 333 boolean result = false; 334 335 if (!tabsOpaque) { 336 return false; 337 } 338 339 if ( currentRun == runCount - 2 ) { // If it's the second to last row. 340 Rectangle lastTabBounds = getTabBounds( tabPane, tabPane.getTabCount() - 1 ); 341 Rectangle tabBounds = getTabBounds( tabPane, tabIndex ); 342 if (MetalUtils.isLeftToRight(tabPane)) { 343 int lastTabRight = lastTabBounds.x + lastTabBounds.width - 1; 344 345 // is the right edge of the last tab to the right 346 // of the left edge of the current tab? 347 if ( lastTabRight > tabBounds.x + 2 ) { 348 return true; 349 } 350 } else { 351 int lastTabLeft = lastTabBounds.x; 352 int currentTabRight = tabBounds.x + tabBounds.width - 1; 353 354 // is the left edge of the last tab to the left 355 // of the right edge of the current tab? 356 if ( lastTabLeft < currentTabRight - 2 ) { 357 return true; 358 } 359 } 360 } else { 361 // fill in gap for all other rows except last row 362 result = currentRun != runCount - 1; 363 } 364 365 return result; 366 } 367 368 /** 369 * Returns the color of the gap. 370 * 371 * @param currentRun the current run 372 * @param x an X coordinate 373 * @param y an Y coordinate 374 * @return the color of the gap 375 */ 376 protected Color getColorForGap( int currentRun, int x, int y ) { 377 final int shadowWidth = 4; 378 int selectedIndex = tabPane.getSelectedIndex(); 379 int startIndex = tabRuns[ currentRun + 1 ]; 380 int endIndex = lastTabInRun( tabPane.getTabCount(), currentRun + 1 ); 381 int tabOverGap = -1; 382 // Check each tab in the row that is 'on top' of this row 383 for ( int i = startIndex; i <= endIndex; ++i ) { 384 Rectangle tabBounds = getTabBounds( tabPane, i ); 385 int tabLeft = tabBounds.x; 386 int tabRight = (tabBounds.x + tabBounds.width) - 1; 387 // Check to see if this tab is over the gap 388 if ( MetalUtils.isLeftToRight(tabPane) ) { 389 if ( tabLeft <= x && tabRight - shadowWidth > x ) { 390 return selectedIndex == i ? selectColor : getUnselectedBackgroundAt( i ); 391 } 392 } 393 else { 394 if ( tabLeft + shadowWidth < x && tabRight >= x ) { 395 return selectedIndex == i ? selectColor : getUnselectedBackgroundAt( i ); 396 } 397 } 398 } 399 400 return tabPane.getBackground(); 401 } 402 403 /** 404 * Paints the left tab border. 405 * 406 * @param tabIndex a tab index 407 * @param g an instance of {@code Graphics} 408 * @param x an X coordinate 409 * @param y an Y coordinate 410 * @param w a width 411 * @param h a height 412 * @param btm bottom 413 * @param rght right 414 * @param isSelected a selection 415 */ 416 protected void paintLeftTabBorder( int tabIndex, Graphics g, 417 int x, int y, int w, int h, 418 int btm, int rght, 419 boolean isSelected ) { 420 int tabCount = tabPane.getTabCount(); 421 int currentRun = getRunForTab( tabCount, tabIndex ); 422 int lastIndex = lastTabInRun( tabCount, currentRun ); 423 int firstIndex = tabRuns[ currentRun ]; 424 425 g.translate( x, y ); 426 427 int bottom = h - 1; 428 int right = w - 1; 429 430 // 431 // Paint part of the tab above 432 // 433 434 if ( tabIndex != firstIndex && tabsOpaque ) { 435 g.setColor( tabPane.getSelectedIndex() == tabIndex - 1 ? 436 selectColor : 437 getUnselectedBackgroundAt( tabIndex - 1 ) ); 438 g.fillRect( 2, 0, 4, 3 ); 439 g.drawLine( 2, 3, 2, 3 ); 440 } 441 442 443 // 444 // Paint Highlight 445 // 446 447 if (ocean) { 448 g.setColor(isSelected ? selectHighlight : 449 MetalLookAndFeel.getWhite()); 450 } 451 else { 452 g.setColor( isSelected ? selectHighlight : highlight ); 453 } 454 455 // Paint slant 456 g.drawLine( 1, 6, 6, 1 ); 457 458 // Paint left 459 g.drawLine( 1, 6, 1, bottom ); 460 461 // Paint top 462 g.drawLine( 6, 1, right, 1 ); 463 464 if ( tabIndex != firstIndex ) { 465 if (tabPane.getSelectedIndex() == tabIndex - 1) { 466 g.setColor(selectHighlight); 467 } else { 468 g.setColor(ocean ? MetalLookAndFeel.getWhite() : highlight); 469 } 470 471 g.drawLine( 1, 0, 1, 4 ); 472 } 473 474 // 475 // Paint Border 476 // 477 478 if (ocean) { 479 if (isSelected) { 480 g.setColor(oceanSelectedBorderColor); 481 } 482 else { 483 g.setColor( darkShadow ); 484 } 485 } 486 else { 487 g.setColor( darkShadow ); 488 } 489 490 // Paint slant 491 g.drawLine( 1, 5, 6, 0 ); 492 493 // Paint top 494 g.drawLine( 6, 0, right, 0 ); 495 496 // Paint bottom 497 if ( tabIndex == lastIndex ) { 498 g.drawLine( 0, bottom, right, bottom ); 499 } 500 501 // Paint left 502 if (ocean) { 503 if (tabPane.getSelectedIndex() == tabIndex - 1) { 504 g.drawLine(0, 5, 0, bottom); 505 g.setColor(oceanSelectedBorderColor); 506 g.drawLine(0, 0, 0, 5); 507 } 508 else if (isSelected) { 509 g.drawLine( 0, 6, 0, bottom ); 510 if (tabIndex != 0) { 511 g.setColor(darkShadow); 512 g.drawLine(0, 0, 0, 5); 513 } 514 } 515 else if ( tabIndex != firstIndex ) { 516 g.drawLine( 0, 0, 0, bottom ); 517 } else { 518 g.drawLine( 0, 6, 0, bottom ); 519 } 520 } 521 else { // metal 522 if ( tabIndex != firstIndex ) { 523 g.drawLine( 0, 0, 0, bottom ); 524 } else { 525 g.drawLine( 0, 6, 0, bottom ); 526 } 527 } 528 529 g.translate( -x, -y ); 530 } 531 532 533 /** 534 * Paints the bottom tab border. 535 * 536 * @param tabIndex a tab index 537 * @param g an instance of {@code Graphics} 538 * @param x an X coordinate 539 * @param y an Y coordinate 540 * @param w a width 541 * @param h a height 542 * @param btm bottom 543 * @param rght right 544 * @param isSelected a selection 545 */ 546 protected void paintBottomTabBorder( int tabIndex, Graphics g, 547 int x, int y, int w, int h, 548 int btm, int rght, 549 boolean isSelected ) { 550 int tabCount = tabPane.getTabCount(); 551 int currentRun = getRunForTab( tabCount, tabIndex ); 552 int lastIndex = lastTabInRun( tabCount, currentRun ); 553 int firstIndex = tabRuns[ currentRun ]; 554 boolean leftToRight = MetalUtils.isLeftToRight(tabPane); 555 556 int bottom = h - 1; 557 int right = w - 1; 558 559 // 560 // Paint Gap 561 // 562 563 if ( shouldFillGap( currentRun, tabIndex, x, y ) ) { 564 g.translate( x, y ); 565 566 if ( leftToRight ) { 567 g.setColor( getColorForGap( currentRun, x, y ) ); 568 g.fillRect( 1, bottom - 4, 3, 5 ); 569 g.fillRect( 4, bottom - 1, 2, 2 ); 570 } else { 571 g.setColor( getColorForGap( currentRun, x + w - 1, y ) ); 572 g.fillRect( right - 3, bottom - 3, 3, 4 ); 573 g.fillRect( right - 5, bottom - 1, 2, 2 ); 574 g.drawLine( right - 1, bottom - 4, right - 1, bottom - 4 ); 575 } 576 577 g.translate( -x, -y ); 578 } 579 580 g.translate( x, y ); 581 582 583 // 584 // Paint Border 585 // 586 587 if (ocean && isSelected) { 588 g.setColor(oceanSelectedBorderColor); 589 } 590 else { 591 g.setColor( darkShadow ); 592 } 593 594 if ( leftToRight ) { 595 596 // Paint slant 597 g.drawLine( 1, bottom - 5, 6, bottom ); 598 599 // Paint bottom 600 g.drawLine( 6, bottom, right, bottom ); 601 602 // Paint right 603 if ( tabIndex == lastIndex ) { 604 g.drawLine( right, 0, right, bottom ); 605 } 606 607 // Paint left 608 if (ocean && isSelected) { 609 g.drawLine(0, 0, 0, bottom - 6); 610 if ((currentRun == 0 && tabIndex != 0) || 611 (currentRun > 0 && tabIndex != tabRuns[currentRun - 1])) { 612 g.setColor(darkShadow); 613 g.drawLine(0, bottom - 5, 0, bottom); 614 } 615 } 616 else { 617 if (ocean && tabIndex == tabPane.getSelectedIndex() + 1) { 618 g.setColor(oceanSelectedBorderColor); 619 } 620 if ( tabIndex != tabRuns[ runCount - 1 ] ) { 621 g.drawLine( 0, 0, 0, bottom ); 622 } else { 623 g.drawLine( 0, 0, 0, bottom - 6 ); 624 } 625 } 626 } else { 627 628 // Paint slant 629 g.drawLine( right - 1, bottom - 5, right - 6, bottom ); 630 631 // Paint bottom 632 g.drawLine( right - 6, bottom, 0, bottom ); 633 634 // Paint left 635 if ( tabIndex==lastIndex ) { 636 // last tab in run 637 g.drawLine( 0, 0, 0, bottom ); 638 } 639 640 // Paint right 641 if (ocean && tabIndex == tabPane.getSelectedIndex() + 1) { 642 g.setColor(oceanSelectedBorderColor); 643 g.drawLine(right, 0, right, bottom); 644 } 645 else if (ocean && isSelected) { 646 g.drawLine(right, 0, right, bottom - 6); 647 if (tabIndex != firstIndex) { 648 g.setColor(darkShadow); 649 g.drawLine(right, bottom - 5, right, bottom); 650 } 651 } 652 else if ( tabIndex != tabRuns[ runCount - 1 ] ) { 653 // not the first tab in the last run 654 g.drawLine( right, 0, right, bottom ); 655 } else { 656 // the first tab in the last run 657 g.drawLine( right, 0, right, bottom - 6 ); 658 } 659 } 660 661 // 662 // Paint Highlight 663 // 664 665 g.setColor( isSelected ? selectHighlight : highlight ); 666 667 if ( leftToRight ) { 668 669 // Paint slant 670 g.drawLine( 1, bottom - 6, 6, bottom - 1 ); 671 672 // Paint left 673 g.drawLine( 1, 0, 1, bottom - 6 ); 674 675 // paint highlight in the gap on tab behind this one 676 // on the left end (where they all line up) 677 if ( tabIndex==firstIndex && tabIndex!=tabRuns[runCount - 1] ) { 678 // first tab in run but not first tab in last run 679 if (tabPane.getSelectedIndex()==tabRuns[currentRun+1]) { 680 // tab in front of selected tab 681 g.setColor( selectHighlight ); 682 } 683 else { 684 // tab in front of normal tab 685 g.setColor( highlight ); 686 } 687 g.drawLine( 1, bottom - 4, 1, bottom ); 688 } 689 } else { 690 691 // Paint left 692 if ( tabIndex==lastIndex ) { 693 // last tab in run 694 g.drawLine( 1, 0, 1, bottom - 1 ); 695 } else { 696 g.drawLine( 0, 0, 0, bottom - 1 ); 697 } 698 } 699 700 g.translate( -x, -y ); 701 } 702 703 /** 704 * Paints the right tab border. 705 * 706 * @param tabIndex a tab index 707 * @param g an instance of {@code Graphics} 708 * @param x an X coordinate 709 * @param y an Y coordinate 710 * @param w a width 711 * @param h a height 712 * @param btm bottom 713 * @param rght right 714 * @param isSelected a selection 715 */ 716 protected void paintRightTabBorder( int tabIndex, Graphics g, 717 int x, int y, int w, int h, 718 int btm, int rght, 719 boolean isSelected ) { 720 int tabCount = tabPane.getTabCount(); 721 int currentRun = getRunForTab( tabCount, tabIndex ); 722 int lastIndex = lastTabInRun( tabCount, currentRun ); 723 int firstIndex = tabRuns[ currentRun ]; 724 725 g.translate( x, y ); 726 727 int bottom = h - 1; 728 int right = w - 1; 729 730 // 731 // Paint part of the tab above 732 // 733 734 if ( tabIndex != firstIndex && tabsOpaque ) { 735 g.setColor( tabPane.getSelectedIndex() == tabIndex - 1 ? 736 selectColor : 737 getUnselectedBackgroundAt( tabIndex - 1 ) ); 738 g.fillRect( right - 5, 0, 5, 3 ); 739 g.fillRect( right - 2, 3, 2, 2 ); 740 } 741 742 743 // 744 // Paint Highlight 745 // 746 747 g.setColor( isSelected ? selectHighlight : highlight ); 748 749 // Paint slant 750 g.drawLine( right - 6, 1, right - 1, 6 ); 751 752 // Paint top 753 g.drawLine( 0, 1, right - 6, 1 ); 754 755 // Paint left 756 if ( !isSelected ) { 757 g.drawLine( 0, 1, 0, bottom ); 758 } 759 760 761 // 762 // Paint Border 763 // 764 765 if (ocean && isSelected) { 766 g.setColor(oceanSelectedBorderColor); 767 } 768 else { 769 g.setColor( darkShadow ); 770 } 771 772 // Paint bottom 773 if ( tabIndex == lastIndex ) { 774 g.drawLine( 0, bottom, right, bottom ); 775 } 776 777 // Paint slant 778 if (ocean && tabPane.getSelectedIndex() == tabIndex - 1) { 779 g.setColor(oceanSelectedBorderColor); 780 } 781 g.drawLine( right - 6, 0, right, 6 ); 782 783 // Paint top 784 g.drawLine( 0, 0, right - 6, 0 ); 785 786 // Paint right 787 if (ocean && isSelected) { 788 g.drawLine(right, 6, right, bottom); 789 if (tabIndex != firstIndex) { 790 g.setColor(darkShadow); 791 g.drawLine(right, 0, right, 5); 792 } 793 } 794 else if (ocean && tabPane.getSelectedIndex() == tabIndex - 1) { 795 g.setColor(oceanSelectedBorderColor); 796 g.drawLine(right, 0, right, 6); 797 g.setColor(darkShadow); 798 g.drawLine(right, 6, right, bottom); 799 } 800 else if ( tabIndex != firstIndex ) { 801 g.drawLine( right, 0, right, bottom ); 802 } else { 803 g.drawLine( right, 6, right, bottom ); 804 } 805 806 g.translate( -x, -y ); 807 } 808 809 public void update( Graphics g, JComponent c ) { 810 if ( c.isOpaque() ) { 811 g.setColor( tabAreaBackground ); 812 g.fillRect( 0, 0, c.getWidth(),c.getHeight() ); 813 } 814 paint( g, c ); 815 } 816 817 protected void paintTabBackground( Graphics g, int tabPlacement, 818 int tabIndex, int x, int y, int w, int h, boolean isSelected ) { 819 int slantWidth = h / 2; 820 if ( isSelected ) { 821 g.setColor( selectColor ); 822 } else { 823 g.setColor( getUnselectedBackgroundAt( tabIndex ) ); 824 } 825 826 if (MetalUtils.isLeftToRight(tabPane)) { 827 switch ( tabPlacement ) { 828 case LEFT: 829 g.fillRect( x + 5, y + 1, w - 5, h - 1); 830 g.fillRect( x + 2, y + 4, 3, h - 4 ); 831 break; 832 case BOTTOM: 833 g.fillRect( x + 2, y, w - 2, h - 4 ); 834 g.fillRect( x + 5, y + (h - 1) - 3, w - 5, 3 ); 835 break; 836 case RIGHT: 837 g.fillRect( x, y + 2, w - 4, h - 2); 838 g.fillRect( x + (w - 1) - 3, y + 5, 3, h - 5 ); 839 break; 840 case TOP: 841 default: 842 g.fillRect( x + 4, y + 2, (w - 1) - 3, (h - 1) - 1 ); 843 g.fillRect( x + 2, y + 5, 2, h - 5 ); 844 } 845 } else { 846 switch ( tabPlacement ) { 847 case LEFT: 848 g.fillRect( x + 5, y + 1, w - 5, h - 1); 849 g.fillRect( x + 2, y + 4, 3, h - 4 ); 850 break; 851 case BOTTOM: 852 g.fillRect( x, y, w - 5, h - 1 ); 853 g.fillRect( x + (w - 1) - 4, y, 4, h - 5); 854 g.fillRect( x + (w - 1) - 4, y + (h - 1) - 4, 2, 2); 855 break; 856 case RIGHT: 857 g.fillRect( x + 1, y + 1, w - 5, h - 1); 858 g.fillRect( x + (w - 1) - 3, y + 5, 3, h - 5 ); 859 break; 860 case TOP: 861 default: 862 g.fillRect( x, y + 2, (w - 1) - 3, (h - 1) - 1 ); 863 g.fillRect( x + (w - 1) - 3, y + 5, 3, h - 3 ); 864 } 865 } 866 } 867 868 /** 869 * Overridden to do nothing for the Java L&F. 870 */ 871 protected int getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected ) { 872 return 0; 873 } 874 875 876 /** 877 * Overridden to do nothing for the Java L&F. 878 */ 879 protected int getTabLabelShiftY( int tabPlacement, int tabIndex, boolean isSelected ) { 880 return 0; 881 } 882 883 /** 884 * {@inheritDoc} 885 * 886 * @since 1.6 887 */ 888 protected int getBaselineOffset() { 889 return 0; 890 } 891 892 public void paint( Graphics g, JComponent c ) { 893 int tabPlacement = tabPane.getTabPlacement(); 894 895 Insets insets = c.getInsets(); Dimension size = c.getSize(); 896 897 // Paint the background for the tab area 898 if ( tabPane.isOpaque() ) { 899 Color background = c.getBackground(); 900 if (background instanceof UIResource && tabAreaBackground != null) { 901 g.setColor(tabAreaBackground); 902 } 903 else { 904 g.setColor(background); 905 } 906 switch ( tabPlacement ) { 907 case LEFT: 908 g.fillRect( insets.left, insets.top, 909 calculateTabAreaWidth( tabPlacement, runCount, maxTabWidth ), 910 size.height - insets.bottom - insets.top ); 911 break; 912 case BOTTOM: 913 int totalTabHeight = calculateTabAreaHeight( tabPlacement, runCount, maxTabHeight ); 914 g.fillRect( insets.left, size.height - insets.bottom - totalTabHeight, 915 size.width - insets.left - insets.right, 916 totalTabHeight ); 917 break; 918 case RIGHT: 919 int totalTabWidth = calculateTabAreaWidth( tabPlacement, runCount, maxTabWidth ); 920 g.fillRect( size.width - insets.right - totalTabWidth, 921 insets.top, totalTabWidth, 922 size.height - insets.top - insets.bottom ); 923 break; 924 case TOP: 925 default: 926 g.fillRect( insets.left, insets.top, 927 size.width - insets.right - insets.left, 928 calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) ); 929 paintHighlightBelowTab(); 930 } 931 } 932 933 super.paint( g, c ); 934 } 935 936 /** 937 * Paints highlights below tab. 938 */ 939 protected void paintHighlightBelowTab( ) { 940 941 } 942 943 944 protected void paintFocusIndicator(Graphics g, int tabPlacement, 945 Rectangle[] rects, int tabIndex, 946 Rectangle iconRect, Rectangle textRect, 947 boolean isSelected) { 948 if ( tabPane.hasFocus() && isSelected ) { 949 Rectangle tabRect = rects[tabIndex]; 950 boolean lastInRun = isLastInRun( tabIndex ); 951 g.setColor( focus ); 952 g.translate( tabRect.x, tabRect.y ); 953 int right = tabRect.width - 1; 954 int bottom = tabRect.height - 1; 955 boolean leftToRight = MetalUtils.isLeftToRight(tabPane); 956 switch ( tabPlacement ) { 957 case RIGHT: 958 g.drawLine( right - 6,2 , right - 2,6 ); // slant 959 g.drawLine( 1,2 , right - 6,2 ); // top 960 g.drawLine( right - 2,6 , right - 2,bottom ); // right 961 g.drawLine( 1,2 , 1,bottom ); // left 962 g.drawLine( 1,bottom , right - 2,bottom ); // bottom 963 break; 964 case BOTTOM: 965 if ( leftToRight ) { 966 g.drawLine( 2, bottom - 6, 6, bottom - 2 ); // slant 967 g.drawLine( 6, bottom - 2, 968 right, bottom - 2 ); // bottom 969 g.drawLine( 2, 0, 2, bottom - 6 ); // left 970 g.drawLine( 2, 0, right, 0 ); // top 971 g.drawLine( right, 0, right, bottom - 2 ); // right 972 } else { 973 g.drawLine( right - 2, bottom - 6, 974 right - 6, bottom - 2 ); // slant 975 g.drawLine( right - 2, 0, 976 right - 2, bottom - 6 ); // right 977 if ( lastInRun ) { 978 // last tab in run 979 g.drawLine( 2, bottom - 2, 980 right - 6, bottom - 2 ); // bottom 981 g.drawLine( 2, 0, right - 2, 0 ); // top 982 g.drawLine( 2, 0, 2, bottom - 2 ); // left 983 } else { 984 g.drawLine( 1, bottom - 2, 985 right - 6, bottom - 2 ); // bottom 986 g.drawLine( 1, 0, right - 2, 0 ); // top 987 g.drawLine( 1, 0, 1, bottom - 2 ); // left 988 } 989 } 990 break; 991 case LEFT: 992 g.drawLine( 2, 6, 6, 2 ); // slant 993 g.drawLine( 2, 6, 2, bottom - 1); // left 994 g.drawLine( 6, 2, right, 2 ); // top 995 g.drawLine( right, 2, right, bottom - 1 ); // right 996 g.drawLine( 2, bottom - 1, 997 right, bottom - 1 ); // bottom 998 break; 999 case TOP: 1000 default: 1001 if ( leftToRight ) { 1002 g.drawLine( 2, 6, 6, 2 ); // slant 1003 g.drawLine( 2, 6, 2, bottom - 1); // left 1004 g.drawLine( 6, 2, right, 2 ); // top 1005 g.drawLine( right, 2, right, bottom - 1 ); // right 1006 g.drawLine( 2, bottom - 1, 1007 right, bottom - 1 ); // bottom 1008 } 1009 else { 1010 g.drawLine( right - 2, 6, right - 6, 2 ); // slant 1011 g.drawLine( right - 2, 6, 1012 right - 2, bottom - 1); // right 1013 if ( lastInRun ) { 1014 // last tab in run 1015 g.drawLine( right - 6, 2, 2, 2 ); // top 1016 g.drawLine( 2, 2, 2, bottom - 1 ); // left 1017 g.drawLine( right - 2, bottom - 1, 1018 2, bottom - 1 ); // bottom 1019 } 1020 else { 1021 g.drawLine( right - 6, 2, 1, 2 ); // top 1022 g.drawLine( 1, 2, 1, bottom - 1 ); // left 1023 g.drawLine( right - 2, bottom - 1, 1024 1, bottom - 1 ); // bottom 1025 } 1026 } 1027 } 1028 g.translate( -tabRect.x, -tabRect.y ); 1029 } 1030 } 1031 1032 protected void paintContentBorderTopEdge( Graphics g, int tabPlacement, 1033 int selectedIndex, 1034 int x, int y, int w, int h ) { 1035 boolean leftToRight = MetalUtils.isLeftToRight(tabPane); 1036 int right = x + w - 1; 1037 Rectangle selRect = selectedIndex < 0? null : 1038 getTabBounds(selectedIndex, calcRect); 1039 if (ocean) { 1040 g.setColor(oceanSelectedBorderColor); 1041 } 1042 else { 1043 g.setColor(selectHighlight); 1044 } 1045 1046 // Draw unbroken line if tabs are not on TOP, OR 1047 // selected tab is not in run adjacent to content, OR 1048 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1049 // 1050 if (tabPlacement != TOP || selectedIndex < 0 || 1051 (selRect.y + selRect.height + 1 < y) || 1052 (selRect.x < x || selRect.x > x + w)) { 1053 g.drawLine(x, y, x+w-2, y); 1054 if (ocean && tabPlacement == TOP) { 1055 g.setColor(MetalLookAndFeel.getWhite()); 1056 g.drawLine(x, y + 1, x+w-2, y + 1); 1057 } 1058 } else { 1059 // Break line to show visual connection to selected tab 1060 boolean lastInRun = isLastInRun(selectedIndex); 1061 1062 if ( leftToRight || lastInRun ) { 1063 g.drawLine(x, y, selRect.x + 1, y); 1064 } else { 1065 g.drawLine(x, y, selRect.x, y); 1066 } 1067 1068 if (selRect.x + selRect.width < right - 1) { 1069 if ( leftToRight && !lastInRun ) { 1070 g.drawLine(selRect.x + selRect.width, y, right - 1, y); 1071 } else { 1072 g.drawLine(selRect.x + selRect.width - 1, y, right - 1, y); 1073 } 1074 } else { 1075 g.setColor(shadow); 1076 g.drawLine(x+w-2, y, x+w-2, y); 1077 } 1078 1079 if (ocean) { 1080 g.setColor(MetalLookAndFeel.getWhite()); 1081 1082 if ( leftToRight || lastInRun ) { 1083 g.drawLine(x, y + 1, selRect.x + 1, y + 1); 1084 } else { 1085 g.drawLine(x, y + 1, selRect.x, y + 1); 1086 } 1087 1088 if (selRect.x + selRect.width < right - 1) { 1089 if ( leftToRight && !lastInRun ) { 1090 g.drawLine(selRect.x + selRect.width, y + 1, 1091 right - 1, y + 1); 1092 } else { 1093 g.drawLine(selRect.x + selRect.width - 1, y + 1, 1094 right - 1, y + 1); 1095 } 1096 } else { 1097 g.setColor(shadow); 1098 g.drawLine(x+w-2, y + 1, x+w-2, y + 1); 1099 } 1100 } 1101 } 1102 } 1103 1104 protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, 1105 int selectedIndex, 1106 int x, int y, int w, int h) { 1107 boolean leftToRight = MetalUtils.isLeftToRight(tabPane); 1108 int bottom = y + h - 1; 1109 int right = x + w - 1; 1110 Rectangle selRect = selectedIndex < 0? null : 1111 getTabBounds(selectedIndex, calcRect); 1112 1113 g.setColor(darkShadow); 1114 1115 // Draw unbroken line if tabs are not on BOTTOM, OR 1116 // selected tab is not in run adjacent to content, OR 1117 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1118 // 1119 if (tabPlacement != BOTTOM || selectedIndex < 0 || 1120 (selRect.y - 1 > h) || 1121 (selRect.x < x || selRect.x > x + w)) { 1122 if (ocean && tabPlacement == BOTTOM) { 1123 g.setColor(oceanSelectedBorderColor); 1124 } 1125 g.drawLine(x, y+h-1, x+w-1, y+h-1); 1126 } else { 1127 // Break line to show visual connection to selected tab 1128 boolean lastInRun = isLastInRun(selectedIndex); 1129 1130 if (ocean) { 1131 g.setColor(oceanSelectedBorderColor); 1132 } 1133 1134 if ( leftToRight || lastInRun ) { 1135 g.drawLine(x, bottom, selRect.x, bottom); 1136 } else { 1137 g.drawLine(x, bottom, selRect.x - 1, bottom); 1138 } 1139 1140 if (selRect.x + selRect.width < x + w - 2) { 1141 if ( leftToRight && !lastInRun ) { 1142 g.drawLine(selRect.x + selRect.width, bottom, 1143 right, bottom); 1144 } else { 1145 g.drawLine(selRect.x + selRect.width - 1, bottom, 1146 right, bottom); 1147 } 1148 } 1149 } 1150 } 1151 1152 protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, 1153 int selectedIndex, 1154 int x, int y, int w, int h) { 1155 Rectangle selRect = selectedIndex < 0? null : 1156 getTabBounds(selectedIndex, calcRect); 1157 if (ocean) { 1158 g.setColor(oceanSelectedBorderColor); 1159 } 1160 else { 1161 g.setColor(selectHighlight); 1162 } 1163 1164 // Draw unbroken line if tabs are not on LEFT, OR 1165 // selected tab is not in run adjacent to content, OR 1166 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1167 // 1168 if (tabPlacement != LEFT || selectedIndex < 0 || 1169 (selRect.x + selRect.width + 1 < x) || 1170 (selRect.y < y || selRect.y > y + h)) { 1171 g.drawLine(x, y + 1, x, y+h-2); 1172 if (ocean && tabPlacement == LEFT) { 1173 g.setColor(MetalLookAndFeel.getWhite()); 1174 g.drawLine(x + 1, y, x + 1, y + h - 2); 1175 } 1176 } else { 1177 // Break line to show visual connection to selected tab 1178 g.drawLine(x, y, x, selRect.y + 1); 1179 if (selRect.y + selRect.height < y + h - 2) { 1180 g.drawLine(x, selRect.y + selRect.height + 1, 1181 x, y+h+2); 1182 } 1183 if (ocean) { 1184 g.setColor(MetalLookAndFeel.getWhite()); 1185 g.drawLine(x + 1, y + 1, x + 1, selRect.y + 1); 1186 if (selRect.y + selRect.height < y + h - 2) { 1187 g.drawLine(x + 1, selRect.y + selRect.height + 1, 1188 x + 1, y+h+2); 1189 } 1190 } 1191 } 1192 } 1193 1194 protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, 1195 int selectedIndex, 1196 int x, int y, int w, int h) { 1197 Rectangle selRect = selectedIndex < 0? null : 1198 getTabBounds(selectedIndex, calcRect); 1199 1200 g.setColor(darkShadow); 1201 // Draw unbroken line if tabs are not on RIGHT, OR 1202 // selected tab is not in run adjacent to content, OR 1203 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1204 // 1205 if (tabPlacement != RIGHT || selectedIndex < 0 || 1206 (selRect.x - 1 > w) || 1207 (selRect.y < y || selRect.y > y + h)) { 1208 if (ocean && tabPlacement == RIGHT) { 1209 g.setColor(oceanSelectedBorderColor); 1210 } 1211 g.drawLine(x+w-1, y, x+w-1, y+h-1); 1212 } else { 1213 // Break line to show visual connection to selected tab 1214 if (ocean) { 1215 g.setColor(oceanSelectedBorderColor); 1216 } 1217 g.drawLine(x+w-1, y, x+w-1, selRect.y); 1218 1219 if (selRect.y + selRect.height < y + h - 2) { 1220 g.drawLine(x+w-1, selRect.y + selRect.height, 1221 x+w-1, y+h-2); 1222 } 1223 } 1224 } 1225 1226 protected int calculateMaxTabHeight( int tabPlacement ) { 1227 FontMetrics metrics = getFontMetrics(); 1228 int height = metrics.getHeight(); 1229 boolean tallerIcons = false; 1230 1231 for ( int i = 0; i < tabPane.getTabCount(); ++i ) { 1232 Icon icon = tabPane.getIconAt( i ); 1233 if ( icon != null ) { 1234 if ( icon.getIconHeight() > height ) { 1235 tallerIcons = true; 1236 break; 1237 } 1238 } 1239 } 1240 return super.calculateMaxTabHeight( tabPlacement ) - 1241 (tallerIcons ? (tabInsets.top + tabInsets.bottom) : 0); 1242 } 1243 1244 1245 protected int getTabRunOverlay( int tabPlacement ) { 1246 // Tab runs laid out vertically should overlap 1247 // at least as much as the largest slant 1248 if ( tabPlacement == LEFT || tabPlacement == RIGHT ) { 1249 int maxTabHeight = calculateMaxTabHeight(tabPlacement); 1250 return maxTabHeight / 2; 1251 } 1252 return 0; 1253 } 1254 1255 /** 1256 * Returns {@code true} if tab runs should be rotated. 1257 * 1258 * @param tabPlacement a tab placement 1259 * @param selectedRun a selected run 1260 * @return {@code true} if tab runs should be rotated. 1261 */ 1262 protected boolean shouldRotateTabRuns( int tabPlacement, int selectedRun ) { 1263 return false; 1264 } 1265 1266 // Don't pad last run 1267 protected boolean shouldPadTabRun( int tabPlacement, int run ) { 1268 return runCount > 1 && run < runCount - 1; 1269 } 1270 1271 private boolean isLastInRun( int tabIndex ) { 1272 int run = getRunForTab( tabPane.getTabCount(), tabIndex ); 1273 int lastIndex = lastTabInRun( tabPane.getTabCount(), run ); 1274 return tabIndex == lastIndex; 1275 } 1276 1277 /** 1278 * Returns the color to use for the specified tab. 1279 */ 1280 private Color getUnselectedBackgroundAt(int index) { 1281 Color color = tabPane.getBackgroundAt(index); 1282 if (color instanceof UIResource) { 1283 if (unselectedBackground != null) { 1284 return unselectedBackground; 1285 } 1286 } 1287 return color; 1288 } 1289 1290 /** 1291 * Returns the tab index of JTabbedPane the mouse is currently over 1292 */ 1293 int getRolloverTabIndex() { 1294 return getRolloverTab(); 1295 } 1296 1297 /** 1298 * This class should be treated as a "protected" inner class. 1299 * Instantiate it only within subclasses of {@code MetalTabbedPaneUI}. 1300 */ 1301 public class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout { 1302 1303 /** 1304 * Constructs {@code TabbedPaneLayout}. 1305 */ 1306 public TabbedPaneLayout() { 1307 MetalTabbedPaneUI.this.super(); 1308 } 1309 1310 protected void normalizeTabRuns( int tabPlacement, int tabCount, 1311 int start, int max ) { 1312 // Only normalize the runs for top & bottom; normalizing 1313 // doesn't look right for Metal's vertical tabs 1314 // because the last run isn't padded and it looks odd to have 1315 // fat tabs in the first vertical runs, but slimmer ones in the 1316 // last (this effect isn't noticeable for horizontal tabs). 1317 if ( tabPlacement == TOP || tabPlacement == BOTTOM ) { 1318 super.normalizeTabRuns( tabPlacement, tabCount, start, max ); 1319 } 1320 } 1321 1322 // Don't rotate runs! 1323 protected void rotateTabRuns( int tabPlacement, int selectedRun ) { 1324 } 1325 1326 // Don't pad selected tab 1327 protected void padSelectedTab( int tabPlacement, int selectedIndex ) { 1328 } 1329 } 1330 1331 }