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 */ createUI( JComponent x )89 public static ComponentUI createUI( JComponent x ) { 90 return new MetalTabbedPaneUI(); 91 } 92 createLayoutManager()93 protected LayoutManager createLayoutManager() { 94 if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) { 95 return super.createLayoutManager(); 96 } 97 return new TabbedPaneLayout(); 98 } 99 installDefaults()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 paintTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected)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 */ paintTopTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )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 */ shouldFillGap( int currentRun, int tabIndex, int x, int y )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 */ getColorForGap( int currentRun, int x, int y )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 */ paintLeftTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )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 */ paintBottomTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )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 */ paintRightTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )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 update( Graphics g, JComponent c )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 paintTabBackground( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected )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 */ getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected )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 */ getTabLabelShiftY( int tabPlacement, int tabIndex, boolean isSelected )879 protected int getTabLabelShiftY( int tabPlacement, int tabIndex, boolean isSelected ) { 880 return 0; 881 } 882 883 /** 884 * {@inheritDoc} 885 * 886 * @since 1.6 887 */ getBaselineOffset()888 protected int getBaselineOffset() { 889 return 0; 890 } 891 paint( Graphics g, JComponent c )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 */ paintHighlightBelowTab( )939 protected void paintHighlightBelowTab( ) { 940 941 } 942 943 paintFocusIndicator(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect, boolean isSelected)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 paintContentBorderTopEdge( Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h )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 } 1332