1 /* 2 * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.synth; 27 28 import javax.swing.*; 29 import javax.swing.plaf.*; 30 import javax.swing.plaf.basic.*; 31 import javax.swing.text.View; 32 33 import java.awt.*; 34 import java.awt.event.*; 35 import java.beans.PropertyChangeListener; 36 import java.beans.PropertyChangeEvent; 37 import sun.swing.SwingUtilities2; 38 39 /** 40 * Provides the Synth L&F UI delegate for 41 * {@link javax.swing.JTabbedPane}. 42 * 43 * <p>Looks up the {@code selectedTabPadInsets} property from the Style, 44 * which represents additional insets for the selected tab. 45 * 46 * @author Scott Violet 47 * @since 1.7 48 */ 49 public class SynthTabbedPaneUI extends BasicTabbedPaneUI 50 implements PropertyChangeListener, SynthUI { 51 52 /** 53 * <p>If non-zero, tabOverlap indicates the amount that the tab bounds 54 * should be altered such that they would overlap with a tab on either the 55 * leading or trailing end of a run (ie: in TOP, this would be on the left 56 * or right).</p> 57 58 * <p>A positive overlap indicates that tabs should overlap right/down, 59 * while a negative overlap indicates tha tabs should overlap left/up.</p> 60 * 61 * <p>When tabOverlap is specified, it both changes the x position and width 62 * of the tab if in TOP or BOTTOM placement, and changes the y position and 63 * height if in LEFT or RIGHT placement.</p> 64 * 65 * <p>This is done for the following reason. Consider a run of 10 tabs. 66 * There are 9 gaps between these tabs. If you specified a tabOverlap of 67 * "-1", then each of the tabs "x" values will be shifted left. This leaves 68 * 9 pixels of space to the right of the right-most tab unpainted. So, each 69 * tab's width is also extended by 1 pixel to make up the difference.</p> 70 * 71 * <p>This property respects the RTL component orientation.</p> 72 */ 73 private int tabOverlap = 0; 74 75 /** 76 * When a tabbed pane has multiple rows of tabs, this indicates whether 77 * the tabs in the upper row(s) should extend to the base of the tab area, 78 * or whether they should remain at their normal tab height. This does not 79 * affect the bounds of the tabs, only the bounds of area painted by the 80 * tabs. The text position does not change. The result is that the bottom 81 * border of the upper row of tabs becomes fully obscured by the lower tabs, 82 * resulting in a cleaner look. 83 */ 84 private boolean extendTabsToBase = false; 85 86 private SynthContext tabAreaContext; 87 private SynthContext tabContext; 88 private SynthContext tabContentContext; 89 90 private SynthStyle style; 91 private SynthStyle tabStyle; 92 private SynthStyle tabAreaStyle; 93 private SynthStyle tabContentStyle; 94 95 private Rectangle textRect = new Rectangle(); 96 private Rectangle iconRect = new Rectangle(); 97 98 private Rectangle tabAreaBounds = new Rectangle(); 99 100 //added for the Nimbus look and feel, where the tab area is painted differently depending on the 101 //state for the selected tab 102 private boolean tabAreaStatesMatchSelectedTab = false; 103 //added for the Nimbus LAF to ensure that the labels don't move whether the tab is selected or not 104 private boolean nudgeSelectedLabel = true; 105 106 private boolean selectedTabIsPressed = false; 107 108 /** 109 * Creates a new UI object for the given component. 110 * 111 * @param c component to create UI object for 112 * @return the UI object 113 */ 114 public static ComponentUI createUI(JComponent c) { 115 return new SynthTabbedPaneUI(); 116 } 117 118 private boolean scrollableTabLayoutEnabled() { 119 return (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT); 120 } 121 122 /** 123 * {@inheritDoc} 124 */ 125 @Override 126 protected void installDefaults() { 127 updateStyle(tabPane); 128 } 129 130 private void updateStyle(JTabbedPane c) { 131 SynthContext context = getContext(c, ENABLED); 132 SynthStyle oldStyle = style; 133 style = SynthLookAndFeel.updateStyle(context, this); 134 // Add properties other than JComponent colors, Borders and 135 // opacity settings here: 136 if (style != oldStyle) { 137 tabRunOverlay = 138 style.getInt(context, "TabbedPane.tabRunOverlay", 0); 139 tabOverlap = style.getInt(context, "TabbedPane.tabOverlap", 0); 140 extendTabsToBase = style.getBoolean(context, 141 "TabbedPane.extendTabsToBase", false); 142 textIconGap = style.getInt(context, "TabbedPane.textIconGap", 0); 143 selectedTabPadInsets = (Insets)style.get(context, 144 "TabbedPane.selectedTabPadInsets"); 145 if (selectedTabPadInsets == null) { 146 selectedTabPadInsets = new Insets(0, 0, 0, 0); 147 } 148 tabAreaStatesMatchSelectedTab = style.getBoolean(context, 149 "TabbedPane.tabAreaStatesMatchSelectedTab", false); 150 nudgeSelectedLabel = style.getBoolean(context, 151 "TabbedPane.nudgeSelectedLabel", true); 152 if (oldStyle != null) { 153 uninstallKeyboardActions(); 154 installKeyboardActions(); 155 } 156 } 157 context.dispose(); 158 159 if (tabContext != null) { 160 tabContext.dispose(); 161 } 162 tabContext = getContext(c, Region.TABBED_PANE_TAB, ENABLED); 163 this.tabStyle = SynthLookAndFeel.updateStyle(tabContext, this); 164 tabInsets = tabStyle.getInsets(tabContext, null); 165 166 167 if (tabAreaContext != null) { 168 tabAreaContext.dispose(); 169 } 170 tabAreaContext = getContext(c, Region.TABBED_PANE_TAB_AREA, ENABLED); 171 this.tabAreaStyle = SynthLookAndFeel.updateStyle(tabAreaContext, this); 172 tabAreaInsets = tabAreaStyle.getInsets(tabAreaContext, null); 173 174 175 if (tabContentContext != null) { 176 tabContentContext.dispose(); 177 } 178 tabContentContext = getContext(c, Region.TABBED_PANE_CONTENT, ENABLED); 179 this.tabContentStyle = SynthLookAndFeel.updateStyle(tabContentContext, 180 this); 181 contentBorderInsets = 182 tabContentStyle.getInsets(tabContentContext, null); 183 } 184 185 /** 186 * {@inheritDoc} 187 */ 188 @Override 189 protected void installListeners() { 190 super.installListeners(); 191 tabPane.addPropertyChangeListener(this); 192 } 193 194 /** 195 * {@inheritDoc} 196 */ 197 @Override 198 protected void uninstallListeners() { 199 super.uninstallListeners(); 200 tabPane.removePropertyChangeListener(this); 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 protected void uninstallDefaults() { 208 SynthContext context = getContext(tabPane, ENABLED); 209 style.uninstallDefaults(context); 210 context.dispose(); 211 style = null; 212 213 tabStyle.uninstallDefaults(tabContext); 214 tabContext.dispose(); 215 tabContext = null; 216 tabStyle = null; 217 218 tabAreaStyle.uninstallDefaults(tabAreaContext); 219 tabAreaContext.dispose(); 220 tabAreaContext = null; 221 tabAreaStyle = null; 222 223 tabContentStyle.uninstallDefaults(tabContentContext); 224 tabContentContext.dispose(); 225 tabContentContext = null; 226 tabContentStyle = null; 227 } 228 229 /** 230 * {@inheritDoc} 231 */ 232 @Override 233 public SynthContext getContext(JComponent c) { 234 return getContext(c, SynthLookAndFeel.getComponentState(c)); 235 } 236 237 private SynthContext getContext(JComponent c, int state) { 238 return SynthContext.getContext(c, style, state); 239 } 240 241 private SynthContext getContext(JComponent c, Region subregion, int state){ 242 SynthStyle style = null; 243 244 if (subregion == Region.TABBED_PANE_TAB) { 245 style = tabStyle; 246 } 247 else if (subregion == Region.TABBED_PANE_TAB_AREA) { 248 style = tabAreaStyle; 249 } 250 else if (subregion == Region.TABBED_PANE_CONTENT) { 251 style = tabContentStyle; 252 } 253 return SynthContext.getContext(c, subregion, style, state); 254 } 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override 260 protected JButton createScrollButton(int direction) { 261 // added for Nimbus LAF so that it can use the basic arrow buttons 262 // UIManager is queried directly here because this is called before 263 // updateStyle is called so the style can not be queried directly 264 if (UIManager.getBoolean("TabbedPane.useBasicArrows")) { 265 JButton btn = super.createScrollButton(direction); 266 btn.setBorder(BorderFactory.createEmptyBorder()); 267 return btn; 268 } 269 return new SynthScrollableTabButton(direction); 270 } 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override 276 public void propertyChange(PropertyChangeEvent e) { 277 if (SynthLookAndFeel.shouldUpdateStyle(e)) { 278 updateStyle(tabPane); 279 } 280 } 281 282 /** 283 * {@inheritDoc} 284 * 285 * Overridden to keep track of whether the selected tab is also pressed. 286 */ 287 @Override 288 protected MouseListener createMouseListener() { 289 final MouseListener delegate = super.createMouseListener(); 290 final MouseMotionListener delegate2 = (MouseMotionListener)delegate; 291 return new MouseListener() { 292 public void mouseClicked(MouseEvent e) { delegate.mouseClicked(e); } 293 public void mouseEntered(MouseEvent e) { delegate.mouseEntered(e); } 294 public void mouseExited(MouseEvent e) { delegate.mouseExited(e); } 295 296 public void mousePressed(MouseEvent e) { 297 if (!tabPane.isEnabled()) { 298 return; 299 } 300 301 int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); 302 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) { 303 if (tabIndex == tabPane.getSelectedIndex()) { 304 // Clicking on selected tab 305 selectedTabIsPressed = true; 306 //TODO need to just repaint the tab area! 307 tabPane.repaint(); 308 } 309 } 310 311 //forward the event (this will set the selected index, or none at all 312 delegate.mousePressed(e); 313 } 314 315 public void mouseReleased(MouseEvent e) { 316 if (selectedTabIsPressed) { 317 selectedTabIsPressed = false; 318 //TODO need to just repaint the tab area! 319 tabPane.repaint(); 320 } 321 //forward the event 322 delegate.mouseReleased(e); 323 324 //hack: The super method *should* be setting the mouse-over property correctly 325 //here, but it doesn't. That is, when the mouse is released, whatever tab is below the 326 //released mouse should be in rollover state. But, if you select a tab and don't 327 //move the mouse, this doesn't happen. Hence, forwarding the event. 328 delegate2.mouseMoved(e); 329 } 330 }; 331 } 332 333 /** 334 * {@inheritDoc} 335 */ 336 @Override 337 protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) { 338 if (nudgeSelectedLabel) { 339 return super.getTabLabelShiftX(tabPlacement, tabIndex, isSelected); 340 } else { 341 return 0; 342 } 343 } 344 345 /** 346 * {@inheritDoc} 347 */ 348 @Override 349 protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) { 350 if (nudgeSelectedLabel) { 351 return super.getTabLabelShiftY(tabPlacement, tabIndex, isSelected); 352 } else { 353 return 0; 354 } 355 } 356 357 /** 358 * Notifies this UI delegate to repaint the specified component. 359 * This method paints the component background, then calls 360 * the {@link #paint(SynthContext,Graphics)} method. 361 * 362 * <p>In general, this method does not need to be overridden by subclasses. 363 * All Look and Feel rendering code should reside in the {@code paint} method. 364 * 365 * @param g the {@code Graphics} object used for painting 366 * @param c the component being painted 367 * @see #paint(SynthContext,Graphics) 368 */ 369 @Override 370 public void update(Graphics g, JComponent c) { 371 SynthContext context = getContext(c); 372 373 SynthLookAndFeel.update(context, g); 374 context.getPainter().paintTabbedPaneBackground(context, 375 g, 0, 0, c.getWidth(), c.getHeight()); 376 paint(context, g); 377 context.dispose(); 378 } 379 380 /** 381 * {@inheritDoc} 382 */ 383 @Override 384 protected int getBaseline(int tab) { 385 if (tabPane.getTabComponentAt(tab) != null || 386 getTextViewForTab(tab) != null) { 387 return super.getBaseline(tab); 388 } 389 String title = tabPane.getTitleAt(tab); 390 Font font = tabContext.getStyle().getFont(tabContext); 391 FontMetrics metrics = getFontMetrics(font); 392 Icon icon = getIconForTab(tab); 393 textRect.setBounds(0, 0, 0, 0); 394 iconRect.setBounds(0, 0, 0, 0); 395 calcRect.setBounds(0, 0, Short.MAX_VALUE, maxTabHeight); 396 tabContext.getStyle().getGraphicsUtils(tabContext).layoutText( 397 tabContext, metrics, title, icon, SwingUtilities.CENTER, 398 SwingUtilities.CENTER, SwingUtilities.LEADING, 399 SwingUtilities.CENTER, calcRect, 400 iconRect, textRect, textIconGap); 401 return textRect.y + metrics.getAscent() + getBaselineOffset(); 402 } 403 404 /** 405 * {@inheritDoc} 406 */ 407 @Override 408 public void paintBorder(SynthContext context, Graphics g, int x, 409 int y, int w, int h) { 410 context.getPainter().paintTabbedPaneBorder(context, g, x, y, w, h); 411 } 412 413 /** 414 * Paints the specified component according to the Look and Feel. 415 * <p>This method is not used by Synth Look and Feel. 416 * Painting is handled by the {@link #paint(SynthContext,Graphics)} method. 417 * 418 * @param g the {@code Graphics} object used for painting 419 * @param c the component being painted 420 * @see #paint(SynthContext,Graphics) 421 */ 422 @Override 423 public void paint(Graphics g, JComponent c) { 424 SynthContext context = getContext(c); 425 426 paint(context, g); 427 context.dispose(); 428 } 429 430 /** 431 * Paints the specified component. 432 * 433 * @param context context for the component being painted 434 * @param g the {@code Graphics} object used for painting 435 * @see #update(Graphics,JComponent) 436 */ 437 protected void paint(SynthContext context, Graphics g) { 438 int selectedIndex = tabPane.getSelectedIndex(); 439 int tabPlacement = tabPane.getTabPlacement(); 440 441 ensureCurrentLayout(); 442 443 // Paint tab area 444 // If scrollable tabs are enabled, the tab area will be 445 // painted by the scrollable tab panel instead. 446 // 447 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT 448 Insets insets = tabPane.getInsets(); 449 int x = insets.left; 450 int y = insets.top; 451 int width = tabPane.getWidth() - insets.left - insets.right; 452 int height = tabPane.getHeight() - insets.top - insets.bottom; 453 int size; 454 switch(tabPlacement) { 455 case LEFT: 456 width = calculateTabAreaWidth(tabPlacement, runCount, 457 maxTabWidth); 458 break; 459 case RIGHT: 460 size = calculateTabAreaWidth(tabPlacement, runCount, 461 maxTabWidth); 462 x = x + width - size; 463 width = size; 464 break; 465 case BOTTOM: 466 size = calculateTabAreaHeight(tabPlacement, runCount, 467 maxTabHeight); 468 y = y + height - size; 469 height = size; 470 break; 471 case TOP: 472 default: 473 height = calculateTabAreaHeight(tabPlacement, runCount, 474 maxTabHeight); 475 } 476 477 tabAreaBounds.setBounds(x, y, width, height); 478 479 if (g.getClipBounds().intersects(tabAreaBounds)) { 480 paintTabArea(tabAreaContext, g, tabPlacement, 481 selectedIndex, tabAreaBounds); 482 } 483 } 484 485 // Paint content border 486 paintContentBorder(tabContentContext, g, tabPlacement, selectedIndex); 487 } 488 489 protected void paintTabArea(Graphics g, int tabPlacement, 490 int selectedIndex) { 491 // This can be invoked from ScrollabeTabPanel 492 Insets insets = tabPane.getInsets(); 493 int x = insets.left; 494 int y = insets.top; 495 int width = tabPane.getWidth() - insets.left - insets.right; 496 int height = tabPane.getHeight() - insets.top - insets.bottom; 497 498 paintTabArea(tabAreaContext, g, tabPlacement, selectedIndex, 499 new Rectangle(x, y, width, height)); 500 } 501 502 private void paintTabArea(SynthContext ss, Graphics g, 503 int tabPlacement, int selectedIndex, 504 Rectangle tabAreaBounds) { 505 Rectangle clipRect = g.getClipBounds(); 506 507 //if the tab area's states should match that of the selected tab, then 508 //first update the selected tab's states, then set the state 509 //for the tab area to match 510 //otherwise, restore the tab area's state to ENABLED (which is the 511 //only supported state otherwise). 512 if (tabAreaStatesMatchSelectedTab && selectedIndex >= 0) { 513 updateTabContext(selectedIndex, true, selectedTabIsPressed, 514 (getRolloverTab() == selectedIndex), 515 (getFocusIndex() == selectedIndex)); 516 ss.setComponentState(tabContext.getComponentState()); 517 } else { 518 ss.setComponentState(SynthConstants.ENABLED); 519 } 520 521 // Paint the tab area. 522 SynthLookAndFeel.updateSubregion(ss, g, tabAreaBounds); 523 ss.getPainter().paintTabbedPaneTabAreaBackground(ss, g, 524 tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width, 525 tabAreaBounds.height, tabPlacement); 526 ss.getPainter().paintTabbedPaneTabAreaBorder(ss, g, tabAreaBounds.x, 527 tabAreaBounds.y, tabAreaBounds.width, tabAreaBounds.height, 528 tabPlacement); 529 530 int tabCount = tabPane.getTabCount(); 531 532 iconRect.setBounds(0, 0, 0, 0); 533 textRect.setBounds(0, 0, 0, 0); 534 535 // Paint tabRuns of tabs from back to front 536 for (int i = runCount - 1; i >= 0; i--) { 537 int start = tabRuns[i]; 538 int next = tabRuns[(i == runCount - 1)? 0 : i + 1]; 539 int end = (next != 0? next - 1: tabCount - 1); 540 for (int j = start; j <= end; j++) { 541 if (rects[j].intersects(clipRect) && selectedIndex != j) { 542 paintTab(tabContext, g, tabPlacement, rects, j, iconRect, 543 textRect); 544 } 545 } 546 } 547 548 if (selectedIndex >= 0) { 549 if (rects[selectedIndex].intersects(clipRect)) { 550 paintTab(tabContext, g, tabPlacement, rects, selectedIndex, 551 iconRect, textRect); 552 } 553 } 554 } 555 556 /** 557 * {@inheritDoc} 558 */ 559 @Override 560 protected void setRolloverTab(int index) { 561 int oldRolloverTab = getRolloverTab(); 562 super.setRolloverTab(index); 563 564 Rectangle r = null; 565 566 if (oldRolloverTab != index && tabAreaStatesMatchSelectedTab) { 567 //TODO need to just repaint the tab area! 568 tabPane.repaint(); 569 } else { 570 if ((oldRolloverTab >= 0) && (oldRolloverTab < tabPane.getTabCount())) { 571 r = getTabBounds(tabPane, oldRolloverTab); 572 if (r != null) { 573 tabPane.repaint(r); 574 } 575 } 576 577 if (index >= 0) { 578 r = getTabBounds(tabPane, index); 579 if (r != null) { 580 tabPane.repaint(r); 581 } 582 } 583 } 584 } 585 586 private void paintTab(SynthContext ss, Graphics g, 587 int tabPlacement, Rectangle[] rects, int tabIndex, 588 Rectangle iconRect, Rectangle textRect) { 589 Rectangle tabRect = rects[tabIndex]; 590 int selectedIndex = tabPane.getSelectedIndex(); 591 boolean isSelected = selectedIndex == tabIndex; 592 updateTabContext(tabIndex, isSelected, isSelected && selectedTabIsPressed, 593 (getRolloverTab() == tabIndex), 594 (getFocusIndex() == tabIndex)); 595 596 SynthLookAndFeel.updateSubregion(ss, g, tabRect); 597 int x = tabRect.x; 598 int y = tabRect.y; 599 int height = tabRect.height; 600 int width = tabRect.width; 601 int placement = tabPane.getTabPlacement(); 602 if (extendTabsToBase && runCount > 1) { 603 //paint this tab such that its edge closest to the base is equal to 604 //edge of the selected tab closest to the base. In terms of the TOP 605 //tab placement, this will cause the bottom of each tab to be 606 //painted even with the bottom of the selected tab. This is because 607 //in each tab placement (TOP, LEFT, BOTTOM, RIGHT) the selected tab 608 //is closest to the base. 609 if (selectedIndex >= 0) { 610 Rectangle r = rects[selectedIndex]; 611 switch (placement) { 612 case TOP: 613 int bottomY = r.y + r.height; 614 height = bottomY - tabRect.y; 615 break; 616 case LEFT: 617 int rightX = r.x + r.width; 618 width = rightX - tabRect.x; 619 break; 620 case BOTTOM: 621 int topY = r.y; 622 height = (tabRect.y + tabRect.height) - topY; 623 y = topY; 624 break; 625 case RIGHT: 626 int leftX = r.x; 627 width = (tabRect.x + tabRect.width) - leftX; 628 x = leftX; 629 break; 630 } 631 } 632 } 633 tabContext.getPainter().paintTabbedPaneTabBackground(tabContext, g, 634 x, y, width, height, tabIndex, placement); 635 tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g, 636 x, y, width, height, tabIndex, placement); 637 638 if (tabPane.getTabComponentAt(tabIndex) == null) { 639 String title = tabPane.getTitleAt(tabIndex); 640 Font font = ss.getStyle().getFont(ss); 641 FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font); 642 Icon icon = getIconForTab(tabIndex); 643 644 layoutLabel(ss, tabPlacement, metrics, tabIndex, title, icon, 645 tabRect, iconRect, textRect, isSelected); 646 647 paintText(ss, g, tabPlacement, font, metrics, 648 tabIndex, title, textRect, isSelected); 649 650 paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); 651 } 652 } 653 654 private void layoutLabel(SynthContext ss, int tabPlacement, 655 FontMetrics metrics, int tabIndex, 656 String title, Icon icon, 657 Rectangle tabRect, Rectangle iconRect, 658 Rectangle textRect, boolean isSelected ) { 659 View v = getTextViewForTab(tabIndex); 660 if (v != null) { 661 tabPane.putClientProperty("html", v); 662 } 663 664 textRect.x = textRect.y = iconRect.x = iconRect.y = 0; 665 666 ss.getStyle().getGraphicsUtils(ss).layoutText(ss, metrics, title, 667 icon, SwingUtilities.CENTER, SwingUtilities.CENTER, 668 SwingUtilities.LEADING, SwingUtilities.CENTER, 669 tabRect, iconRect, textRect, textIconGap); 670 671 tabPane.putClientProperty("html", null); 672 673 int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected); 674 int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected); 675 iconRect.x += xNudge; 676 iconRect.y += yNudge; 677 textRect.x += xNudge; 678 textRect.y += yNudge; 679 } 680 681 private void paintText(SynthContext ss, 682 Graphics g, int tabPlacement, 683 Font font, FontMetrics metrics, int tabIndex, 684 String title, Rectangle textRect, 685 boolean isSelected) { 686 g.setFont(font); 687 688 View v = getTextViewForTab(tabIndex); 689 if (v != null) { 690 // html 691 v.paint(g, textRect); 692 } else { 693 // plain text 694 int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex); 695 696 g.setColor(ss.getStyle().getColor(ss, ColorType.TEXT_FOREGROUND)); 697 ss.getStyle().getGraphicsUtils(ss).paintText(ss, g, title, 698 textRect, mnemIndex); 699 } 700 } 701 702 703 private void paintContentBorder(SynthContext ss, Graphics g, 704 int tabPlacement, int selectedIndex) { 705 int width = tabPane.getWidth(); 706 int height = tabPane.getHeight(); 707 Insets insets = tabPane.getInsets(); 708 709 int x = insets.left; 710 int y = insets.top; 711 int w = width - insets.right - insets.left; 712 int h = height - insets.top - insets.bottom; 713 714 switch(tabPlacement) { 715 case LEFT: 716 x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 717 w -= (x - insets.left); 718 break; 719 case RIGHT: 720 w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 721 break; 722 case BOTTOM: 723 h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 724 break; 725 case TOP: 726 default: 727 y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 728 h -= (y - insets.top); 729 } 730 SynthLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w, h)); 731 ss.getPainter().paintTabbedPaneContentBackground(ss, g, x, y, 732 w, h); 733 ss.getPainter().paintTabbedPaneContentBorder(ss, g, x, y, w, h); 734 } 735 736 private void ensureCurrentLayout() { 737 if (!tabPane.isValid()) { 738 tabPane.validate(); 739 } 740 /* If tabPane doesn't have a peer yet, the validate() call will 741 * silently fail. We handle that by forcing a layout if tabPane 742 * is still invalid. See bug 4237677. 743 */ 744 if (!tabPane.isValid()) { 745 TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout(); 746 layout.calculateLayoutInfo(); 747 } 748 } 749 750 /** 751 * {@inheritDoc} 752 */ 753 @Override 754 protected int calculateMaxTabHeight(int tabPlacement) { 755 FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont( 756 tabContext)); 757 int tabCount = tabPane.getTabCount(); 758 int result = 0; 759 int fontHeight = metrics.getHeight(); 760 for(int i = 0; i < tabCount; i++) { 761 result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result); 762 } 763 return result; 764 } 765 766 /** 767 * {@inheritDoc} 768 */ 769 @Override 770 protected int calculateTabWidth(int tabPlacement, int tabIndex, 771 FontMetrics metrics) { 772 Icon icon = getIconForTab(tabIndex); 773 Insets tabInsets = getTabInsets(tabPlacement, tabIndex); 774 int width = tabInsets.left + tabInsets.right; 775 Component tabComponent = tabPane.getTabComponentAt(tabIndex); 776 if (tabComponent != null) { 777 width += tabComponent.getPreferredSize().width; 778 } else { 779 if (icon != null) { 780 width += icon.getIconWidth() + textIconGap; 781 } 782 View v = getTextViewForTab(tabIndex); 783 if (v != null) { 784 // html 785 width += (int) v.getPreferredSpan(View.X_AXIS); 786 } else { 787 // plain text 788 String title = tabPane.getTitleAt(tabIndex); 789 width += tabContext.getStyle().getGraphicsUtils(tabContext). 790 computeStringWidth(tabContext, metrics.getFont(), 791 metrics, title); 792 } 793 } 794 return width; 795 } 796 797 /** 798 * {@inheritDoc} 799 */ 800 @Override 801 protected int calculateMaxTabWidth(int tabPlacement) { 802 FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont( 803 tabContext)); 804 int tabCount = tabPane.getTabCount(); 805 int result = 0; 806 for(int i = 0; i < tabCount; i++) { 807 result = Math.max(calculateTabWidth(tabPlacement, i, metrics), 808 result); 809 } 810 return result; 811 } 812 813 /** 814 * {@inheritDoc} 815 */ 816 @Override 817 protected Insets getTabInsets(int tabPlacement, int tabIndex) { 818 updateTabContext(tabIndex, false, false, false, 819 (getFocusIndex() == tabIndex)); 820 return tabInsets; 821 } 822 823 /** 824 * {@inheritDoc} 825 */ 826 @Override 827 protected FontMetrics getFontMetrics() { 828 return getFontMetrics(tabContext.getStyle().getFont(tabContext)); 829 } 830 831 private FontMetrics getFontMetrics(Font font) { 832 return tabPane.getFontMetrics(font); 833 } 834 835 private void updateTabContext(int index, boolean selected, 836 boolean isMouseDown, boolean isMouseOver, boolean hasFocus) { 837 int state = 0; 838 if (!tabPane.isEnabled() || !tabPane.isEnabledAt(index)) { 839 state |= SynthConstants.DISABLED; 840 if (selected) { 841 state |= SynthConstants.SELECTED; 842 } 843 } 844 else if (selected) { 845 state |= (SynthConstants.ENABLED | SynthConstants.SELECTED); 846 if (isMouseOver && UIManager.getBoolean("TabbedPane.isTabRollover")) { 847 state |= SynthConstants.MOUSE_OVER; 848 } 849 } 850 else if (isMouseOver) { 851 state |= (SynthConstants.ENABLED | SynthConstants.MOUSE_OVER); 852 } 853 else { 854 state = SynthLookAndFeel.getComponentState(tabPane); 855 state &= ~SynthConstants.FOCUSED; // don't use tabbedpane focus state 856 } 857 if (hasFocus && tabPane.hasFocus()) { 858 state |= SynthConstants.FOCUSED; // individual tab has focus 859 } 860 if (isMouseDown) { 861 state |= SynthConstants.PRESSED; 862 } 863 864 tabContext.setComponentState(state); 865 } 866 867 /** 868 * {@inheritDoc} 869 * 870 * Overridden to create a TabbedPaneLayout subclass which takes into 871 * account tabOverlap. 872 */ 873 @Override 874 protected LayoutManager createLayoutManager() { 875 if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) { 876 return super.createLayoutManager(); 877 } else { /* WRAP_TAB_LAYOUT */ 878 return new TabbedPaneLayout() { 879 @Override 880 public void calculateLayoutInfo() { 881 super.calculateLayoutInfo(); 882 //shift all the tabs, if necessary 883 if (tabOverlap != 0) { 884 int tabCount = tabPane.getTabCount(); 885 //left-to-right/right-to-left only affects layout 886 //when placement is TOP or BOTTOM 887 boolean ltr = tabPane.getComponentOrientation().isLeftToRight(); 888 for (int i = runCount - 1; i >= 0; i--) { 889 int start = tabRuns[i]; 890 int next = tabRuns[(i == runCount - 1)? 0 : i + 1]; 891 int end = (next != 0? next - 1: tabCount - 1); 892 for (int j = start+1; j <= end; j++) { 893 // xshift and yshift represent the amount & 894 // direction to shift the tab in their 895 // respective axis. 896 int xshift = 0; 897 int yshift = 0; 898 // configure xshift and y shift based on tab 899 // position and ltr/rtl 900 switch (tabPane.getTabPlacement()) { 901 case JTabbedPane.TOP: 902 case JTabbedPane.BOTTOM: 903 xshift = ltr ? tabOverlap : -tabOverlap; 904 break; 905 case JTabbedPane.LEFT: 906 case JTabbedPane.RIGHT: 907 yshift = tabOverlap; 908 break; 909 default: //do nothing 910 } 911 rects[j].x += xshift; 912 rects[j].y += yshift; 913 rects[j].width += Math.abs(xshift); 914 rects[j].height += Math.abs(yshift); 915 } 916 } 917 } 918 } 919 }; 920 } 921 } 922 923 @SuppressWarnings("serial") // Superclass is not serializable across versions 924 private class SynthScrollableTabButton extends SynthArrowButton implements 925 UIResource { 926 public SynthScrollableTabButton(int direction) { 927 super(direction); 928 setName("TabbedPane.button"); 929 } 930 } 931 }