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(SynthContext.class, c, 239 SynthLookAndFeel.getRegion(c),style, state); 240 } 241 242 private SynthContext getContext(JComponent c, Region subregion, int state){ 243 SynthStyle style = null; 244 Class klass = SynthContext.class; 245 246 if (subregion == Region.TABBED_PANE_TAB) { 247 style = tabStyle; 248 } 249 else if (subregion == Region.TABBED_PANE_TAB_AREA) { 250 style = tabAreaStyle; 251 } 252 else if (subregion == Region.TABBED_PANE_CONTENT) { 253 style = tabContentStyle; 254 } 255 return SynthContext.getContext(klass, c, subregion, style, state); 256 } 257 258 /** 259 * {@inheritDoc} 260 */ 261 @Override 262 protected JButton createScrollButton(int direction) { 263 // added for Nimbus LAF so that it can use the basic arrow buttons 264 // UIManager is queried directly here because this is called before 265 // updateStyle is called so the style can not be queried directly 266 if (UIManager.getBoolean("TabbedPane.useBasicArrows")) { 267 JButton btn = super.createScrollButton(direction); 268 btn.setBorder(BorderFactory.createEmptyBorder()); 269 return btn; 270 } 271 return new SynthScrollableTabButton(direction); 272 } 273 274 /** 275 * {@inheritDoc} 276 */ 277 @Override 278 public void propertyChange(PropertyChangeEvent e) { 279 if (SynthLookAndFeel.shouldUpdateStyle(e)) { 280 updateStyle(tabPane); 281 } 282 } 283 284 /** 285 * {@inheritDoc} 286 * 287 * Overridden to keep track of whether the selected tab is also pressed. 288 */ 289 @Override 290 protected MouseListener createMouseListener() { 291 final MouseListener delegate = super.createMouseListener(); 292 final MouseMotionListener delegate2 = (MouseMotionListener)delegate; 293 return new MouseListener() { 294 public void mouseClicked(MouseEvent e) { delegate.mouseClicked(e); } 295 public void mouseEntered(MouseEvent e) { delegate.mouseEntered(e); } 296 public void mouseExited(MouseEvent e) { delegate.mouseExited(e); } 297 298 public void mousePressed(MouseEvent e) { 299 if (!tabPane.isEnabled()) { 300 return; 301 } 302 303 int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); 304 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) { 305 if (tabIndex == tabPane.getSelectedIndex()) { 306 // Clicking on selected tab 307 selectedTabIsPressed = true; 308 //TODO need to just repaint the tab area! 309 tabPane.repaint(); 310 } 311 } 312 313 //forward the event (this will set the selected index, or none at all 314 delegate.mousePressed(e); 315 } 316 317 public void mouseReleased(MouseEvent e) { 318 if (selectedTabIsPressed) { 319 selectedTabIsPressed = false; 320 //TODO need to just repaint the tab area! 321 tabPane.repaint(); 322 } 323 //forward the event 324 delegate.mouseReleased(e); 325 326 //hack: The super method *should* be setting the mouse-over property correctly 327 //here, but it doesn't. That is, when the mouse is released, whatever tab is below the 328 //released mouse should be in rollover state. But, if you select a tab and don't 329 //move the mouse, this doesn't happen. Hence, forwarding the event. 330 delegate2.mouseMoved(e); 331 } 332 }; 333 } 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override 339 protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) { 340 if (nudgeSelectedLabel) { 341 return super.getTabLabelShiftX(tabPlacement, tabIndex, isSelected); 342 } else { 343 return 0; 344 } 345 } 346 347 /** 348 * {@inheritDoc} 349 */ 350 @Override 351 protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) { 352 if (nudgeSelectedLabel) { 353 return super.getTabLabelShiftY(tabPlacement, tabIndex, isSelected); 354 } else { 355 return 0; 356 } 357 } 358 359 /** 360 * Notifies this UI delegate to repaint the specified component. 361 * This method paints the component background, then calls 362 * the {@link #paint(SynthContext,Graphics)} method. 363 * 364 * <p>In general, this method does not need to be overridden by subclasses. 365 * All Look and Feel rendering code should reside in the {@code paint} method. 366 * 367 * @param g the {@code Graphics} object used for painting 368 * @param c the component being painted 369 * @see #paint(SynthContext,Graphics) 370 */ 371 @Override 372 public void update(Graphics g, JComponent c) { 373 SynthContext context = getContext(c); 374 375 SynthLookAndFeel.update(context, g); 376 context.getPainter().paintTabbedPaneBackground(context, 377 g, 0, 0, c.getWidth(), c.getHeight()); 378 paint(context, g); 379 context.dispose(); 380 } 381 382 /** 383 * {@inheritDoc} 384 */ 385 @Override 386 protected int getBaseline(int tab) { 387 if (tabPane.getTabComponentAt(tab) != null || 388 getTextViewForTab(tab) != null) { 389 return super.getBaseline(tab); 390 } 391 String title = tabPane.getTitleAt(tab); 392 Font font = tabContext.getStyle().getFont(tabContext); 393 FontMetrics metrics = getFontMetrics(font); 394 Icon icon = getIconForTab(tab); 395 textRect.setBounds(0, 0, 0, 0); 396 iconRect.setBounds(0, 0, 0, 0); 397 calcRect.setBounds(0, 0, Short.MAX_VALUE, maxTabHeight); 398 tabContext.getStyle().getGraphicsUtils(tabContext).layoutText( 399 tabContext, metrics, title, icon, SwingUtilities.CENTER, 400 SwingUtilities.CENTER, SwingUtilities.LEADING, 401 SwingUtilities.CENTER, calcRect, 402 iconRect, textRect, textIconGap); 403 return textRect.y + metrics.getAscent() + getBaselineOffset(); 404 } 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override 410 public void paintBorder(SynthContext context, Graphics g, int x, 411 int y, int w, int h) { 412 context.getPainter().paintTabbedPaneBorder(context, g, x, y, w, h); 413 } 414 415 /** 416 * Paints the specified component according to the Look and Feel. 417 * <p>This method is not used by Synth Look and Feel. 418 * Painting is handled by the {@link #paint(SynthContext,Graphics)} method. 419 * 420 * @param g the {@code Graphics} object used for painting 421 * @param c the component being painted 422 * @see #paint(SynthContext,Graphics) 423 */ 424 @Override 425 public void paint(Graphics g, JComponent c) { 426 SynthContext context = getContext(c); 427 428 paint(context, g); 429 context.dispose(); 430 } 431 432 /** 433 * Paints the specified component. 434 * 435 * @param context context for the component being painted 436 * @param g the {@code Graphics} object used for painting 437 * @see #update(Graphics,JComponent) 438 */ 439 protected void paint(SynthContext context, Graphics g) { 440 int selectedIndex = tabPane.getSelectedIndex(); 441 int tabPlacement = tabPane.getTabPlacement(); 442 443 ensureCurrentLayout(); 444 445 // Paint tab area 446 // If scrollable tabs are enabled, the tab area will be 447 // painted by the scrollable tab panel instead. 448 // 449 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT 450 Insets insets = tabPane.getInsets(); 451 int x = insets.left; 452 int y = insets.top; 453 int width = tabPane.getWidth() - insets.left - insets.right; 454 int height = tabPane.getHeight() - insets.top - insets.bottom; 455 int size; 456 switch(tabPlacement) { 457 case LEFT: 458 width = calculateTabAreaWidth(tabPlacement, runCount, 459 maxTabWidth); 460 break; 461 case RIGHT: 462 size = calculateTabAreaWidth(tabPlacement, runCount, 463 maxTabWidth); 464 x = x + width - size; 465 width = size; 466 break; 467 case BOTTOM: 468 size = calculateTabAreaHeight(tabPlacement, runCount, 469 maxTabHeight); 470 y = y + height - size; 471 height = size; 472 break; 473 case TOP: 474 default: 475 height = calculateTabAreaHeight(tabPlacement, runCount, 476 maxTabHeight); 477 } 478 479 tabAreaBounds.setBounds(x, y, width, height); 480 481 if (g.getClipBounds().intersects(tabAreaBounds)) { 482 paintTabArea(tabAreaContext, g, tabPlacement, 483 selectedIndex, tabAreaBounds); 484 } 485 } 486 487 // Paint content border 488 paintContentBorder(tabContentContext, g, tabPlacement, selectedIndex); 489 } 490 491 protected void paintTabArea(Graphics g, int tabPlacement, 492 int selectedIndex) { 493 // This can be invoked from ScrollabeTabPanel 494 Insets insets = tabPane.getInsets(); 495 int x = insets.left; 496 int y = insets.top; 497 int width = tabPane.getWidth() - insets.left - insets.right; 498 int height = tabPane.getHeight() - insets.top - insets.bottom; 499 500 paintTabArea(tabAreaContext, g, tabPlacement, selectedIndex, 501 new Rectangle(x, y, width, height)); 502 } 503 504 private void paintTabArea(SynthContext ss, Graphics g, 505 int tabPlacement, int selectedIndex, 506 Rectangle tabAreaBounds) { 507 Rectangle clipRect = g.getClipBounds(); 508 509 //if the tab area's states should match that of the selected tab, then 510 //first update the selected tab's states, then set the state 511 //for the tab area to match 512 //otherwise, restore the tab area's state to ENABLED (which is the 513 //only supported state otherwise). 514 if (tabAreaStatesMatchSelectedTab && selectedIndex >= 0) { 515 updateTabContext(selectedIndex, true, selectedTabIsPressed, 516 (getRolloverTab() == selectedIndex), 517 (getFocusIndex() == selectedIndex)); 518 ss.setComponentState(tabContext.getComponentState()); 519 } else { 520 ss.setComponentState(SynthConstants.ENABLED); 521 } 522 523 // Paint the tab area. 524 SynthLookAndFeel.updateSubregion(ss, g, tabAreaBounds); 525 ss.getPainter().paintTabbedPaneTabAreaBackground(ss, g, 526 tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width, 527 tabAreaBounds.height, tabPlacement); 528 ss.getPainter().paintTabbedPaneTabAreaBorder(ss, g, tabAreaBounds.x, 529 tabAreaBounds.y, tabAreaBounds.width, tabAreaBounds.height, 530 tabPlacement); 531 532 int tabCount = tabPane.getTabCount(); 533 534 iconRect.setBounds(0, 0, 0, 0); 535 textRect.setBounds(0, 0, 0, 0); 536 537 // Paint tabRuns of tabs from back to front 538 for (int i = runCount - 1; i >= 0; i--) { 539 int start = tabRuns[i]; 540 int next = tabRuns[(i == runCount - 1)? 0 : i + 1]; 541 int end = (next != 0? next - 1: tabCount - 1); 542 for (int j = start; j <= end; j++) { 543 if (rects[j].intersects(clipRect) && selectedIndex != j) { 544 paintTab(tabContext, g, tabPlacement, rects, j, iconRect, 545 textRect); 546 } 547 } 548 } 549 550 if (selectedIndex >= 0) { 551 if (rects[selectedIndex].intersects(clipRect)) { 552 paintTab(tabContext, g, tabPlacement, rects, selectedIndex, 553 iconRect, textRect); 554 } 555 } 556 } 557 558 /** 559 * {@inheritDoc} 560 */ 561 @Override 562 protected void setRolloverTab(int index) { 563 int oldRolloverTab = getRolloverTab(); 564 super.setRolloverTab(index); 565 566 Rectangle r = null; 567 568 if (oldRolloverTab != index && tabAreaStatesMatchSelectedTab) { 569 //TODO need to just repaint the tab area! 570 tabPane.repaint(); 571 } else { 572 if ((oldRolloverTab >= 0) && (oldRolloverTab < tabPane.getTabCount())) { 573 r = getTabBounds(tabPane, oldRolloverTab); 574 if (r != null) { 575 tabPane.repaint(r); 576 } 577 } 578 579 if (index >= 0) { 580 r = getTabBounds(tabPane, index); 581 if (r != null) { 582 tabPane.repaint(r); 583 } 584 } 585 } 586 } 587 588 private void paintTab(SynthContext ss, Graphics g, 589 int tabPlacement, Rectangle[] rects, int tabIndex, 590 Rectangle iconRect, Rectangle textRect) { 591 Rectangle tabRect = rects[tabIndex]; 592 int selectedIndex = tabPane.getSelectedIndex(); 593 boolean isSelected = selectedIndex == tabIndex; 594 updateTabContext(tabIndex, isSelected, isSelected && selectedTabIsPressed, 595 (getRolloverTab() == tabIndex), 596 (getFocusIndex() == tabIndex)); 597 598 SynthLookAndFeel.updateSubregion(ss, g, tabRect); 599 int x = tabRect.x; 600 int y = tabRect.y; 601 int height = tabRect.height; 602 int width = tabRect.width; 603 int placement = tabPane.getTabPlacement(); 604 if (extendTabsToBase && runCount > 1) { 605 //paint this tab such that its edge closest to the base is equal to 606 //edge of the selected tab closest to the base. In terms of the TOP 607 //tab placement, this will cause the bottom of each tab to be 608 //painted even with the bottom of the selected tab. This is because 609 //in each tab placement (TOP, LEFT, BOTTOM, RIGHT) the selected tab 610 //is closest to the base. 611 if (selectedIndex >= 0) { 612 Rectangle r = rects[selectedIndex]; 613 switch (placement) { 614 case TOP: 615 int bottomY = r.y + r.height; 616 height = bottomY - tabRect.y; 617 break; 618 case LEFT: 619 int rightX = r.x + r.width; 620 width = rightX - tabRect.x; 621 break; 622 case BOTTOM: 623 int topY = r.y; 624 height = (tabRect.y + tabRect.height) - topY; 625 y = topY; 626 break; 627 case RIGHT: 628 int leftX = r.x; 629 width = (tabRect.x + tabRect.width) - leftX; 630 x = leftX; 631 break; 632 } 633 } 634 } 635 tabContext.getPainter().paintTabbedPaneTabBackground(tabContext, g, 636 x, y, width, height, tabIndex, placement); 637 tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g, 638 x, y, width, height, tabIndex, placement); 639 640 if (tabPane.getTabComponentAt(tabIndex) == null) { 641 String title = tabPane.getTitleAt(tabIndex); 642 Font font = ss.getStyle().getFont(ss); 643 FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font); 644 Icon icon = getIconForTab(tabIndex); 645 646 layoutLabel(ss, tabPlacement, metrics, tabIndex, title, icon, 647 tabRect, iconRect, textRect, isSelected); 648 649 paintText(ss, g, tabPlacement, font, metrics, 650 tabIndex, title, textRect, isSelected); 651 652 paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); 653 } 654 } 655 656 private void layoutLabel(SynthContext ss, int tabPlacement, 657 FontMetrics metrics, int tabIndex, 658 String title, Icon icon, 659 Rectangle tabRect, Rectangle iconRect, 660 Rectangle textRect, boolean isSelected ) { 661 View v = getTextViewForTab(tabIndex); 662 if (v != null) { 663 tabPane.putClientProperty("html", v); 664 } 665 666 textRect.x = textRect.y = iconRect.x = iconRect.y = 0; 667 668 ss.getStyle().getGraphicsUtils(ss).layoutText(ss, metrics, title, 669 icon, SwingUtilities.CENTER, SwingUtilities.CENTER, 670 SwingUtilities.LEADING, SwingUtilities.CENTER, 671 tabRect, iconRect, textRect, textIconGap); 672 673 tabPane.putClientProperty("html", null); 674 675 int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected); 676 int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected); 677 iconRect.x += xNudge; 678 iconRect.y += yNudge; 679 textRect.x += xNudge; 680 textRect.y += yNudge; 681 } 682 683 private void paintText(SynthContext ss, 684 Graphics g, int tabPlacement, 685 Font font, FontMetrics metrics, int tabIndex, 686 String title, Rectangle textRect, 687 boolean isSelected) { 688 g.setFont(font); 689 690 View v = getTextViewForTab(tabIndex); 691 if (v != null) { 692 // html 693 v.paint(g, textRect); 694 } else { 695 // plain text 696 int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex); 697 698 g.setColor(ss.getStyle().getColor(ss, ColorType.TEXT_FOREGROUND)); 699 ss.getStyle().getGraphicsUtils(ss).paintText(ss, g, title, 700 textRect, mnemIndex); 701 } 702 } 703 704 705 private void paintContentBorder(SynthContext ss, Graphics g, 706 int tabPlacement, int selectedIndex) { 707 int width = tabPane.getWidth(); 708 int height = tabPane.getHeight(); 709 Insets insets = tabPane.getInsets(); 710 711 int x = insets.left; 712 int y = insets.top; 713 int w = width - insets.right - insets.left; 714 int h = height - insets.top - insets.bottom; 715 716 switch(tabPlacement) { 717 case LEFT: 718 x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 719 w -= (x - insets.left); 720 break; 721 case RIGHT: 722 w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 723 break; 724 case BOTTOM: 725 h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 726 break; 727 case TOP: 728 default: 729 y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 730 h -= (y - insets.top); 731 } 732 SynthLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w, h)); 733 ss.getPainter().paintTabbedPaneContentBackground(ss, g, x, y, 734 w, h); 735 ss.getPainter().paintTabbedPaneContentBorder(ss, g, x, y, w, h); 736 } 737 738 private void ensureCurrentLayout() { 739 if (!tabPane.isValid()) { 740 tabPane.validate(); 741 } 742 /* If tabPane doesn't have a peer yet, the validate() call will 743 * silently fail. We handle that by forcing a layout if tabPane 744 * is still invalid. See bug 4237677. 745 */ 746 if (!tabPane.isValid()) { 747 TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout(); 748 layout.calculateLayoutInfo(); 749 } 750 } 751 752 /** 753 * {@inheritDoc} 754 */ 755 @Override 756 protected int calculateMaxTabHeight(int tabPlacement) { 757 FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont( 758 tabContext)); 759 int tabCount = tabPane.getTabCount(); 760 int result = 0; 761 int fontHeight = metrics.getHeight(); 762 for(int i = 0; i < tabCount; i++) { 763 result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result); 764 } 765 return result; 766 } 767 768 /** 769 * {@inheritDoc} 770 */ 771 @Override 772 protected int calculateTabWidth(int tabPlacement, int tabIndex, 773 FontMetrics metrics) { 774 Icon icon = getIconForTab(tabIndex); 775 Insets tabInsets = getTabInsets(tabPlacement, tabIndex); 776 int width = tabInsets.left + tabInsets.right; 777 Component tabComponent = tabPane.getTabComponentAt(tabIndex); 778 if (tabComponent != null) { 779 width += tabComponent.getPreferredSize().width; 780 } else { 781 if (icon != null) { 782 width += icon.getIconWidth() + textIconGap; 783 } 784 View v = getTextViewForTab(tabIndex); 785 if (v != null) { 786 // html 787 width += (int) v.getPreferredSpan(View.X_AXIS); 788 } else { 789 // plain text 790 String title = tabPane.getTitleAt(tabIndex); 791 width += tabContext.getStyle().getGraphicsUtils(tabContext). 792 computeStringWidth(tabContext, metrics.getFont(), 793 metrics, title); 794 } 795 } 796 return width; 797 } 798 799 /** 800 * {@inheritDoc} 801 */ 802 @Override 803 protected int calculateMaxTabWidth(int tabPlacement) { 804 FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont( 805 tabContext)); 806 int tabCount = tabPane.getTabCount(); 807 int result = 0; 808 for(int i = 0; i < tabCount; i++) { 809 result = Math.max(calculateTabWidth(tabPlacement, i, metrics), 810 result); 811 } 812 return result; 813 } 814 815 /** 816 * {@inheritDoc} 817 */ 818 @Override 819 protected Insets getTabInsets(int tabPlacement, int tabIndex) { 820 updateTabContext(tabIndex, false, false, false, 821 (getFocusIndex() == tabIndex)); 822 return tabInsets; 823 } 824 825 /** 826 * {@inheritDoc} 827 */ 828 @Override 829 protected FontMetrics getFontMetrics() { 830 return getFontMetrics(tabContext.getStyle().getFont(tabContext)); 831 } 832 833 private FontMetrics getFontMetrics(Font font) { 834 return tabPane.getFontMetrics(font); 835 } 836 837 private void updateTabContext(int index, boolean selected, 838 boolean isMouseDown, boolean isMouseOver, boolean hasFocus) { 839 int state = 0; 840 if (!tabPane.isEnabled() || !tabPane.isEnabledAt(index)) { 841 state |= SynthConstants.DISABLED; 842 if (selected) { 843 state |= SynthConstants.SELECTED; 844 } 845 } 846 else if (selected) { 847 state |= (SynthConstants.ENABLED | SynthConstants.SELECTED); 848 if (isMouseOver && UIManager.getBoolean("TabbedPane.isTabRollover")) { 849 state |= SynthConstants.MOUSE_OVER; 850 } 851 } 852 else if (isMouseOver) { 853 state |= (SynthConstants.ENABLED | SynthConstants.MOUSE_OVER); 854 } 855 else { 856 state = SynthLookAndFeel.getComponentState(tabPane); 857 state &= ~SynthConstants.FOCUSED; // don't use tabbedpane focus state 858 } 859 if (hasFocus && tabPane.hasFocus()) { 860 state |= SynthConstants.FOCUSED; // individual tab has focus 861 } 862 if (isMouseDown) { 863 state |= SynthConstants.PRESSED; 864 } 865 866 tabContext.setComponentState(state); 867 } 868 869 /** 870 * {@inheritDoc} 871 * 872 * Overridden to create a TabbedPaneLayout subclass which takes into 873 * account tabOverlap. 874 */ 875 @Override 876 protected LayoutManager createLayoutManager() { 877 if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) { 878 return super.createLayoutManager(); 879 } else { /* WRAP_TAB_LAYOUT */ 880 return new TabbedPaneLayout() { 881 @Override 882 public void calculateLayoutInfo() { 883 super.calculateLayoutInfo(); 884 //shift all the tabs, if necessary 885 if (tabOverlap != 0) { 886 int tabCount = tabPane.getTabCount(); 887 //left-to-right/right-to-left only affects layout 888 //when placement is TOP or BOTTOM 889 boolean ltr = tabPane.getComponentOrientation().isLeftToRight(); 890 for (int i = runCount - 1; i >= 0; i--) { 891 int start = tabRuns[i]; 892 int next = tabRuns[(i == runCount - 1)? 0 : i + 1]; 893 int end = (next != 0? next - 1: tabCount - 1); 894 for (int j = start+1; j <= end; j++) { 895 // xshift and yshift represent the amount & 896 // direction to shift the tab in their 897 // respective axis. 898 int xshift = 0; 899 int yshift = 0; 900 // configure xshift and y shift based on tab 901 // position and ltr/rtl 902 switch (tabPane.getTabPlacement()) { 903 case JTabbedPane.TOP: 904 case JTabbedPane.BOTTOM: 905 xshift = ltr ? tabOverlap : -tabOverlap; 906 break; 907 case JTabbedPane.LEFT: 908 case JTabbedPane.RIGHT: 909 yshift = tabOverlap; 910 break; 911 default: //do nothing 912 } 913 rects[j].x += xshift; 914 rects[j].y += yshift; 915 rects[j].width += Math.abs(xshift); 916 rects[j].height += Math.abs(yshift); 917 } 918 } 919 } 920 } 921 }; 922 } 923 } 924 925 @SuppressWarnings("serial") // Superclass is not serializable across versions 926 private class SynthScrollableTabButton extends SynthArrowButton implements 927 UIResource { 928 public SynthScrollableTabButton(int direction) { 929 super(direction); 930 setName("TabbedPane.button"); 931 } 932 } 933 }