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