1 /* 2 * Copyright (c) 1997, 2010, 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.basic; 27 28 import sun.swing.SwingUtilities2; 29 30 import javax.swing.*; 31 import javax.swing.event.*; 32 import javax.swing.plaf.*; 33 import javax.swing.text.View; 34 35 import java.awt.*; 36 import java.awt.event.*; 37 import java.beans.PropertyChangeListener; 38 import java.beans.PropertyChangeEvent; 39 import java.util.Vector; 40 import java.util.Hashtable; 41 42 import sun.swing.DefaultLookup; 43 import sun.swing.UIAction; 44 45 /** 46 * A Basic L&F implementation of TabbedPaneUI. 47 * 48 * @author Amy Fowler 49 * @author Philip Milne 50 * @author Steve Wilson 51 * @author Tom Santos 52 * @author Dave Moore 53 */ 54 public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants { 55 56 57 // Instance variables initialized at installation 58 59 protected JTabbedPane tabPane; 60 61 protected Color highlight; 62 protected Color lightHighlight; 63 protected Color shadow; 64 protected Color darkShadow; 65 protected Color focus; 66 private Color selectedColor; 67 68 protected int textIconGap; 69 70 protected int tabRunOverlay; 71 72 protected Insets tabInsets; 73 protected Insets selectedTabPadInsets; 74 protected Insets tabAreaInsets; 75 protected Insets contentBorderInsets; 76 private boolean tabsOverlapBorder; 77 private boolean tabsOpaque = true; 78 private boolean contentOpaque = true; 79 80 /** 81 * As of Java 2 platform v1.3 this previously undocumented field is no 82 * longer used. 83 * Key bindings are now defined by the LookAndFeel, please refer to 84 * the key bindings specification for further details. 85 * 86 * @deprecated As of Java 2 platform v1.3. 87 */ 88 @Deprecated 89 protected KeyStroke upKey; 90 /** 91 * As of Java 2 platform v1.3 this previously undocumented field is no 92 * longer used. 93 * Key bindings are now defined by the LookAndFeel, please refer to 94 * the key bindings specification for further details. 95 * 96 * @deprecated As of Java 2 platform v1.3. 97 */ 98 @Deprecated 99 protected KeyStroke downKey; 100 /** 101 * As of Java 2 platform v1.3 this previously undocumented field is no 102 * longer used. 103 * Key bindings are now defined by the LookAndFeel, please refer to 104 * the key bindings specification for further details. 105 * 106 * @deprecated As of Java 2 platform v1.3. 107 */ 108 @Deprecated 109 protected KeyStroke leftKey; 110 /** 111 * As of Java 2 platform v1.3 this previously undocumented field is no 112 * longer used. 113 * Key bindings are now defined by the LookAndFeel, please refer to 114 * the key bindings specification for further details. 115 * 116 * @deprecated As of Java 2 platform v1.3. 117 */ 118 @Deprecated 119 protected KeyStroke rightKey; 120 121 122 // Transient variables (recalculated each time TabbedPane is layed out) 123 124 protected int tabRuns[] = new int[10]; 125 protected int runCount = 0; 126 protected int selectedRun = -1; 127 protected Rectangle rects[] = new Rectangle[0]; 128 protected int maxTabHeight; 129 protected int maxTabWidth; 130 131 // Listeners 132 133 protected ChangeListener tabChangeListener; 134 protected PropertyChangeListener propertyChangeListener; 135 protected MouseListener mouseListener; 136 protected FocusListener focusListener; 137 138 // Private instance data 139 140 private Insets currentPadInsets = new Insets(0,0,0,0); 141 private Insets currentTabAreaInsets = new Insets(0,0,0,0); 142 143 private Component visibleComponent; 144 // PENDING(api): See comment for ContainerHandler 145 private Vector<View> htmlViews; 146 147 private Hashtable<Integer, Integer> mnemonicToIndexMap; 148 149 /** 150 * InputMap used for mnemonics. Only non-null if the JTabbedPane has 151 * mnemonics associated with it. Lazily created in initMnemonics. 152 */ 153 private InputMap mnemonicInputMap; 154 155 // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT 156 private ScrollableTabSupport tabScroller; 157 158 private TabContainer tabContainer; 159 160 /** 161 * A rectangle used for general layout calculations in order 162 * to avoid constructing many new Rectangles on the fly. 163 */ 164 protected transient Rectangle calcRect = new Rectangle(0,0,0,0); 165 166 /** 167 * Tab that has focus. 168 */ 169 private int focusIndex; 170 171 /** 172 * Combined listeners. 173 */ 174 private Handler handler; 175 176 /** 177 * Index of the tab the mouse is over. 178 */ 179 private int rolloverTabIndex; 180 181 /** 182 * This is set to true when a component is added/removed from the tab 183 * pane and set to false when layout happens. If true it indicates that 184 * tabRuns is not valid and shouldn't be used. 185 */ 186 private boolean isRunsDirty; 187 188 private boolean calculatedBaseline; 189 private int baseline; 190 191 // UI creation 192 193 public static ComponentUI createUI(JComponent c) { 194 return new BasicTabbedPaneUI(); 195 } 196 197 static void loadActionMap(LazyActionMap map) { 198 map.put(new Actions(Actions.NEXT)); 199 map.put(new Actions(Actions.PREVIOUS)); 200 map.put(new Actions(Actions.RIGHT)); 201 map.put(new Actions(Actions.LEFT)); 202 map.put(new Actions(Actions.UP)); 203 map.put(new Actions(Actions.DOWN)); 204 map.put(new Actions(Actions.PAGE_UP)); 205 map.put(new Actions(Actions.PAGE_DOWN)); 206 map.put(new Actions(Actions.REQUEST_FOCUS)); 207 map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE)); 208 map.put(new Actions(Actions.SET_SELECTED)); 209 map.put(new Actions(Actions.SELECT_FOCUSED)); 210 map.put(new Actions(Actions.SCROLL_FORWARD)); 211 map.put(new Actions(Actions.SCROLL_BACKWARD)); 212 } 213 214 // UI Installation/De-installation 215 216 public void installUI(JComponent c) { 217 this.tabPane = (JTabbedPane)c; 218 219 calculatedBaseline = false; 220 rolloverTabIndex = -1; 221 focusIndex = -1; 222 c.setLayout(createLayoutManager()); 223 installComponents(); 224 installDefaults(); 225 installListeners(); 226 installKeyboardActions(); 227 } 228 229 public void uninstallUI(JComponent c) { 230 uninstallKeyboardActions(); 231 uninstallListeners(); 232 uninstallDefaults(); 233 uninstallComponents(); 234 c.setLayout(null); 235 236 this.tabPane = null; 237 } 238 239 /** 240 * Invoked by <code>installUI</code> to create 241 * a layout manager object to manage 242 * the <code>JTabbedPane</code>. 243 * 244 * @return a layout manager object 245 * 246 * @see TabbedPaneLayout 247 * @see javax.swing.JTabbedPane#getTabLayoutPolicy 248 */ 249 protected LayoutManager createLayoutManager() { 250 if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) { 251 return new TabbedPaneScrollLayout(); 252 } else { /* WRAP_TAB_LAYOUT */ 253 return new TabbedPaneLayout(); 254 } 255 } 256 257 /* In an attempt to preserve backward compatibility for programs 258 * which have extended BasicTabbedPaneUI to do their own layout, the 259 * UI uses the installed layoutManager (and not tabLayoutPolicy) to 260 * determine if scrollTabLayout is enabled. 261 */ 262 private boolean scrollableTabLayoutEnabled() { 263 return (tabPane.getLayout() instanceof TabbedPaneScrollLayout); 264 } 265 266 /** 267 * Creates and installs any required subcomponents for the JTabbedPane. 268 * Invoked by installUI. 269 * 270 * @since 1.4 271 */ 272 protected void installComponents() { 273 if (scrollableTabLayoutEnabled()) { 274 if (tabScroller == null) { 275 tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement()); 276 tabPane.add(tabScroller.viewport); 277 } 278 } 279 installTabContainer(); 280 } 281 282 private void installTabContainer() { 283 for (int i = 0; i < tabPane.getTabCount(); i++) { 284 Component tabComponent = tabPane.getTabComponentAt(i); 285 if (tabComponent != null) { 286 if(tabContainer == null) { 287 tabContainer = new TabContainer(); 288 } 289 tabContainer.add(tabComponent); 290 } 291 } 292 if(tabContainer == null) { 293 return; 294 } 295 if (scrollableTabLayoutEnabled()) { 296 tabScroller.tabPanel.add(tabContainer); 297 } else { 298 tabPane.add(tabContainer); 299 } 300 } 301 302 /** 303 * Creates and returns a JButton that will provide the user 304 * with a way to scroll the tabs in a particular direction. The 305 * returned JButton must be instance of UIResource. 306 * 307 * @param direction One of the SwingConstants constants: 308 * SOUTH, NORTH, EAST or WEST 309 * @return Widget for user to 310 * @see javax.swing.JTabbedPane#setTabPlacement 311 * @see javax.swing.SwingConstants 312 * @throws IllegalArgumentException if direction is not one of 313 * NORTH, SOUTH, EAST or WEST 314 * @since 1.5 315 */ 316 protected JButton createScrollButton(int direction) { 317 if (direction != SOUTH && direction != NORTH && direction != EAST && 318 direction != WEST) { 319 throw new IllegalArgumentException("Direction must be one of: " + 320 "SOUTH, NORTH, EAST or WEST"); 321 } 322 return new ScrollableTabButton(direction); 323 } 324 325 /** 326 * Removes any installed subcomponents from the JTabbedPane. 327 * Invoked by uninstallUI. 328 * 329 * @since 1.4 330 */ 331 protected void uninstallComponents() { 332 uninstallTabContainer(); 333 if (scrollableTabLayoutEnabled()) { 334 tabPane.remove(tabScroller.viewport); 335 tabPane.remove(tabScroller.scrollForwardButton); 336 tabPane.remove(tabScroller.scrollBackwardButton); 337 tabScroller = null; 338 } 339 } 340 341 private void uninstallTabContainer() { 342 if(tabContainer == null) { 343 return; 344 } 345 // Remove all the tabComponents, making sure not to notify 346 // the tabbedpane. 347 tabContainer.notifyTabbedPane = false; 348 tabContainer.removeAll(); 349 if(scrollableTabLayoutEnabled()) { 350 tabContainer.remove(tabScroller.croppedEdge); 351 tabScroller.tabPanel.remove(tabContainer); 352 } else { 353 tabPane.remove(tabContainer); 354 } 355 tabContainer = null; 356 } 357 358 protected void installDefaults() { 359 LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background", 360 "TabbedPane.foreground", "TabbedPane.font"); 361 highlight = UIManager.getColor("TabbedPane.light"); 362 lightHighlight = UIManager.getColor("TabbedPane.highlight"); 363 shadow = UIManager.getColor("TabbedPane.shadow"); 364 darkShadow = UIManager.getColor("TabbedPane.darkShadow"); 365 focus = UIManager.getColor("TabbedPane.focus"); 366 selectedColor = UIManager.getColor("TabbedPane.selected"); 367 368 textIconGap = UIManager.getInt("TabbedPane.textIconGap"); 369 tabInsets = UIManager.getInsets("TabbedPane.tabInsets"); 370 selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets"); 371 tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets"); 372 tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder"); 373 contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets"); 374 tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay"); 375 tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque"); 376 contentOpaque = UIManager.getBoolean("TabbedPane.contentOpaque"); 377 Object opaque = UIManager.get("TabbedPane.opaque"); 378 if (opaque == null) { 379 opaque = Boolean.FALSE; 380 } 381 LookAndFeel.installProperty(tabPane, "opaque", opaque); 382 383 // Fix for 6711145 BasicTabbedPanuUI should not throw a NPE if these 384 // keys are missing. So we are setting them to there default values here 385 // if the keys are missing. 386 if (tabInsets == null) tabInsets = new Insets(0,4,1,4); 387 if (selectedTabPadInsets == null) selectedTabPadInsets = new Insets(2,2,2,1); 388 if (tabAreaInsets == null) tabAreaInsets = new Insets(3,2,0,2); 389 if (contentBorderInsets == null) contentBorderInsets = new Insets(2,2,3,3); 390 } 391 392 protected void uninstallDefaults() { 393 highlight = null; 394 lightHighlight = null; 395 shadow = null; 396 darkShadow = null; 397 focus = null; 398 tabInsets = null; 399 selectedTabPadInsets = null; 400 tabAreaInsets = null; 401 contentBorderInsets = null; 402 } 403 404 protected void installListeners() { 405 if ((propertyChangeListener = createPropertyChangeListener()) != null) { 406 tabPane.addPropertyChangeListener(propertyChangeListener); 407 } 408 if ((tabChangeListener = createChangeListener()) != null) { 409 tabPane.addChangeListener(tabChangeListener); 410 } 411 if ((mouseListener = createMouseListener()) != null) { 412 tabPane.addMouseListener(mouseListener); 413 } 414 tabPane.addMouseMotionListener(getHandler()); 415 if ((focusListener = createFocusListener()) != null) { 416 tabPane.addFocusListener(focusListener); 417 } 418 tabPane.addContainerListener(getHandler()); 419 if (tabPane.getTabCount()>0) { 420 htmlViews = createHTMLVector(); 421 } 422 } 423 424 protected void uninstallListeners() { 425 if (mouseListener != null) { 426 tabPane.removeMouseListener(mouseListener); 427 mouseListener = null; 428 } 429 tabPane.removeMouseMotionListener(getHandler()); 430 if (focusListener != null) { 431 tabPane.removeFocusListener(focusListener); 432 focusListener = null; 433 } 434 435 tabPane.removeContainerListener(getHandler()); 436 if (htmlViews!=null) { 437 htmlViews.removeAllElements(); 438 htmlViews = null; 439 } 440 if (tabChangeListener != null) { 441 tabPane.removeChangeListener(tabChangeListener); 442 tabChangeListener = null; 443 } 444 if (propertyChangeListener != null) { 445 tabPane.removePropertyChangeListener(propertyChangeListener); 446 propertyChangeListener = null; 447 } 448 handler = null; 449 } 450 451 protected MouseListener createMouseListener() { 452 return getHandler(); 453 } 454 455 protected FocusListener createFocusListener() { 456 return getHandler(); 457 } 458 459 protected ChangeListener createChangeListener() { 460 return getHandler(); 461 } 462 463 protected PropertyChangeListener createPropertyChangeListener() { 464 return getHandler(); 465 } 466 467 private Handler getHandler() { 468 if (handler == null) { 469 handler = new Handler(); 470 } 471 return handler; 472 } 473 474 protected void installKeyboardActions() { 475 InputMap km = getInputMap(JComponent. 476 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 477 478 SwingUtilities.replaceUIInputMap(tabPane, JComponent. 479 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 480 km); 481 km = getInputMap(JComponent.WHEN_FOCUSED); 482 SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km); 483 484 LazyActionMap.installLazyActionMap(tabPane, BasicTabbedPaneUI.class, 485 "TabbedPane.actionMap"); 486 updateMnemonics(); 487 } 488 489 InputMap getInputMap(int condition) { 490 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 491 return (InputMap)DefaultLookup.get(tabPane, this, 492 "TabbedPane.ancestorInputMap"); 493 } 494 else if (condition == JComponent.WHEN_FOCUSED) { 495 return (InputMap)DefaultLookup.get(tabPane, this, 496 "TabbedPane.focusInputMap"); 497 } 498 return null; 499 } 500 501 protected void uninstallKeyboardActions() { 502 SwingUtilities.replaceUIActionMap(tabPane, null); 503 SwingUtilities.replaceUIInputMap(tabPane, JComponent. 504 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 505 null); 506 SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, 507 null); 508 SwingUtilities.replaceUIInputMap(tabPane, 509 JComponent.WHEN_IN_FOCUSED_WINDOW, 510 null); 511 mnemonicToIndexMap = null; 512 mnemonicInputMap = null; 513 } 514 515 /** 516 * Reloads the mnemonics. This should be invoked when a memonic changes, 517 * when the title of a mnemonic changes, or when tabs are added/removed. 518 */ 519 private void updateMnemonics() { 520 resetMnemonics(); 521 for (int counter = tabPane.getTabCount() - 1; counter >= 0; 522 counter--) { 523 int mnemonic = tabPane.getMnemonicAt(counter); 524 525 if (mnemonic > 0) { 526 addMnemonic(counter, mnemonic); 527 } 528 } 529 } 530 531 /** 532 * Resets the mnemonics bindings to an empty state. 533 */ 534 private void resetMnemonics() { 535 if (mnemonicToIndexMap != null) { 536 mnemonicToIndexMap.clear(); 537 mnemonicInputMap.clear(); 538 } 539 } 540 541 /** 542 * Adds the specified mnemonic at the specified index. 543 */ 544 private void addMnemonic(int index, int mnemonic) { 545 if (mnemonicToIndexMap == null) { 546 initMnemonics(); 547 } 548 mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, Event.ALT_MASK), 549 "setSelectedIndex"); 550 mnemonicToIndexMap.put(Integer.valueOf(mnemonic), Integer.valueOf(index)); 551 } 552 553 /** 554 * Installs the state needed for mnemonics. 555 */ 556 private void initMnemonics() { 557 mnemonicToIndexMap = new Hashtable<Integer, Integer>(); 558 mnemonicInputMap = new ComponentInputMapUIResource(tabPane); 559 mnemonicInputMap.setParent(SwingUtilities.getUIInputMap(tabPane, 560 JComponent.WHEN_IN_FOCUSED_WINDOW)); 561 SwingUtilities.replaceUIInputMap(tabPane, 562 JComponent.WHEN_IN_FOCUSED_WINDOW, 563 mnemonicInputMap); 564 } 565 566 /** 567 * Sets the tab the mouse is over by location. This is a cover method 568 * for <code>setRolloverTab(tabForCoordinate(x, y, false))</code>. 569 */ 570 private void setRolloverTab(int x, int y) { 571 // NOTE: 572 // This calls in with false otherwise it could trigger a validate, 573 // which should NOT happen if the user is only dragging the 574 // mouse around. 575 setRolloverTab(tabForCoordinate(tabPane, x, y, false)); 576 } 577 578 /** 579 * Sets the tab the mouse is currently over to <code>index</code>. 580 * <code>index</code> will be -1 if the mouse is no longer over any 581 * tab. No checking is done to ensure the passed in index identifies a 582 * valid tab. 583 * 584 * @param index Index of the tab the mouse is over. 585 * @since 1.5 586 */ 587 protected void setRolloverTab(int index) { 588 rolloverTabIndex = index; 589 } 590 591 /** 592 * Returns the tab the mouse is currently over, or {@code -1} if the mouse is no 593 * longer over any tab. 594 * 595 * @return the tab the mouse is currently over, or {@code -1} if the mouse is no 596 * longer over any tab 597 * @since 1.5 598 */ 599 protected int getRolloverTab() { 600 return rolloverTabIndex; 601 } 602 603 public Dimension getMinimumSize(JComponent c) { 604 // Default to LayoutManager's minimumLayoutSize 605 return null; 606 } 607 608 public Dimension getMaximumSize(JComponent c) { 609 // Default to LayoutManager's maximumLayoutSize 610 return null; 611 } 612 613 /** 614 * Returns the baseline. 615 * 616 * @throws NullPointerException {@inheritDoc} 617 * @throws IllegalArgumentException {@inheritDoc} 618 * @see javax.swing.JComponent#getBaseline(int, int) 619 * @since 1.6 620 */ 621 public int getBaseline(JComponent c, int width, int height) { 622 super.getBaseline(c, width, height); 623 int baseline = calculateBaselineIfNecessary(); 624 if (baseline != -1) { 625 int placement = tabPane.getTabPlacement(); 626 Insets insets = tabPane.getInsets(); 627 Insets tabAreaInsets = getTabAreaInsets(placement); 628 switch(placement) { 629 case JTabbedPane.TOP: 630 baseline += insets.top + tabAreaInsets.top; 631 return baseline; 632 case JTabbedPane.BOTTOM: 633 baseline = height - insets.bottom - 634 tabAreaInsets.bottom - maxTabHeight + baseline; 635 return baseline; 636 case JTabbedPane.LEFT: 637 case JTabbedPane.RIGHT: 638 baseline += insets.top + tabAreaInsets.top; 639 return baseline; 640 } 641 } 642 return -1; 643 } 644 645 /** 646 * Returns an enum indicating how the baseline of the component 647 * changes as the size changes. 648 * 649 * @throws NullPointerException {@inheritDoc} 650 * @see javax.swing.JComponent#getBaseline(int, int) 651 * @since 1.6 652 */ 653 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 654 JComponent c) { 655 super.getBaselineResizeBehavior(c); 656 switch(tabPane.getTabPlacement()) { 657 case JTabbedPane.LEFT: 658 case JTabbedPane.RIGHT: 659 case JTabbedPane.TOP: 660 return Component.BaselineResizeBehavior.CONSTANT_ASCENT; 661 case JTabbedPane.BOTTOM: 662 return Component.BaselineResizeBehavior.CONSTANT_DESCENT; 663 } 664 return Component.BaselineResizeBehavior.OTHER; 665 } 666 667 /** 668 * Returns the baseline for the specified tab. 669 * 670 * @param tab index of tab to get baseline for 671 * @exception IndexOutOfBoundsException if index is out of range 672 * (index < 0 || index >= tab count) 673 * @return baseline or a value < 0 indicating there is no reasonable 674 * baseline 675 * @since 1.6 676 */ 677 protected int getBaseline(int tab) { 678 if (tabPane.getTabComponentAt(tab) != null) { 679 int offset = getBaselineOffset(); 680 if (offset != 0) { 681 // The offset is not applied to the tab component, and so 682 // in general we can't get good alignment like with components 683 // in the tab. 684 return -1; 685 } 686 Component c = tabPane.getTabComponentAt(tab); 687 Dimension pref = c.getPreferredSize(); 688 Insets tabInsets = getTabInsets(tabPane.getTabPlacement(), tab); 689 int cellHeight = maxTabHeight - tabInsets.top - tabInsets.bottom; 690 return c.getBaseline(pref.width, pref.height) + 691 (cellHeight - pref.height) / 2 + tabInsets.top; 692 } 693 else { 694 View view = getTextViewForTab(tab); 695 if (view != null) { 696 int viewHeight = (int)view.getPreferredSpan(View.Y_AXIS); 697 int baseline = BasicHTML.getHTMLBaseline( 698 view, (int)view.getPreferredSpan(View.X_AXIS), viewHeight); 699 if (baseline >= 0) { 700 return maxTabHeight / 2 - viewHeight / 2 + baseline + 701 getBaselineOffset(); 702 } 703 return -1; 704 } 705 } 706 FontMetrics metrics = getFontMetrics(); 707 int fontHeight = metrics.getHeight(); 708 int fontBaseline = metrics.getAscent(); 709 return maxTabHeight / 2 - fontHeight / 2 + fontBaseline + 710 getBaselineOffset(); 711 } 712 713 /** 714 * Returns the amount the baseline is offset by. This is typically 715 * the same as <code>getTabLabelShiftY</code>. 716 * 717 * @return amount to offset the baseline by 718 * @since 1.6 719 */ 720 protected int getBaselineOffset() { 721 switch(tabPane.getTabPlacement()) { 722 case JTabbedPane.TOP: 723 if (tabPane.getTabCount() > 1) { 724 return 1; 725 } 726 else { 727 return -1; 728 } 729 case JTabbedPane.BOTTOM: 730 if (tabPane.getTabCount() > 1) { 731 return -1; 732 } 733 else { 734 return 1; 735 } 736 default: // RIGHT|LEFT 737 return (maxTabHeight % 2); 738 } 739 } 740 741 private int calculateBaselineIfNecessary() { 742 if (!calculatedBaseline) { 743 calculatedBaseline = true; 744 baseline = -1; 745 if (tabPane.getTabCount() > 0) { 746 calculateBaseline(); 747 } 748 } 749 return baseline; 750 } 751 752 private void calculateBaseline() { 753 int tabCount = tabPane.getTabCount(); 754 int tabPlacement = tabPane.getTabPlacement(); 755 maxTabHeight = calculateMaxTabHeight(tabPlacement); 756 baseline = getBaseline(0); 757 if (isHorizontalTabPlacement()) { 758 for(int i = 1; i < tabCount; i++) { 759 if (getBaseline(i) != baseline) { 760 baseline = -1; 761 break; 762 } 763 } 764 } 765 else { 766 // left/right, tabs may be different sizes. 767 FontMetrics fontMetrics = getFontMetrics(); 768 int fontHeight = fontMetrics.getHeight(); 769 int height = calculateTabHeight(tabPlacement, 0, fontHeight); 770 for(int i = 1; i < tabCount; i++) { 771 int newHeight = calculateTabHeight(tabPlacement, i,fontHeight); 772 if (height != newHeight) { 773 // assume different baseline 774 baseline = -1; 775 break; 776 } 777 } 778 } 779 } 780 781 // UI Rendering 782 783 public void paint(Graphics g, JComponent c) { 784 int selectedIndex = tabPane.getSelectedIndex(); 785 int tabPlacement = tabPane.getTabPlacement(); 786 787 ensureCurrentLayout(); 788 789 // Paint content border and tab area 790 if (tabsOverlapBorder) { 791 paintContentBorder(g, tabPlacement, selectedIndex); 792 } 793 // If scrollable tabs are enabled, the tab area will be 794 // painted by the scrollable tab panel instead. 795 // 796 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT 797 paintTabArea(g, tabPlacement, selectedIndex); 798 } 799 if (!tabsOverlapBorder) { 800 paintContentBorder(g, tabPlacement, selectedIndex); 801 } 802 } 803 804 /** 805 * Paints the tabs in the tab area. 806 * Invoked by paint(). 807 * The graphics parameter must be a valid <code>Graphics</code> 808 * object. Tab placement may be either: 809 * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>, 810 * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>. 811 * The selected index must be a valid tabbed pane tab index (0 to 812 * tab count - 1, inclusive) or -1 if no tab is currently selected. 813 * The handling of invalid parameters is unspecified. 814 * 815 * @param g the graphics object to use for rendering 816 * @param tabPlacement the placement for the tabs within the JTabbedPane 817 * @param selectedIndex the tab index of the selected component 818 * 819 * @since 1.4 820 */ 821 protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) { 822 int tabCount = tabPane.getTabCount(); 823 824 Rectangle iconRect = new Rectangle(), 825 textRect = new Rectangle(); 826 Rectangle clipRect = g.getClipBounds(); 827 828 // Paint tabRuns of tabs from back to front 829 for (int i = runCount - 1; i >= 0; i--) { 830 int start = tabRuns[i]; 831 int next = tabRuns[(i == runCount - 1)? 0 : i + 1]; 832 int end = (next != 0? next - 1: tabCount - 1); 833 for (int j = start; j <= end; j++) { 834 if (j != selectedIndex && rects[j].intersects(clipRect)) { 835 paintTab(g, tabPlacement, rects, j, iconRect, textRect); 836 } 837 } 838 } 839 840 // Paint selected tab if its in the front run 841 // since it may overlap other tabs 842 if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) { 843 paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect); 844 } 845 } 846 847 protected void paintTab(Graphics g, int tabPlacement, 848 Rectangle[] rects, int tabIndex, 849 Rectangle iconRect, Rectangle textRect) { 850 Rectangle tabRect = rects[tabIndex]; 851 int selectedIndex = tabPane.getSelectedIndex(); 852 boolean isSelected = selectedIndex == tabIndex; 853 854 if (tabsOpaque || tabPane.isOpaque()) { 855 paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y, 856 tabRect.width, tabRect.height, isSelected); 857 } 858 859 paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y, 860 tabRect.width, tabRect.height, isSelected); 861 862 String title = tabPane.getTitleAt(tabIndex); 863 Font font = tabPane.getFont(); 864 FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font); 865 Icon icon = getIconForTab(tabIndex); 866 867 layoutLabel(tabPlacement, metrics, tabIndex, title, icon, 868 tabRect, iconRect, textRect, isSelected); 869 870 if (tabPane.getTabComponentAt(tabIndex) == null) { 871 String clippedTitle = title; 872 873 if (scrollableTabLayoutEnabled() && tabScroller.croppedEdge.isParamsSet() && 874 tabScroller.croppedEdge.getTabIndex() == tabIndex && isHorizontalTabPlacement()) { 875 int availTextWidth = tabScroller.croppedEdge.getCropline() - 876 (textRect.x - tabRect.x) - tabScroller.croppedEdge.getCroppedSideWidth(); 877 clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, availTextWidth); 878 } 879 880 paintText(g, tabPlacement, font, metrics, 881 tabIndex, clippedTitle, textRect, isSelected); 882 883 paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); 884 } 885 paintFocusIndicator(g, tabPlacement, rects, tabIndex, 886 iconRect, textRect, isSelected); 887 } 888 889 private boolean isHorizontalTabPlacement() { 890 return tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM; 891 } 892 893 /* This method will create and return a polygon shape for the given tab rectangle 894 * which has been cropped at the specified cropline with a torn edge visual. 895 * e.g. A "File" tab which has cropped been cropped just after the "i": 896 * ------------- 897 * | ..... | 898 * | . | 899 * | ... . | 900 * | . . | 901 * | . . | 902 * | . . | 903 * -------------- 904 * 905 * The x, y arrays below define the pattern used to create a "torn" edge 906 * segment which is repeated to fill the edge of the tab. 907 * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by 908 * line segments which are defined by coordinates obtained by 909 * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i] 910 * to (tab.y). 911 * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by 912 * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i] 913 * to (tab.x). 914 */ 915 private static int xCropLen[] = {1,1,0,0,1,1,2,2}; 916 private static int yCropLen[] = {0,3,3,6,6,9,9,12}; 917 private static final int CROP_SEGMENT = 12; 918 919 private static Polygon createCroppedTabShape(int tabPlacement, Rectangle tabRect, int cropline) { 920 int rlen; 921 int start; 922 int end; 923 int ostart; 924 925 switch(tabPlacement) { 926 case LEFT: 927 case RIGHT: 928 rlen = tabRect.width; 929 start = tabRect.x; 930 end = tabRect.x + tabRect.width; 931 ostart = tabRect.y + tabRect.height; 932 break; 933 case TOP: 934 case BOTTOM: 935 default: 936 rlen = tabRect.height; 937 start = tabRect.y; 938 end = tabRect.y + tabRect.height; 939 ostart = tabRect.x + tabRect.width; 940 } 941 int rcnt = rlen/CROP_SEGMENT; 942 if (rlen%CROP_SEGMENT > 0) { 943 rcnt++; 944 } 945 int npts = 2 + (rcnt*8); 946 int xp[] = new int[npts]; 947 int yp[] = new int[npts]; 948 int pcnt = 0; 949 950 xp[pcnt] = ostart; 951 yp[pcnt++] = end; 952 xp[pcnt] = ostart; 953 yp[pcnt++] = start; 954 for(int i = 0; i < rcnt; i++) { 955 for(int j = 0; j < xCropLen.length; j++) { 956 xp[pcnt] = cropline - xCropLen[j]; 957 yp[pcnt] = start + (i*CROP_SEGMENT) + yCropLen[j]; 958 if (yp[pcnt] >= end) { 959 yp[pcnt] = end; 960 pcnt++; 961 break; 962 } 963 pcnt++; 964 } 965 } 966 if (tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM) { 967 return new Polygon(xp, yp, pcnt); 968 969 } else { // LEFT or RIGHT 970 return new Polygon(yp, xp, pcnt); 971 } 972 } 973 974 /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge 975 * indicating the tab is cropped in the viewport display 976 */ 977 private void paintCroppedTabEdge(Graphics g) { 978 int tabIndex = tabScroller.croppedEdge.getTabIndex(); 979 int cropline = tabScroller.croppedEdge.getCropline(); 980 int x,y; 981 switch(tabPane.getTabPlacement()) { 982 case LEFT: 983 case RIGHT: 984 x = rects[tabIndex].x; 985 y = cropline; 986 int xx = x; 987 g.setColor(shadow); 988 while(xx <= x+rects[tabIndex].width) { 989 for (int i=0; i < xCropLen.length; i+=2) { 990 g.drawLine(xx+yCropLen[i],y-xCropLen[i], 991 xx+yCropLen[i+1]-1,y-xCropLen[i+1]); 992 } 993 xx+=CROP_SEGMENT; 994 } 995 break; 996 case TOP: 997 case BOTTOM: 998 default: 999 x = cropline; 1000 y = rects[tabIndex].y; 1001 int yy = y; 1002 g.setColor(shadow); 1003 while(yy <= y+rects[tabIndex].height) { 1004 for (int i=0; i < xCropLen.length; i+=2) { 1005 g.drawLine(x-xCropLen[i],yy+yCropLen[i], 1006 x-xCropLen[i+1],yy+yCropLen[i+1]-1); 1007 } 1008 yy+=CROP_SEGMENT; 1009 } 1010 } 1011 } 1012 1013 protected void layoutLabel(int tabPlacement, 1014 FontMetrics metrics, int tabIndex, 1015 String title, Icon icon, 1016 Rectangle tabRect, Rectangle iconRect, 1017 Rectangle textRect, boolean isSelected ) { 1018 textRect.x = textRect.y = iconRect.x = iconRect.y = 0; 1019 1020 View v = getTextViewForTab(tabIndex); 1021 if (v != null) { 1022 tabPane.putClientProperty("html", v); 1023 } 1024 1025 SwingUtilities.layoutCompoundLabel(tabPane, 1026 metrics, title, icon, 1027 SwingUtilities.CENTER, 1028 SwingUtilities.CENTER, 1029 SwingUtilities.CENTER, 1030 SwingUtilities.TRAILING, 1031 tabRect, 1032 iconRect, 1033 textRect, 1034 textIconGap); 1035 1036 tabPane.putClientProperty("html", null); 1037 1038 int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected); 1039 int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected); 1040 iconRect.x += xNudge; 1041 iconRect.y += yNudge; 1042 textRect.x += xNudge; 1043 textRect.y += yNudge; 1044 } 1045 1046 protected void paintIcon(Graphics g, int tabPlacement, 1047 int tabIndex, Icon icon, Rectangle iconRect, 1048 boolean isSelected ) { 1049 if (icon != null) { 1050 icon.paintIcon(tabPane, g, iconRect.x, iconRect.y); 1051 } 1052 } 1053 1054 protected void paintText(Graphics g, int tabPlacement, 1055 Font font, FontMetrics metrics, int tabIndex, 1056 String title, Rectangle textRect, 1057 boolean isSelected) { 1058 1059 g.setFont(font); 1060 1061 View v = getTextViewForTab(tabIndex); 1062 if (v != null) { 1063 // html 1064 v.paint(g, textRect); 1065 } else { 1066 // plain text 1067 int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex); 1068 1069 if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) { 1070 Color fg = tabPane.getForegroundAt(tabIndex); 1071 if (isSelected && (fg instanceof UIResource)) { 1072 Color selectedFG = UIManager.getColor( 1073 "TabbedPane.selectedForeground"); 1074 if (selectedFG != null) { 1075 fg = selectedFG; 1076 } 1077 } 1078 g.setColor(fg); 1079 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, 1080 title, mnemIndex, 1081 textRect.x, textRect.y + metrics.getAscent()); 1082 1083 } else { // tab disabled 1084 g.setColor(tabPane.getBackgroundAt(tabIndex).brighter()); 1085 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, 1086 title, mnemIndex, 1087 textRect.x, textRect.y + metrics.getAscent()); 1088 g.setColor(tabPane.getBackgroundAt(tabIndex).darker()); 1089 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, 1090 title, mnemIndex, 1091 textRect.x - 1, textRect.y + metrics.getAscent() - 1); 1092 1093 } 1094 } 1095 } 1096 1097 1098 protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) { 1099 Rectangle tabRect = rects[tabIndex]; 1100 String propKey = (isSelected ? "selectedLabelShift" : "labelShift"); 1101 int nudge = DefaultLookup.getInt( 1102 tabPane, this, "TabbedPane." + propKey, 1); 1103 1104 switch (tabPlacement) { 1105 case LEFT: 1106 return nudge; 1107 case RIGHT: 1108 return -nudge; 1109 case BOTTOM: 1110 case TOP: 1111 default: 1112 return tabRect.width % 2; 1113 } 1114 } 1115 1116 protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) { 1117 Rectangle tabRect = rects[tabIndex]; 1118 String propKey = (isSelected ? "selectedLabelShift" : "labelShift"); 1119 int nudge = DefaultLookup.getInt( 1120 tabPane, this, "TabbedPane." + propKey, 1); 1121 1122 switch (tabPlacement) { 1123 case BOTTOM: 1124 return -nudge; 1125 case LEFT: 1126 case RIGHT: 1127 return tabRect.height % 2; 1128 case TOP: 1129 default: 1130 return nudge; 1131 } 1132 } 1133 1134 protected void paintFocusIndicator(Graphics g, int tabPlacement, 1135 Rectangle[] rects, int tabIndex, 1136 Rectangle iconRect, Rectangle textRect, 1137 boolean isSelected) { 1138 Rectangle tabRect = rects[tabIndex]; 1139 if (tabPane.hasFocus() && isSelected) { 1140 int x, y, w, h; 1141 g.setColor(focus); 1142 switch(tabPlacement) { 1143 case LEFT: 1144 x = tabRect.x + 3; 1145 y = tabRect.y + 3; 1146 w = tabRect.width - 5; 1147 h = tabRect.height - 6; 1148 break; 1149 case RIGHT: 1150 x = tabRect.x + 2; 1151 y = tabRect.y + 3; 1152 w = tabRect.width - 5; 1153 h = tabRect.height - 6; 1154 break; 1155 case BOTTOM: 1156 x = tabRect.x + 3; 1157 y = tabRect.y + 2; 1158 w = tabRect.width - 6; 1159 h = tabRect.height - 5; 1160 break; 1161 case TOP: 1162 default: 1163 x = tabRect.x + 3; 1164 y = tabRect.y + 3; 1165 w = tabRect.width - 6; 1166 h = tabRect.height - 5; 1167 } 1168 BasicGraphicsUtils.drawDashedRect(g, x, y, w, h); 1169 } 1170 } 1171 1172 /** 1173 * this function draws the border around each tab 1174 * note that this function does now draw the background of the tab. 1175 * that is done elsewhere 1176 */ 1177 protected void paintTabBorder(Graphics g, int tabPlacement, 1178 int tabIndex, 1179 int x, int y, int w, int h, 1180 boolean isSelected ) { 1181 g.setColor(lightHighlight); 1182 1183 switch (tabPlacement) { 1184 case LEFT: 1185 g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight 1186 g.drawLine(x, y+2, x, y+h-3); // left highlight 1187 g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight 1188 g.drawLine(x+2, y, x+w-1, y); // top highlight 1189 1190 g.setColor(shadow); 1191 g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow 1192 1193 g.setColor(darkShadow); 1194 g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow 1195 break; 1196 case RIGHT: 1197 g.drawLine(x, y, x+w-3, y); // top highlight 1198 1199 g.setColor(shadow); 1200 g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow 1201 g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow 1202 1203 g.setColor(darkShadow); 1204 g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow 1205 g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow 1206 g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow 1207 g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow 1208 break; 1209 case BOTTOM: 1210 g.drawLine(x, y, x, y+h-3); // left highlight 1211 g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight 1212 1213 g.setColor(shadow); 1214 g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow 1215 g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow 1216 1217 g.setColor(darkShadow); 1218 g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow 1219 g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow 1220 g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow 1221 break; 1222 case TOP: 1223 default: 1224 g.drawLine(x, y+2, x, y+h-1); // left highlight 1225 g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight 1226 g.drawLine(x+2, y, x+w-3, y); // top highlight 1227 1228 g.setColor(shadow); 1229 g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow 1230 1231 g.setColor(darkShadow); 1232 g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow 1233 g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow 1234 } 1235 } 1236 1237 protected void paintTabBackground(Graphics g, int tabPlacement, 1238 int tabIndex, 1239 int x, int y, int w, int h, 1240 boolean isSelected ) { 1241 g.setColor(!isSelected || selectedColor == null? 1242 tabPane.getBackgroundAt(tabIndex) : selectedColor); 1243 switch(tabPlacement) { 1244 case LEFT: 1245 g.fillRect(x+1, y+1, w-1, h-3); 1246 break; 1247 case RIGHT: 1248 g.fillRect(x, y+1, w-2, h-3); 1249 break; 1250 case BOTTOM: 1251 g.fillRect(x+1, y, w-3, h-1); 1252 break; 1253 case TOP: 1254 default: 1255 g.fillRect(x+1, y+1, w-3, h-1); 1256 } 1257 } 1258 1259 protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) { 1260 int width = tabPane.getWidth(); 1261 int height = tabPane.getHeight(); 1262 Insets insets = tabPane.getInsets(); 1263 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 1264 1265 int x = insets.left; 1266 int y = insets.top; 1267 int w = width - insets.right - insets.left; 1268 int h = height - insets.top - insets.bottom; 1269 1270 switch(tabPlacement) { 1271 case LEFT: 1272 x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 1273 if (tabsOverlapBorder) { 1274 x -= tabAreaInsets.right; 1275 } 1276 w -= (x - insets.left); 1277 break; 1278 case RIGHT: 1279 w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 1280 if (tabsOverlapBorder) { 1281 w += tabAreaInsets.left; 1282 } 1283 break; 1284 case BOTTOM: 1285 h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 1286 if (tabsOverlapBorder) { 1287 h += tabAreaInsets.top; 1288 } 1289 break; 1290 case TOP: 1291 default: 1292 y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 1293 if (tabsOverlapBorder) { 1294 y -= tabAreaInsets.bottom; 1295 } 1296 h -= (y - insets.top); 1297 } 1298 1299 if ( tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque()) ) { 1300 // Fill region behind content area 1301 Color color = UIManager.getColor("TabbedPane.contentAreaColor"); 1302 if (color != null) { 1303 g.setColor(color); 1304 } 1305 else if ( selectedColor == null || selectedIndex == -1 ) { 1306 g.setColor(tabPane.getBackground()); 1307 } 1308 else { 1309 g.setColor(selectedColor); 1310 } 1311 g.fillRect(x,y,w,h); 1312 } 1313 1314 paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1315 paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1316 paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1317 paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1318 1319 } 1320 1321 protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, 1322 int selectedIndex, 1323 int x, int y, int w, int h) { 1324 Rectangle selRect = selectedIndex < 0? null : 1325 getTabBounds(selectedIndex, calcRect); 1326 1327 g.setColor(lightHighlight); 1328 1329 // Draw unbroken line if tabs are not on TOP, OR 1330 // selected tab is not in run adjacent to content, OR 1331 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1332 // 1333 if (tabPlacement != TOP || selectedIndex < 0 || 1334 (selRect.y + selRect.height + 1 < y) || 1335 (selRect.x < x || selRect.x > x + w)) { 1336 g.drawLine(x, y, x+w-2, y); 1337 } else { 1338 // Break line to show visual connection to selected tab 1339 g.drawLine(x, y, selRect.x - 1, y); 1340 if (selRect.x + selRect.width < x + w - 2) { 1341 g.drawLine(selRect.x + selRect.width, y, 1342 x+w-2, y); 1343 } else { 1344 g.setColor(shadow); 1345 g.drawLine(x+w-2, y, x+w-2, y); 1346 } 1347 } 1348 } 1349 1350 protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, 1351 int selectedIndex, 1352 int x, int y, int w, int h) { 1353 Rectangle selRect = selectedIndex < 0? null : 1354 getTabBounds(selectedIndex, calcRect); 1355 1356 g.setColor(lightHighlight); 1357 1358 // Draw unbroken line if tabs are not on LEFT, OR 1359 // selected tab is not in run adjacent to content, OR 1360 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1361 // 1362 if (tabPlacement != LEFT || selectedIndex < 0 || 1363 (selRect.x + selRect.width + 1 < x) || 1364 (selRect.y < y || selRect.y > y + h)) { 1365 g.drawLine(x, y, x, y+h-2); 1366 } else { 1367 // Break line to show visual connection to selected tab 1368 g.drawLine(x, y, x, selRect.y - 1); 1369 if (selRect.y + selRect.height < y + h - 2) { 1370 g.drawLine(x, selRect.y + selRect.height, 1371 x, y+h-2); 1372 } 1373 } 1374 } 1375 1376 protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, 1377 int selectedIndex, 1378 int x, int y, int w, int h) { 1379 Rectangle selRect = selectedIndex < 0? null : 1380 getTabBounds(selectedIndex, calcRect); 1381 1382 g.setColor(shadow); 1383 1384 // Draw unbroken line if tabs are not on BOTTOM, OR 1385 // selected tab is not in run adjacent to content, OR 1386 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1387 // 1388 if (tabPlacement != BOTTOM || selectedIndex < 0 || 1389 (selRect.y - 1 > h) || 1390 (selRect.x < x || selRect.x > x + w)) { 1391 g.drawLine(x+1, y+h-2, x+w-2, y+h-2); 1392 g.setColor(darkShadow); 1393 g.drawLine(x, y+h-1, x+w-1, y+h-1); 1394 } else { 1395 // Break line to show visual connection to selected tab 1396 g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2); 1397 g.setColor(darkShadow); 1398 g.drawLine(x, y+h-1, selRect.x - 1, y+h-1); 1399 if (selRect.x + selRect.width < x + w - 2) { 1400 g.setColor(shadow); 1401 g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2); 1402 g.setColor(darkShadow); 1403 g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1); 1404 } 1405 } 1406 1407 } 1408 1409 protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, 1410 int selectedIndex, 1411 int x, int y, int w, int h) { 1412 Rectangle selRect = selectedIndex < 0? null : 1413 getTabBounds(selectedIndex, calcRect); 1414 1415 g.setColor(shadow); 1416 1417 // Draw unbroken line if tabs are not on RIGHT, OR 1418 // selected tab is not in run adjacent to content, OR 1419 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1420 // 1421 if (tabPlacement != RIGHT || selectedIndex < 0 || 1422 (selRect.x - 1 > w) || 1423 (selRect.y < y || selRect.y > y + h)) { 1424 g.drawLine(x+w-2, y+1, x+w-2, y+h-3); 1425 g.setColor(darkShadow); 1426 g.drawLine(x+w-1, y, x+w-1, y+h-1); 1427 } else { 1428 // Break line to show visual connection to selected tab 1429 g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1); 1430 g.setColor(darkShadow); 1431 g.drawLine(x+w-1, y, x+w-1, selRect.y - 1); 1432 1433 if (selRect.y + selRect.height < y + h - 2) { 1434 g.setColor(shadow); 1435 g.drawLine(x+w-2, selRect.y + selRect.height, 1436 x+w-2, y+h-2); 1437 g.setColor(darkShadow); 1438 g.drawLine(x+w-1, selRect.y + selRect.height, 1439 x+w-1, y+h-2); 1440 } 1441 } 1442 } 1443 1444 private void ensureCurrentLayout() { 1445 if (!tabPane.isValid()) { 1446 tabPane.validate(); 1447 } 1448 /* If tabPane doesn't have a peer yet, the validate() call will 1449 * silently fail. We handle that by forcing a layout if tabPane 1450 * is still invalid. See bug 4237677. 1451 */ 1452 if (!tabPane.isValid()) { 1453 TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout(); 1454 layout.calculateLayoutInfo(); 1455 } 1456 } 1457 1458 1459 // TabbedPaneUI methods 1460 1461 /** 1462 * Returns the bounds of the specified tab index. The bounds are 1463 * with respect to the JTabbedPane's coordinate space. 1464 */ 1465 public Rectangle getTabBounds(JTabbedPane pane, int i) { 1466 ensureCurrentLayout(); 1467 Rectangle tabRect = new Rectangle(); 1468 return getTabBounds(i, tabRect); 1469 } 1470 1471 public int getTabRunCount(JTabbedPane pane) { 1472 ensureCurrentLayout(); 1473 return runCount; 1474 } 1475 1476 /** 1477 * Returns the tab index which intersects the specified point 1478 * in the JTabbedPane's coordinate space. 1479 */ 1480 public int tabForCoordinate(JTabbedPane pane, int x, int y) { 1481 return tabForCoordinate(pane, x, y, true); 1482 } 1483 1484 private int tabForCoordinate(JTabbedPane pane, int x, int y, 1485 boolean validateIfNecessary) { 1486 if (validateIfNecessary) { 1487 ensureCurrentLayout(); 1488 } 1489 if (isRunsDirty) { 1490 // We didn't recalculate the layout, runs and tabCount may not 1491 // line up, bail. 1492 return -1; 1493 } 1494 Point p = new Point(x, y); 1495 1496 if (scrollableTabLayoutEnabled()) { 1497 translatePointToTabPanel(x, y, p); 1498 Rectangle viewRect = tabScroller.viewport.getViewRect(); 1499 if (!viewRect.contains(p)) { 1500 return -1; 1501 } 1502 } 1503 int tabCount = tabPane.getTabCount(); 1504 for (int i = 0; i < tabCount; i++) { 1505 if (rects[i].contains(p.x, p.y)) { 1506 return i; 1507 } 1508 } 1509 return -1; 1510 } 1511 1512 /** 1513 * Returns the bounds of the specified tab in the coordinate space 1514 * of the JTabbedPane component. This is required because the tab rects 1515 * are by default defined in the coordinate space of the component where 1516 * they are rendered, which could be the JTabbedPane 1517 * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT). 1518 * This method should be used whenever the tab rectangle must be relative 1519 * to the JTabbedPane itself and the result should be placed in a 1520 * designated Rectangle object (rather than instantiating and returning 1521 * a new Rectangle each time). The tab index parameter must be a valid 1522 * tabbed pane tab index (0 to tab count - 1, inclusive). The destination 1523 * rectangle parameter must be a valid <code>Rectangle</code> instance. 1524 * The handling of invalid parameters is unspecified. 1525 * 1526 * @param tabIndex the index of the tab 1527 * @param dest the rectangle where the result should be placed 1528 * @return the resulting rectangle 1529 * 1530 * @since 1.4 1531 */ 1532 protected Rectangle getTabBounds(int tabIndex, Rectangle dest) { 1533 dest.width = rects[tabIndex].width; 1534 dest.height = rects[tabIndex].height; 1535 1536 if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT 1537 // Need to translate coordinates based on viewport location & 1538 // view position 1539 Point vpp = tabScroller.viewport.getLocation(); 1540 Point viewp = tabScroller.viewport.getViewPosition(); 1541 dest.x = rects[tabIndex].x + vpp.x - viewp.x; 1542 dest.y = rects[tabIndex].y + vpp.y - viewp.y; 1543 1544 } else { // WRAP_TAB_LAYOUT 1545 dest.x = rects[tabIndex].x; 1546 dest.y = rects[tabIndex].y; 1547 } 1548 return dest; 1549 } 1550 1551 /** 1552 * Returns the index of the tab closest to the passed in location, note 1553 * that the returned tab may not contain the location x,y. 1554 */ 1555 private int getClosestTab(int x, int y) { 1556 int min = 0; 1557 int tabCount = Math.min(rects.length, tabPane.getTabCount()); 1558 int max = tabCount; 1559 int tabPlacement = tabPane.getTabPlacement(); 1560 boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM); 1561 int want = (useX) ? x : y; 1562 1563 while (min != max) { 1564 int current = (max + min) / 2; 1565 int minLoc; 1566 int maxLoc; 1567 1568 if (useX) { 1569 minLoc = rects[current].x; 1570 maxLoc = minLoc + rects[current].width; 1571 } 1572 else { 1573 minLoc = rects[current].y; 1574 maxLoc = minLoc + rects[current].height; 1575 } 1576 if (want < minLoc) { 1577 max = current; 1578 if (min == max) { 1579 return Math.max(0, current - 1); 1580 } 1581 } 1582 else if (want >= maxLoc) { 1583 min = current; 1584 if (max - min <= 1) { 1585 return Math.max(current + 1, tabCount - 1); 1586 } 1587 } 1588 else { 1589 return current; 1590 } 1591 } 1592 return min; 1593 } 1594 1595 /** 1596 * Returns a point which is translated from the specified point in the 1597 * JTabbedPane's coordinate space to the coordinate space of the 1598 * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY. 1599 */ 1600 private Point translatePointToTabPanel(int srcx, int srcy, Point dest) { 1601 Point vpp = tabScroller.viewport.getLocation(); 1602 Point viewp = tabScroller.viewport.getViewPosition(); 1603 dest.x = srcx - vpp.x + viewp.x; 1604 dest.y = srcy - vpp.y + viewp.y; 1605 return dest; 1606 } 1607 1608 // BasicTabbedPaneUI methods 1609 1610 protected Component getVisibleComponent() { 1611 return visibleComponent; 1612 } 1613 1614 protected void setVisibleComponent(Component component) { 1615 if (visibleComponent != null 1616 && visibleComponent != component 1617 && visibleComponent.getParent() == tabPane 1618 && visibleComponent.isVisible()) { 1619 1620 visibleComponent.setVisible(false); 1621 } 1622 if (component != null && !component.isVisible()) { 1623 component.setVisible(true); 1624 } 1625 visibleComponent = component; 1626 } 1627 1628 protected void assureRectsCreated(int tabCount) { 1629 int rectArrayLen = rects.length; 1630 if (tabCount != rectArrayLen ) { 1631 Rectangle[] tempRectArray = new Rectangle[tabCount]; 1632 System.arraycopy(rects, 0, tempRectArray, 0, 1633 Math.min(rectArrayLen, tabCount)); 1634 rects = tempRectArray; 1635 for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) { 1636 rects[rectIndex] = new Rectangle(); 1637 } 1638 } 1639 1640 } 1641 1642 protected void expandTabRunsArray() { 1643 int rectLen = tabRuns.length; 1644 int[] newArray = new int[rectLen+10]; 1645 System.arraycopy(tabRuns, 0, newArray, 0, runCount); 1646 tabRuns = newArray; 1647 } 1648 1649 protected int getRunForTab(int tabCount, int tabIndex) { 1650 for (int i = 0; i < runCount; i++) { 1651 int first = tabRuns[i]; 1652 int last = lastTabInRun(tabCount, i); 1653 if (tabIndex >= first && tabIndex <= last) { 1654 return i; 1655 } 1656 } 1657 return 0; 1658 } 1659 1660 protected int lastTabInRun(int tabCount, int run) { 1661 if (runCount == 1) { 1662 return tabCount - 1; 1663 } 1664 int nextRun = (run == runCount - 1? 0 : run + 1); 1665 if (tabRuns[nextRun] == 0) { 1666 return tabCount - 1; 1667 } 1668 return tabRuns[nextRun]-1; 1669 } 1670 1671 protected int getTabRunOverlay(int tabPlacement) { 1672 return tabRunOverlay; 1673 } 1674 1675 protected int getTabRunIndent(int tabPlacement, int run) { 1676 return 0; 1677 } 1678 1679 protected boolean shouldPadTabRun(int tabPlacement, int run) { 1680 return runCount > 1; 1681 } 1682 1683 protected boolean shouldRotateTabRuns(int tabPlacement) { 1684 return true; 1685 } 1686 1687 protected Icon getIconForTab(int tabIndex) { 1688 return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))? 1689 tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex); 1690 } 1691 1692 /** 1693 * Returns the text View object required to render stylized text (HTML) for 1694 * the specified tab or null if no specialized text rendering is needed 1695 * for this tab. This is provided to support html rendering inside tabs. 1696 * 1697 * @param tabIndex the index of the tab 1698 * @return the text view to render the tab's text or null if no 1699 * specialized rendering is required 1700 * 1701 * @since 1.4 1702 */ 1703 protected View getTextViewForTab(int tabIndex) { 1704 if (htmlViews != null) { 1705 return htmlViews.elementAt(tabIndex); 1706 } 1707 return null; 1708 } 1709 1710 protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) { 1711 int height = 0; 1712 Component c = tabPane.getTabComponentAt(tabIndex); 1713 if (c != null) { 1714 height = c.getPreferredSize().height; 1715 } else { 1716 View v = getTextViewForTab(tabIndex); 1717 if (v != null) { 1718 // html 1719 height += (int) v.getPreferredSpan(View.Y_AXIS); 1720 } else { 1721 // plain text 1722 height += fontHeight; 1723 } 1724 Icon icon = getIconForTab(tabIndex); 1725 1726 if (icon != null) { 1727 height = Math.max(height, icon.getIconHeight()); 1728 } 1729 } 1730 Insets tabInsets = getTabInsets(tabPlacement, tabIndex); 1731 height += tabInsets.top + tabInsets.bottom + 2; 1732 return height; 1733 } 1734 1735 protected int calculateMaxTabHeight(int tabPlacement) { 1736 FontMetrics metrics = getFontMetrics(); 1737 int tabCount = tabPane.getTabCount(); 1738 int result = 0; 1739 int fontHeight = metrics.getHeight(); 1740 for(int i = 0; i < tabCount; i++) { 1741 result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result); 1742 } 1743 return result; 1744 } 1745 1746 protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) { 1747 Insets tabInsets = getTabInsets(tabPlacement, tabIndex); 1748 int width = tabInsets.left + tabInsets.right + 3; 1749 Component tabComponent = tabPane.getTabComponentAt(tabIndex); 1750 if (tabComponent != null) { 1751 width += tabComponent.getPreferredSize().width; 1752 } else { 1753 Icon icon = getIconForTab(tabIndex); 1754 if (icon != null) { 1755 width += icon.getIconWidth() + textIconGap; 1756 } 1757 View v = getTextViewForTab(tabIndex); 1758 if (v != null) { 1759 // html 1760 width += (int) v.getPreferredSpan(View.X_AXIS); 1761 } else { 1762 // plain text 1763 String title = tabPane.getTitleAt(tabIndex); 1764 width += SwingUtilities2.stringWidth(tabPane, metrics, title); 1765 } 1766 } 1767 return width; 1768 } 1769 1770 protected int calculateMaxTabWidth(int tabPlacement) { 1771 FontMetrics metrics = getFontMetrics(); 1772 int tabCount = tabPane.getTabCount(); 1773 int result = 0; 1774 for(int i = 0; i < tabCount; i++) { 1775 result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result); 1776 } 1777 return result; 1778 } 1779 1780 protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) { 1781 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 1782 int tabRunOverlay = getTabRunOverlay(tabPlacement); 1783 return (horizRunCount > 0? 1784 horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay + 1785 tabAreaInsets.top + tabAreaInsets.bottom : 1786 0); 1787 } 1788 1789 protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) { 1790 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 1791 int tabRunOverlay = getTabRunOverlay(tabPlacement); 1792 return (vertRunCount > 0? 1793 vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay + 1794 tabAreaInsets.left + tabAreaInsets.right : 1795 0); 1796 } 1797 1798 protected Insets getTabInsets(int tabPlacement, int tabIndex) { 1799 return tabInsets; 1800 } 1801 1802 protected Insets getSelectedTabPadInsets(int tabPlacement) { 1803 rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement); 1804 return currentPadInsets; 1805 } 1806 1807 protected Insets getTabAreaInsets(int tabPlacement) { 1808 rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement); 1809 return currentTabAreaInsets; 1810 } 1811 1812 protected Insets getContentBorderInsets(int tabPlacement) { 1813 return contentBorderInsets; 1814 } 1815 1816 protected FontMetrics getFontMetrics() { 1817 Font font = tabPane.getFont(); 1818 return tabPane.getFontMetrics(font); 1819 } 1820 1821 1822 // Tab Navigation methods 1823 1824 protected void navigateSelectedTab(int direction) { 1825 int tabPlacement = tabPane.getTabPlacement(); 1826 int current = DefaultLookup.getBoolean(tabPane, this, 1827 "TabbedPane.selectionFollowsFocus", true) ? 1828 tabPane.getSelectedIndex() : getFocusIndex(); 1829 int tabCount = tabPane.getTabCount(); 1830 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 1831 1832 // If we have no tabs then don't navigate. 1833 if (tabCount <= 0) { 1834 return; 1835 } 1836 1837 int offset; 1838 switch(tabPlacement) { 1839 case LEFT: 1840 case RIGHT: 1841 switch(direction) { 1842 case NEXT: 1843 selectNextTab(current); 1844 break; 1845 case PREVIOUS: 1846 selectPreviousTab(current); 1847 break; 1848 case NORTH: 1849 selectPreviousTabInRun(current); 1850 break; 1851 case SOUTH: 1852 selectNextTabInRun(current); 1853 break; 1854 case WEST: 1855 offset = getTabRunOffset(tabPlacement, tabCount, current, false); 1856 selectAdjacentRunTab(tabPlacement, current, offset); 1857 break; 1858 case EAST: 1859 offset = getTabRunOffset(tabPlacement, tabCount, current, true); 1860 selectAdjacentRunTab(tabPlacement, current, offset); 1861 break; 1862 default: 1863 } 1864 break; 1865 case BOTTOM: 1866 case TOP: 1867 default: 1868 switch(direction) { 1869 case NEXT: 1870 selectNextTab(current); 1871 break; 1872 case PREVIOUS: 1873 selectPreviousTab(current); 1874 break; 1875 case NORTH: 1876 offset = getTabRunOffset(tabPlacement, tabCount, current, false); 1877 selectAdjacentRunTab(tabPlacement, current, offset); 1878 break; 1879 case SOUTH: 1880 offset = getTabRunOffset(tabPlacement, tabCount, current, true); 1881 selectAdjacentRunTab(tabPlacement, current, offset); 1882 break; 1883 case EAST: 1884 if (leftToRight) { 1885 selectNextTabInRun(current); 1886 } else { 1887 selectPreviousTabInRun(current); 1888 } 1889 break; 1890 case WEST: 1891 if (leftToRight) { 1892 selectPreviousTabInRun(current); 1893 } else { 1894 selectNextTabInRun(current); 1895 } 1896 break; 1897 default: 1898 } 1899 } 1900 } 1901 1902 protected void selectNextTabInRun(int current) { 1903 int tabCount = tabPane.getTabCount(); 1904 int tabIndex = getNextTabIndexInRun(tabCount, current); 1905 1906 while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1907 tabIndex = getNextTabIndexInRun(tabCount, tabIndex); 1908 } 1909 navigateTo(tabIndex); 1910 } 1911 1912 protected void selectPreviousTabInRun(int current) { 1913 int tabCount = tabPane.getTabCount(); 1914 int tabIndex = getPreviousTabIndexInRun(tabCount, current); 1915 1916 while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1917 tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex); 1918 } 1919 navigateTo(tabIndex); 1920 } 1921 1922 protected void selectNextTab(int current) { 1923 int tabIndex = getNextTabIndex(current); 1924 1925 while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1926 tabIndex = getNextTabIndex(tabIndex); 1927 } 1928 navigateTo(tabIndex); 1929 } 1930 1931 protected void selectPreviousTab(int current) { 1932 int tabIndex = getPreviousTabIndex(current); 1933 1934 while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1935 tabIndex = getPreviousTabIndex(tabIndex); 1936 } 1937 navigateTo(tabIndex); 1938 } 1939 1940 protected void selectAdjacentRunTab(int tabPlacement, 1941 int tabIndex, int offset) { 1942 if ( runCount < 2 ) { 1943 return; 1944 } 1945 int newIndex; 1946 Rectangle r = rects[tabIndex]; 1947 switch(tabPlacement) { 1948 case LEFT: 1949 case RIGHT: 1950 newIndex = tabForCoordinate(tabPane, r.x + r.width/2 + offset, 1951 r.y + r.height/2); 1952 break; 1953 case BOTTOM: 1954 case TOP: 1955 default: 1956 newIndex = tabForCoordinate(tabPane, r.x + r.width/2, 1957 r.y + r.height/2 + offset); 1958 } 1959 if (newIndex != -1) { 1960 while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) { 1961 newIndex = getNextTabIndex(newIndex); 1962 } 1963 navigateTo(newIndex); 1964 } 1965 } 1966 1967 private void navigateTo(int index) { 1968 if (DefaultLookup.getBoolean(tabPane, this, 1969 "TabbedPane.selectionFollowsFocus", true)) { 1970 tabPane.setSelectedIndex(index); 1971 } else { 1972 // Just move focus (not selection) 1973 setFocusIndex(index, true); 1974 } 1975 } 1976 1977 void setFocusIndex(int index, boolean repaint) { 1978 if (repaint && !isRunsDirty) { 1979 repaintTab(focusIndex); 1980 focusIndex = index; 1981 repaintTab(focusIndex); 1982 } 1983 else { 1984 focusIndex = index; 1985 } 1986 } 1987 1988 /** 1989 * Repaints the specified tab. 1990 */ 1991 private void repaintTab(int index) { 1992 // If we're not valid that means we will shortly be validated and 1993 // painted, which means we don't have to do anything here. 1994 if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) { 1995 tabPane.repaint(getTabBounds(tabPane, index)); 1996 } 1997 } 1998 1999 /** 2000 * Makes sure the focusIndex is valid. 2001 */ 2002 private void validateFocusIndex() { 2003 if (focusIndex >= tabPane.getTabCount()) { 2004 setFocusIndex(tabPane.getSelectedIndex(), false); 2005 } 2006 } 2007 2008 /** 2009 * Returns the index of the tab that has focus. 2010 * 2011 * @return index of tab that has focus 2012 * @since 1.5 2013 */ 2014 protected int getFocusIndex() { 2015 return focusIndex; 2016 } 2017 2018 protected int getTabRunOffset(int tabPlacement, int tabCount, 2019 int tabIndex, boolean forward) { 2020 int run = getRunForTab(tabCount, tabIndex); 2021 int offset; 2022 switch(tabPlacement) { 2023 case LEFT: { 2024 if (run == 0) { 2025 offset = (forward? 2026 -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) : 2027 -maxTabWidth); 2028 2029 } else if (run == runCount - 1) { 2030 offset = (forward? 2031 maxTabWidth : 2032 calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth); 2033 } else { 2034 offset = (forward? maxTabWidth : -maxTabWidth); 2035 } 2036 break; 2037 } 2038 case RIGHT: { 2039 if (run == 0) { 2040 offset = (forward? 2041 maxTabWidth : 2042 calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth); 2043 } else if (run == runCount - 1) { 2044 offset = (forward? 2045 -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) : 2046 -maxTabWidth); 2047 } else { 2048 offset = (forward? maxTabWidth : -maxTabWidth); 2049 } 2050 break; 2051 } 2052 case BOTTOM: { 2053 if (run == 0) { 2054 offset = (forward? 2055 maxTabHeight : 2056 calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight); 2057 } else if (run == runCount - 1) { 2058 offset = (forward? 2059 -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) : 2060 -maxTabHeight); 2061 } else { 2062 offset = (forward? maxTabHeight : -maxTabHeight); 2063 } 2064 break; 2065 } 2066 case TOP: 2067 default: { 2068 if (run == 0) { 2069 offset = (forward? 2070 -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) : 2071 -maxTabHeight); 2072 } else if (run == runCount - 1) { 2073 offset = (forward? 2074 maxTabHeight : 2075 calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight); 2076 } else { 2077 offset = (forward? maxTabHeight : -maxTabHeight); 2078 } 2079 } 2080 } 2081 return offset; 2082 } 2083 2084 protected int getPreviousTabIndex(int base) { 2085 int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1); 2086 return (tabIndex >= 0? tabIndex : 0); 2087 } 2088 2089 protected int getNextTabIndex(int base) { 2090 return (base+1)%tabPane.getTabCount(); 2091 } 2092 2093 protected int getNextTabIndexInRun(int tabCount, int base) { 2094 if (runCount < 2) { 2095 return getNextTabIndex(base); 2096 } 2097 int currentRun = getRunForTab(tabCount, base); 2098 int next = getNextTabIndex(base); 2099 if (next == tabRuns[getNextTabRun(currentRun)]) { 2100 return tabRuns[currentRun]; 2101 } 2102 return next; 2103 } 2104 2105 protected int getPreviousTabIndexInRun(int tabCount, int base) { 2106 if (runCount < 2) { 2107 return getPreviousTabIndex(base); 2108 } 2109 int currentRun = getRunForTab(tabCount, base); 2110 if (base == tabRuns[currentRun]) { 2111 int previous = tabRuns[getNextTabRun(currentRun)]-1; 2112 return (previous != -1? previous : tabCount-1); 2113 } 2114 return getPreviousTabIndex(base); 2115 } 2116 2117 protected int getPreviousTabRun(int baseRun) { 2118 int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1); 2119 return (runIndex >= 0? runIndex : 0); 2120 } 2121 2122 protected int getNextTabRun(int baseRun) { 2123 return (baseRun+1)%runCount; 2124 } 2125 2126 protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) { 2127 2128 switch(targetPlacement) { 2129 case LEFT: 2130 targetInsets.top = topInsets.left; 2131 targetInsets.left = topInsets.top; 2132 targetInsets.bottom = topInsets.right; 2133 targetInsets.right = topInsets.bottom; 2134 break; 2135 case BOTTOM: 2136 targetInsets.top = topInsets.bottom; 2137 targetInsets.left = topInsets.left; 2138 targetInsets.bottom = topInsets.top; 2139 targetInsets.right = topInsets.right; 2140 break; 2141 case RIGHT: 2142 targetInsets.top = topInsets.left; 2143 targetInsets.left = topInsets.bottom; 2144 targetInsets.bottom = topInsets.right; 2145 targetInsets.right = topInsets.top; 2146 break; 2147 case TOP: 2148 default: 2149 targetInsets.top = topInsets.top; 2150 targetInsets.left = topInsets.left; 2151 targetInsets.bottom = topInsets.bottom; 2152 targetInsets.right = topInsets.right; 2153 } 2154 } 2155 2156 // REMIND(aim,7/29/98): This method should be made 2157 // protected in the next release where 2158 // API changes are allowed 2159 boolean requestFocusForVisibleComponent() { 2160 return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent()); 2161 } 2162 2163 private static class Actions extends UIAction { 2164 final static String NEXT = "navigateNext"; 2165 final static String PREVIOUS = "navigatePrevious"; 2166 final static String RIGHT = "navigateRight"; 2167 final static String LEFT = "navigateLeft"; 2168 final static String UP = "navigateUp"; 2169 final static String DOWN = "navigateDown"; 2170 final static String PAGE_UP = "navigatePageUp"; 2171 final static String PAGE_DOWN = "navigatePageDown"; 2172 final static String REQUEST_FOCUS = "requestFocus"; 2173 final static String REQUEST_FOCUS_FOR_VISIBLE = 2174 "requestFocusForVisibleComponent"; 2175 final static String SET_SELECTED = "setSelectedIndex"; 2176 final static String SELECT_FOCUSED = "selectTabWithFocus"; 2177 final static String SCROLL_FORWARD = "scrollTabsForwardAction"; 2178 final static String SCROLL_BACKWARD = "scrollTabsBackwardAction"; 2179 2180 Actions(String key) { 2181 super(key); 2182 } 2183 2184 public void actionPerformed(ActionEvent e) { 2185 String key = getName(); 2186 JTabbedPane pane = (JTabbedPane)e.getSource(); 2187 BasicTabbedPaneUI ui = (BasicTabbedPaneUI)BasicLookAndFeel. 2188 getUIOfType(pane.getUI(), BasicTabbedPaneUI.class); 2189 2190 if (ui == null) { 2191 return; 2192 } 2193 if (key == NEXT) { 2194 ui.navigateSelectedTab(SwingConstants.NEXT); 2195 } 2196 else if (key == PREVIOUS) { 2197 ui.navigateSelectedTab(SwingConstants.PREVIOUS); 2198 } 2199 else if (key == RIGHT) { 2200 ui.navigateSelectedTab(SwingConstants.EAST); 2201 } 2202 else if (key == LEFT) { 2203 ui.navigateSelectedTab(SwingConstants.WEST); 2204 } 2205 else if (key == UP) { 2206 ui.navigateSelectedTab(SwingConstants.NORTH); 2207 } 2208 else if (key == DOWN) { 2209 ui.navigateSelectedTab(SwingConstants.SOUTH); 2210 } 2211 else if (key == PAGE_UP) { 2212 int tabPlacement = pane.getTabPlacement(); 2213 if (tabPlacement == TOP|| tabPlacement == BOTTOM) { 2214 ui.navigateSelectedTab(SwingConstants.WEST); 2215 } else { 2216 ui.navigateSelectedTab(SwingConstants.NORTH); 2217 } 2218 } 2219 else if (key == PAGE_DOWN) { 2220 int tabPlacement = pane.getTabPlacement(); 2221 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 2222 ui.navigateSelectedTab(SwingConstants.EAST); 2223 } else { 2224 ui.navigateSelectedTab(SwingConstants.SOUTH); 2225 } 2226 } 2227 else if (key == REQUEST_FOCUS) { 2228 pane.requestFocus(); 2229 } 2230 else if (key == REQUEST_FOCUS_FOR_VISIBLE) { 2231 ui.requestFocusForVisibleComponent(); 2232 } 2233 else if (key == SET_SELECTED) { 2234 String command = e.getActionCommand(); 2235 2236 if (command != null && command.length() > 0) { 2237 int mnemonic = (int)e.getActionCommand().charAt(0); 2238 if (mnemonic >= 'a' && mnemonic <='z') { 2239 mnemonic -= ('a' - 'A'); 2240 } 2241 Integer index = ui.mnemonicToIndexMap.get(Integer.valueOf(mnemonic)); 2242 if (index != null && pane.isEnabledAt(index.intValue())) { 2243 pane.setSelectedIndex(index.intValue()); 2244 } 2245 } 2246 } 2247 else if (key == SELECT_FOCUSED) { 2248 int focusIndex = ui.getFocusIndex(); 2249 if (focusIndex != -1) { 2250 pane.setSelectedIndex(focusIndex); 2251 } 2252 } 2253 else if (key == SCROLL_FORWARD) { 2254 if (ui.scrollableTabLayoutEnabled()) { 2255 ui.tabScroller.scrollForward(pane.getTabPlacement()); 2256 } 2257 } 2258 else if (key == SCROLL_BACKWARD) { 2259 if (ui.scrollableTabLayoutEnabled()) { 2260 ui.tabScroller.scrollBackward(pane.getTabPlacement()); 2261 } 2262 } 2263 } 2264 } 2265 2266 /** 2267 * This class should be treated as a "protected" inner class. 2268 * Instantiate it only within subclasses of BasicTabbedPaneUI. 2269 */ 2270 public class TabbedPaneLayout implements LayoutManager { 2271 2272 public void addLayoutComponent(String name, Component comp) {} 2273 2274 public void removeLayoutComponent(Component comp) {} 2275 2276 public Dimension preferredLayoutSize(Container parent) { 2277 return calculateSize(false); 2278 } 2279 2280 public Dimension minimumLayoutSize(Container parent) { 2281 return calculateSize(true); 2282 } 2283 2284 protected Dimension calculateSize(boolean minimum) { 2285 int tabPlacement = tabPane.getTabPlacement(); 2286 Insets insets = tabPane.getInsets(); 2287 Insets contentInsets = getContentBorderInsets(tabPlacement); 2288 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 2289 2290 Dimension zeroSize = new Dimension(0,0); 2291 int height = 0; 2292 int width = 0; 2293 int cWidth = 0; 2294 int cHeight = 0; 2295 2296 // Determine minimum size required to display largest 2297 // child in each dimension 2298 // 2299 for (int i = 0; i < tabPane.getTabCount(); i++) { 2300 Component component = tabPane.getComponentAt(i); 2301 if (component != null) { 2302 Dimension size = minimum ? component.getMinimumSize() : 2303 component.getPreferredSize(); 2304 2305 if (size != null) { 2306 cHeight = Math.max(size.height, cHeight); 2307 cWidth = Math.max(size.width, cWidth); 2308 } 2309 } 2310 } 2311 // Add content border insets to minimum size 2312 width += cWidth; 2313 height += cHeight; 2314 int tabExtent; 2315 2316 // Calculate how much space the tabs will need, based on the 2317 // minimum size required to display largest child + content border 2318 // 2319 switch(tabPlacement) { 2320 case LEFT: 2321 case RIGHT: 2322 height = Math.max(height, calculateMaxTabHeight(tabPlacement)); 2323 tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom); 2324 width += tabExtent; 2325 break; 2326 case TOP: 2327 case BOTTOM: 2328 default: 2329 width = Math.max(width, calculateMaxTabWidth(tabPlacement)); 2330 tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right); 2331 height += tabExtent; 2332 } 2333 return new Dimension(width + insets.left + insets.right + contentInsets.left + contentInsets.right, 2334 height + insets.bottom + insets.top + contentInsets.top + contentInsets.bottom); 2335 2336 } 2337 2338 protected int preferredTabAreaHeight(int tabPlacement, int width) { 2339 FontMetrics metrics = getFontMetrics(); 2340 int tabCount = tabPane.getTabCount(); 2341 int total = 0; 2342 if (tabCount > 0) { 2343 int rows = 1; 2344 int x = 0; 2345 2346 int maxTabHeight = calculateMaxTabHeight(tabPlacement); 2347 2348 for (int i = 0; i < tabCount; i++) { 2349 int tabWidth = calculateTabWidth(tabPlacement, i, metrics); 2350 2351 if (x != 0 && x + tabWidth > width) { 2352 rows++; 2353 x = 0; 2354 } 2355 x += tabWidth; 2356 } 2357 total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight); 2358 } 2359 return total; 2360 } 2361 2362 protected int preferredTabAreaWidth(int tabPlacement, int height) { 2363 FontMetrics metrics = getFontMetrics(); 2364 int tabCount = tabPane.getTabCount(); 2365 int total = 0; 2366 if (tabCount > 0) { 2367 int columns = 1; 2368 int y = 0; 2369 int fontHeight = metrics.getHeight(); 2370 2371 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2372 2373 for (int i = 0; i < tabCount; i++) { 2374 int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight); 2375 2376 if (y != 0 && y + tabHeight > height) { 2377 columns++; 2378 y = 0; 2379 } 2380 y += tabHeight; 2381 } 2382 total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth); 2383 } 2384 return total; 2385 } 2386 2387 public void layoutContainer(Container parent) { 2388 /* Some of the code in this method deals with changing the 2389 * visibility of components to hide and show the contents for the 2390 * selected tab. This is older code that has since been duplicated 2391 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2392 * changes to happen sooner (see the note there). This code remains 2393 * for backward compatibility as there are some cases, such as 2394 * subclasses that don't fireStateChanged() where it may be used. 2395 * Any changes here need to be kept in synch with 2396 * JTabbedPane.fireStateChanged(). 2397 */ 2398 2399 setRolloverTab(-1); 2400 2401 int tabPlacement = tabPane.getTabPlacement(); 2402 Insets insets = tabPane.getInsets(); 2403 int selectedIndex = tabPane.getSelectedIndex(); 2404 Component visibleComponent = getVisibleComponent(); 2405 2406 calculateLayoutInfo(); 2407 2408 Component selectedComponent = null; 2409 if (selectedIndex < 0) { 2410 if (visibleComponent != null) { 2411 // The last tab was removed, so remove the component 2412 setVisibleComponent(null); 2413 } 2414 } else { 2415 selectedComponent = tabPane.getComponentAt(selectedIndex); 2416 } 2417 int cx, cy, cw, ch; 2418 int totalTabWidth = 0; 2419 int totalTabHeight = 0; 2420 Insets contentInsets = getContentBorderInsets(tabPlacement); 2421 2422 boolean shouldChangeFocus = false; 2423 2424 // In order to allow programs to use a single component 2425 // as the display for multiple tabs, we will not change 2426 // the visible compnent if the currently selected tab 2427 // has a null component. This is a bit dicey, as we don't 2428 // explicitly state we support this in the spec, but since 2429 // programs are now depending on this, we're making it work. 2430 // 2431 if(selectedComponent != null) { 2432 if(selectedComponent != visibleComponent && 2433 visibleComponent != null) { 2434 if(SwingUtilities.findFocusOwner(visibleComponent) != null) { 2435 shouldChangeFocus = true; 2436 } 2437 } 2438 setVisibleComponent(selectedComponent); 2439 } 2440 2441 Rectangle bounds = tabPane.getBounds(); 2442 int numChildren = tabPane.getComponentCount(); 2443 2444 if(numChildren > 0) { 2445 2446 switch(tabPlacement) { 2447 case LEFT: 2448 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2449 cx = insets.left + totalTabWidth + contentInsets.left; 2450 cy = insets.top + contentInsets.top; 2451 break; 2452 case RIGHT: 2453 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2454 cx = insets.left + contentInsets.left; 2455 cy = insets.top + contentInsets.top; 2456 break; 2457 case BOTTOM: 2458 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2459 cx = insets.left + contentInsets.left; 2460 cy = insets.top + contentInsets.top; 2461 break; 2462 case TOP: 2463 default: 2464 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2465 cx = insets.left + contentInsets.left; 2466 cy = insets.top + totalTabHeight + contentInsets.top; 2467 } 2468 2469 cw = bounds.width - totalTabWidth - 2470 insets.left - insets.right - 2471 contentInsets.left - contentInsets.right; 2472 ch = bounds.height - totalTabHeight - 2473 insets.top - insets.bottom - 2474 contentInsets.top - contentInsets.bottom; 2475 2476 for(int i = 0; i < numChildren; i++) { 2477 Component child = tabPane.getComponent(i); 2478 if(child == tabContainer) { 2479 2480 int tabContainerWidth = totalTabWidth == 0 ? bounds.width : 2481 totalTabWidth + insets.left + insets.right + 2482 contentInsets.left + contentInsets.right; 2483 int tabContainerHeight = totalTabHeight == 0 ? bounds.height : 2484 totalTabHeight + insets.top + insets.bottom + 2485 contentInsets.top + contentInsets.bottom; 2486 2487 int tabContainerX = 0; 2488 int tabContainerY = 0; 2489 if(tabPlacement == BOTTOM) { 2490 tabContainerY = bounds.height - tabContainerHeight; 2491 } else if(tabPlacement == RIGHT) { 2492 tabContainerX = bounds.width - tabContainerWidth; 2493 } 2494 child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight); 2495 } else { 2496 child.setBounds(cx, cy, cw, ch); 2497 } 2498 } 2499 } 2500 layoutTabComponents(); 2501 if(shouldChangeFocus) { 2502 if(!requestFocusForVisibleComponent()) { 2503 tabPane.requestFocus(); 2504 } 2505 } 2506 } 2507 2508 public void calculateLayoutInfo() { 2509 int tabCount = tabPane.getTabCount(); 2510 assureRectsCreated(tabCount); 2511 calculateTabRects(tabPane.getTabPlacement(), tabCount); 2512 isRunsDirty = false; 2513 } 2514 2515 private void layoutTabComponents() { 2516 if (tabContainer == null) { 2517 return; 2518 } 2519 Rectangle rect = new Rectangle(); 2520 Point delta = new Point(-tabContainer.getX(), -tabContainer.getY()); 2521 if (scrollableTabLayoutEnabled()) { 2522 translatePointToTabPanel(0, 0, delta); 2523 } 2524 for (int i = 0; i < tabPane.getTabCount(); i++) { 2525 Component c = tabPane.getTabComponentAt(i); 2526 if (c == null) { 2527 continue; 2528 } 2529 getTabBounds(i, rect); 2530 Dimension preferredSize = c.getPreferredSize(); 2531 Insets insets = getTabInsets(tabPane.getTabPlacement(), i); 2532 int outerX = rect.x + insets.left + delta.x; 2533 int outerY = rect.y + insets.top + delta.y; 2534 int outerWidth = rect.width - insets.left - insets.right; 2535 int outerHeight = rect.height - insets.top - insets.bottom; 2536 //centralize component 2537 int x = outerX + (outerWidth - preferredSize.width) / 2; 2538 int y = outerY + (outerHeight - preferredSize.height) / 2; 2539 int tabPlacement = tabPane.getTabPlacement(); 2540 boolean isSeleceted = i == tabPane.getSelectedIndex(); 2541 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted), 2542 y + getTabLabelShiftY(tabPlacement, i, isSeleceted), 2543 preferredSize.width, preferredSize.height); 2544 } 2545 } 2546 2547 protected void calculateTabRects(int tabPlacement, int tabCount) { 2548 FontMetrics metrics = getFontMetrics(); 2549 Dimension size = tabPane.getSize(); 2550 Insets insets = tabPane.getInsets(); 2551 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 2552 int fontHeight = metrics.getHeight(); 2553 int selectedIndex = tabPane.getSelectedIndex(); 2554 int tabRunOverlay; 2555 int i, j; 2556 int x, y; 2557 int returnAt; 2558 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2559 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 2560 2561 // 2562 // Calculate bounds within which a tab run must fit 2563 // 2564 switch(tabPlacement) { 2565 case LEFT: 2566 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2567 x = insets.left + tabAreaInsets.left; 2568 y = insets.top + tabAreaInsets.top; 2569 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2570 break; 2571 case RIGHT: 2572 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2573 x = size.width - insets.right - tabAreaInsets.right - maxTabWidth; 2574 y = insets.top + tabAreaInsets.top; 2575 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2576 break; 2577 case BOTTOM: 2578 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2579 x = insets.left + tabAreaInsets.left; 2580 y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight; 2581 returnAt = size.width - (insets.right + tabAreaInsets.right); 2582 break; 2583 case TOP: 2584 default: 2585 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2586 x = insets.left + tabAreaInsets.left; 2587 y = insets.top + tabAreaInsets.top; 2588 returnAt = size.width - (insets.right + tabAreaInsets.right); 2589 break; 2590 } 2591 2592 tabRunOverlay = getTabRunOverlay(tabPlacement); 2593 2594 runCount = 0; 2595 selectedRun = -1; 2596 2597 if (tabCount == 0) { 2598 return; 2599 } 2600 2601 // Run through tabs and partition them into runs 2602 Rectangle rect; 2603 for (i = 0; i < tabCount; i++) { 2604 rect = rects[i]; 2605 2606 if (!verticalTabRuns) { 2607 // Tabs on TOP or BOTTOM.... 2608 if (i > 0) { 2609 rect.x = rects[i-1].x + rects[i-1].width; 2610 } else { 2611 tabRuns[0] = 0; 2612 runCount = 1; 2613 maxTabWidth = 0; 2614 rect.x = x; 2615 } 2616 rect.width = calculateTabWidth(tabPlacement, i, metrics); 2617 maxTabWidth = Math.max(maxTabWidth, rect.width); 2618 2619 // Never move a TAB down a run if it is in the first column. 2620 // Even if there isn't enough room, moving it to a fresh 2621 // line won't help. 2622 if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) { 2623 if (runCount > tabRuns.length - 1) { 2624 expandTabRunsArray(); 2625 } 2626 tabRuns[runCount] = i; 2627 runCount++; 2628 rect.x = x; 2629 } 2630 // Initialize y position in case there's just one run 2631 rect.y = y; 2632 rect.height = maxTabHeight/* - 2*/; 2633 2634 } else { 2635 // Tabs on LEFT or RIGHT... 2636 if (i > 0) { 2637 rect.y = rects[i-1].y + rects[i-1].height; 2638 } else { 2639 tabRuns[0] = 0; 2640 runCount = 1; 2641 maxTabHeight = 0; 2642 rect.y = y; 2643 } 2644 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 2645 maxTabHeight = Math.max(maxTabHeight, rect.height); 2646 2647 // Never move a TAB over a run if it is in the first run. 2648 // Even if there isn't enough room, moving it to a fresh 2649 // column won't help. 2650 if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) { 2651 if (runCount > tabRuns.length - 1) { 2652 expandTabRunsArray(); 2653 } 2654 tabRuns[runCount] = i; 2655 runCount++; 2656 rect.y = y; 2657 } 2658 // Initialize x position in case there's just one column 2659 rect.x = x; 2660 rect.width = maxTabWidth/* - 2*/; 2661 2662 } 2663 if (i == selectedIndex) { 2664 selectedRun = runCount - 1; 2665 } 2666 } 2667 2668 if (runCount > 1) { 2669 // Re-distribute tabs in case last run has leftover space 2670 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt); 2671 2672 selectedRun = getRunForTab(tabCount, selectedIndex); 2673 2674 // Rotate run array so that selected run is first 2675 if (shouldRotateTabRuns(tabPlacement)) { 2676 rotateTabRuns(tabPlacement, selectedRun); 2677 } 2678 } 2679 2680 // Step through runs from back to front to calculate 2681 // tab y locations and to pad runs appropriately 2682 for (i = runCount - 1; i >= 0; i--) { 2683 int start = tabRuns[i]; 2684 int next = tabRuns[i == (runCount - 1)? 0 : i + 1]; 2685 int end = (next != 0? next - 1 : tabCount - 1); 2686 if (!verticalTabRuns) { 2687 for (j = start; j <= end; j++) { 2688 rect = rects[j]; 2689 rect.y = y; 2690 rect.x += getTabRunIndent(tabPlacement, i); 2691 } 2692 if (shouldPadTabRun(tabPlacement, i)) { 2693 padTabRun(tabPlacement, start, end, returnAt); 2694 } 2695 if (tabPlacement == BOTTOM) { 2696 y -= (maxTabHeight - tabRunOverlay); 2697 } else { 2698 y += (maxTabHeight - tabRunOverlay); 2699 } 2700 } else { 2701 for (j = start; j <= end; j++) { 2702 rect = rects[j]; 2703 rect.x = x; 2704 rect.y += getTabRunIndent(tabPlacement, i); 2705 } 2706 if (shouldPadTabRun(tabPlacement, i)) { 2707 padTabRun(tabPlacement, start, end, returnAt); 2708 } 2709 if (tabPlacement == RIGHT) { 2710 x -= (maxTabWidth - tabRunOverlay); 2711 } else { 2712 x += (maxTabWidth - tabRunOverlay); 2713 } 2714 } 2715 } 2716 2717 // Pad the selected tab so that it appears raised in front 2718 padSelectedTab(tabPlacement, selectedIndex); 2719 2720 // if right to left and tab placement on the top or 2721 // the bottom, flip x positions and adjust by widths 2722 if (!leftToRight && !verticalTabRuns) { 2723 int rightMargin = size.width 2724 - (insets.right + tabAreaInsets.right); 2725 for (i = 0; i < tabCount; i++) { 2726 rects[i].x = rightMargin - rects[i].x - rects[i].width; 2727 } 2728 } 2729 } 2730 2731 2732 /* 2733 * Rotates the run-index array so that the selected run is run[0] 2734 */ 2735 protected void rotateTabRuns(int tabPlacement, int selectedRun) { 2736 for (int i = 0; i < selectedRun; i++) { 2737 int save = tabRuns[0]; 2738 for (int j = 1; j < runCount; j++) { 2739 tabRuns[j - 1] = tabRuns[j]; 2740 } 2741 tabRuns[runCount-1] = save; 2742 } 2743 } 2744 2745 protected void normalizeTabRuns(int tabPlacement, int tabCount, 2746 int start, int max) { 2747 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2748 int run = runCount - 1; 2749 boolean keepAdjusting = true; 2750 double weight = 1.25; 2751 2752 // At this point the tab runs are packed to fit as many 2753 // tabs as possible, which can leave the last run with a lot 2754 // of extra space (resulting in very fat tabs on the last run). 2755 // So we'll attempt to distribute this extra space more evenly 2756 // across the runs in order to make the runs look more consistent. 2757 // 2758 // Starting with the last run, determine whether the last tab in 2759 // the previous run would fit (generously) in this run; if so, 2760 // move tab to current run and shift tabs accordingly. Cycle 2761 // through remaining runs using the same algorithm. 2762 // 2763 while (keepAdjusting) { 2764 int last = lastTabInRun(tabCount, run); 2765 int prevLast = lastTabInRun(tabCount, run-1); 2766 int end; 2767 int prevLastLen; 2768 2769 if (!verticalTabRuns) { 2770 end = rects[last].x + rects[last].width; 2771 prevLastLen = (int)(maxTabWidth*weight); 2772 } else { 2773 end = rects[last].y + rects[last].height; 2774 prevLastLen = (int)(maxTabHeight*weight*2); 2775 } 2776 2777 // Check if the run has enough extra space to fit the last tab 2778 // from the previous row... 2779 if (max - end > prevLastLen) { 2780 2781 // Insert tab from previous row and shift rest over 2782 tabRuns[run] = prevLast; 2783 if (!verticalTabRuns) { 2784 rects[prevLast].x = start; 2785 } else { 2786 rects[prevLast].y = start; 2787 } 2788 for (int i = prevLast+1; i <= last; i++) { 2789 if (!verticalTabRuns) { 2790 rects[i].x = rects[i-1].x + rects[i-1].width; 2791 } else { 2792 rects[i].y = rects[i-1].y + rects[i-1].height; 2793 } 2794 } 2795 2796 } else if (run == runCount - 1) { 2797 // no more room left in last run, so we're done! 2798 keepAdjusting = false; 2799 } 2800 if (run - 1 > 0) { 2801 // check previous run next... 2802 run -= 1; 2803 } else { 2804 // check last run again...but require a higher ratio 2805 // of extraspace-to-tabsize because we don't want to 2806 // end up with too many tabs on the last run! 2807 run = runCount - 1; 2808 weight += .25; 2809 } 2810 } 2811 } 2812 2813 protected void padTabRun(int tabPlacement, int start, int end, int max) { 2814 Rectangle lastRect = rects[end]; 2815 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 2816 int runWidth = (lastRect.x + lastRect.width) - rects[start].x; 2817 int deltaWidth = max - (lastRect.x + lastRect.width); 2818 float factor = (float)deltaWidth / (float)runWidth; 2819 2820 for (int j = start; j <= end; j++) { 2821 Rectangle pastRect = rects[j]; 2822 if (j > start) { 2823 pastRect.x = rects[j-1].x + rects[j-1].width; 2824 } 2825 pastRect.width += Math.round((float)pastRect.width * factor); 2826 } 2827 lastRect.width = max - lastRect.x; 2828 } else { 2829 int runHeight = (lastRect.y + lastRect.height) - rects[start].y; 2830 int deltaHeight = max - (lastRect.y + lastRect.height); 2831 float factor = (float)deltaHeight / (float)runHeight; 2832 2833 for (int j = start; j <= end; j++) { 2834 Rectangle pastRect = rects[j]; 2835 if (j > start) { 2836 pastRect.y = rects[j-1].y + rects[j-1].height; 2837 } 2838 pastRect.height += Math.round((float)pastRect.height * factor); 2839 } 2840 lastRect.height = max - lastRect.y; 2841 } 2842 } 2843 2844 protected void padSelectedTab(int tabPlacement, int selectedIndex) { 2845 2846 if (selectedIndex >= 0) { 2847 Rectangle selRect = rects[selectedIndex]; 2848 Insets padInsets = getSelectedTabPadInsets(tabPlacement); 2849 selRect.x -= padInsets.left; 2850 selRect.width += (padInsets.left + padInsets.right); 2851 selRect.y -= padInsets.top; 2852 selRect.height += (padInsets.top + padInsets.bottom); 2853 2854 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT 2855 // do not expand selected tab more then necessary 2856 Dimension size = tabPane.getSize(); 2857 Insets insets = tabPane.getInsets(); 2858 2859 if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) { 2860 int top = insets.top - selRect.y; 2861 if (top > 0) { 2862 selRect.y += top; 2863 selRect.height -= top; 2864 } 2865 int bottom = (selRect.y + selRect.height) + insets.bottom - size.height; 2866 if (bottom > 0) { 2867 selRect.height -= bottom; 2868 } 2869 } else { 2870 int left = insets.left - selRect.x; 2871 if (left > 0) { 2872 selRect.x += left; 2873 selRect.width -= left; 2874 } 2875 int right = (selRect.x + selRect.width) + insets.right - size.width; 2876 if (right > 0) { 2877 selRect.width -= right; 2878 } 2879 } 2880 } 2881 } 2882 } 2883 } 2884 2885 private class TabbedPaneScrollLayout extends TabbedPaneLayout { 2886 2887 protected int preferredTabAreaHeight(int tabPlacement, int width) { 2888 return calculateMaxTabHeight(tabPlacement); 2889 } 2890 2891 protected int preferredTabAreaWidth(int tabPlacement, int height) { 2892 return calculateMaxTabWidth(tabPlacement); 2893 } 2894 2895 public void layoutContainer(Container parent) { 2896 /* Some of the code in this method deals with changing the 2897 * visibility of components to hide and show the contents for the 2898 * selected tab. This is older code that has since been duplicated 2899 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2900 * changes to happen sooner (see the note there). This code remains 2901 * for backward compatibility as there are some cases, such as 2902 * subclasses that don't fireStateChanged() where it may be used. 2903 * Any changes here need to be kept in synch with 2904 * JTabbedPane.fireStateChanged(). 2905 */ 2906 2907 setRolloverTab(-1); 2908 2909 int tabPlacement = tabPane.getTabPlacement(); 2910 int tabCount = tabPane.getTabCount(); 2911 Insets insets = tabPane.getInsets(); 2912 int selectedIndex = tabPane.getSelectedIndex(); 2913 Component visibleComponent = getVisibleComponent(); 2914 2915 calculateLayoutInfo(); 2916 2917 Component selectedComponent = null; 2918 if (selectedIndex < 0) { 2919 if (visibleComponent != null) { 2920 // The last tab was removed, so remove the component 2921 setVisibleComponent(null); 2922 } 2923 } else { 2924 selectedComponent = tabPane.getComponentAt(selectedIndex); 2925 } 2926 2927 if (tabPane.getTabCount() == 0) { 2928 tabScroller.croppedEdge.resetParams(); 2929 tabScroller.scrollForwardButton.setVisible(false); 2930 tabScroller.scrollBackwardButton.setVisible(false); 2931 return; 2932 } 2933 2934 boolean shouldChangeFocus = false; 2935 2936 // In order to allow programs to use a single component 2937 // as the display for multiple tabs, we will not change 2938 // the visible compnent if the currently selected tab 2939 // has a null component. This is a bit dicey, as we don't 2940 // explicitly state we support this in the spec, but since 2941 // programs are now depending on this, we're making it work. 2942 // 2943 if(selectedComponent != null) { 2944 if(selectedComponent != visibleComponent && 2945 visibleComponent != null) { 2946 if(SwingUtilities.findFocusOwner(visibleComponent) != null) { 2947 shouldChangeFocus = true; 2948 } 2949 } 2950 setVisibleComponent(selectedComponent); 2951 } 2952 int tx, ty, tw, th; // tab area bounds 2953 int cx, cy, cw, ch; // content area bounds 2954 Insets contentInsets = getContentBorderInsets(tabPlacement); 2955 Rectangle bounds = tabPane.getBounds(); 2956 int numChildren = tabPane.getComponentCount(); 2957 2958 if(numChildren > 0) { 2959 switch(tabPlacement) { 2960 case LEFT: 2961 // calculate tab area bounds 2962 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2963 th = bounds.height - insets.top - insets.bottom; 2964 tx = insets.left; 2965 ty = insets.top; 2966 2967 // calculate content area bounds 2968 cx = tx + tw + contentInsets.left; 2969 cy = ty + contentInsets.top; 2970 cw = bounds.width - insets.left - insets.right - tw - 2971 contentInsets.left - contentInsets.right; 2972 ch = bounds.height - insets.top - insets.bottom - 2973 contentInsets.top - contentInsets.bottom; 2974 break; 2975 case RIGHT: 2976 // calculate tab area bounds 2977 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2978 th = bounds.height - insets.top - insets.bottom; 2979 tx = bounds.width - insets.right - tw; 2980 ty = insets.top; 2981 2982 // calculate content area bounds 2983 cx = insets.left + contentInsets.left; 2984 cy = insets.top + contentInsets.top; 2985 cw = bounds.width - insets.left - insets.right - tw - 2986 contentInsets.left - contentInsets.right; 2987 ch = bounds.height - insets.top - insets.bottom - 2988 contentInsets.top - contentInsets.bottom; 2989 break; 2990 case BOTTOM: 2991 // calculate tab area bounds 2992 tw = bounds.width - insets.left - insets.right; 2993 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2994 tx = insets.left; 2995 ty = bounds.height - insets.bottom - th; 2996 2997 // calculate content area bounds 2998 cx = insets.left + contentInsets.left; 2999 cy = insets.top + contentInsets.top; 3000 cw = bounds.width - insets.left - insets.right - 3001 contentInsets.left - contentInsets.right; 3002 ch = bounds.height - insets.top - insets.bottom - th - 3003 contentInsets.top - contentInsets.bottom; 3004 break; 3005 case TOP: 3006 default: 3007 // calculate tab area bounds 3008 tw = bounds.width - insets.left - insets.right; 3009 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 3010 tx = insets.left; 3011 ty = insets.top; 3012 3013 // calculate content area bounds 3014 cx = tx + contentInsets.left; 3015 cy = ty + th + contentInsets.top; 3016 cw = bounds.width - insets.left - insets.right - 3017 contentInsets.left - contentInsets.right; 3018 ch = bounds.height - insets.top - insets.bottom - th - 3019 contentInsets.top - contentInsets.bottom; 3020 } 3021 3022 for(int i = 0; i < numChildren; i++) { 3023 Component child = tabPane.getComponent(i); 3024 3025 if(tabScroller != null && child == tabScroller.viewport) { 3026 JViewport viewport = (JViewport) child; 3027 Rectangle viewRect = viewport.getViewRect(); 3028 int vw = tw; 3029 int vh = th; 3030 Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize(); 3031 switch(tabPlacement) { 3032 case LEFT: 3033 case RIGHT: 3034 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 3035 if(totalTabHeight > th) { 3036 // Allow space for scrollbuttons 3037 vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0; 3038 if(totalTabHeight - viewRect.y <= vh) { 3039 // Scrolled to the end, so ensure the viewport size is 3040 // such that the scroll offset aligns with a tab 3041 vh = totalTabHeight - viewRect.y; 3042 } 3043 } 3044 break; 3045 case BOTTOM: 3046 case TOP: 3047 default: 3048 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 3049 if(totalTabWidth > tw) { 3050 // Need to allow space for scrollbuttons 3051 vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0; 3052 if(totalTabWidth - viewRect.x <= vw) { 3053 // Scrolled to the end, so ensure the viewport size is 3054 // such that the scroll offset aligns with a tab 3055 vw = totalTabWidth - viewRect.x; 3056 } 3057 } 3058 } 3059 child.setBounds(tx, ty, vw, vh); 3060 3061 } else if(tabScroller != null && 3062 (child == tabScroller.scrollForwardButton || 3063 child == tabScroller.scrollBackwardButton)) { 3064 Component scrollbutton = child; 3065 Dimension bsize = scrollbutton.getPreferredSize(); 3066 int bx = 0; 3067 int by = 0; 3068 int bw = bsize.width; 3069 int bh = bsize.height; 3070 boolean visible = false; 3071 3072 switch(tabPlacement) { 3073 case LEFT: 3074 case RIGHT: 3075 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 3076 if(totalTabHeight > th) { 3077 visible = true; 3078 bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx); 3079 by = (child == tabScroller.scrollForwardButton) ? 3080 bounds.height - insets.bottom - bsize.height : 3081 bounds.height - insets.bottom - 2 * bsize.height; 3082 } 3083 break; 3084 3085 case BOTTOM: 3086 case TOP: 3087 default: 3088 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 3089 3090 if(totalTabWidth > tw) { 3091 visible = true; 3092 bx = (child == tabScroller.scrollForwardButton) ? 3093 bounds.width - insets.left - bsize.width : 3094 bounds.width - insets.left - 2 * bsize.width; 3095 by = (tabPlacement == TOP ? ty + th - bsize.height : ty); 3096 } 3097 } 3098 child.setVisible(visible); 3099 if(visible) { 3100 child.setBounds(bx, by, bw, bh); 3101 } 3102 3103 } else { 3104 // All content children... 3105 child.setBounds(cx, cy, cw, ch); 3106 } 3107 } 3108 super.layoutTabComponents(); 3109 layoutCroppedEdge(); 3110 if(shouldChangeFocus) { 3111 if(!requestFocusForVisibleComponent()) { 3112 tabPane.requestFocus(); 3113 } 3114 } 3115 } 3116 } 3117 3118 private void layoutCroppedEdge() { 3119 tabScroller.croppedEdge.resetParams(); 3120 Rectangle viewRect = tabScroller.viewport.getViewRect(); 3121 int cropline; 3122 for (int i = 0; i < rects.length; i++) { 3123 Rectangle tabRect = rects[i]; 3124 switch (tabPane.getTabPlacement()) { 3125 case LEFT: 3126 case RIGHT: 3127 cropline = viewRect.y + viewRect.height; 3128 if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) { 3129 tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1, 3130 -currentTabAreaInsets.left, 0); 3131 } 3132 break; 3133 case TOP: 3134 case BOTTOM: 3135 default: 3136 cropline = viewRect.x + viewRect.width; 3137 if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) { 3138 tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1, 3139 0, -currentTabAreaInsets.top); 3140 } 3141 } 3142 } 3143 } 3144 3145 protected void calculateTabRects(int tabPlacement, int tabCount) { 3146 FontMetrics metrics = getFontMetrics(); 3147 Dimension size = tabPane.getSize(); 3148 Insets insets = tabPane.getInsets(); 3149 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 3150 int fontHeight = metrics.getHeight(); 3151 int selectedIndex = tabPane.getSelectedIndex(); 3152 int i; 3153 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 3154 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 3155 int x = tabAreaInsets.left; 3156 int y = tabAreaInsets.top; 3157 int totalWidth = 0; 3158 int totalHeight = 0; 3159 3160 // 3161 // Calculate bounds within which a tab run must fit 3162 // 3163 switch(tabPlacement) { 3164 case LEFT: 3165 case RIGHT: 3166 maxTabWidth = calculateMaxTabWidth(tabPlacement); 3167 break; 3168 case BOTTOM: 3169 case TOP: 3170 default: 3171 maxTabHeight = calculateMaxTabHeight(tabPlacement); 3172 } 3173 3174 runCount = 0; 3175 selectedRun = -1; 3176 3177 if (tabCount == 0) { 3178 return; 3179 } 3180 3181 selectedRun = 0; 3182 runCount = 1; 3183 3184 // Run through tabs and lay them out in a single run 3185 Rectangle rect; 3186 for (i = 0; i < tabCount; i++) { 3187 rect = rects[i]; 3188 3189 if (!verticalTabRuns) { 3190 // Tabs on TOP or BOTTOM.... 3191 if (i > 0) { 3192 rect.x = rects[i-1].x + rects[i-1].width; 3193 } else { 3194 tabRuns[0] = 0; 3195 maxTabWidth = 0; 3196 totalHeight += maxTabHeight; 3197 rect.x = x; 3198 } 3199 rect.width = calculateTabWidth(tabPlacement, i, metrics); 3200 totalWidth = rect.x + rect.width; 3201 maxTabWidth = Math.max(maxTabWidth, rect.width); 3202 3203 rect.y = y; 3204 rect.height = maxTabHeight/* - 2*/; 3205 3206 } else { 3207 // Tabs on LEFT or RIGHT... 3208 if (i > 0) { 3209 rect.y = rects[i-1].y + rects[i-1].height; 3210 } else { 3211 tabRuns[0] = 0; 3212 maxTabHeight = 0; 3213 totalWidth = maxTabWidth; 3214 rect.y = y; 3215 } 3216 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 3217 totalHeight = rect.y + rect.height; 3218 maxTabHeight = Math.max(maxTabHeight, rect.height); 3219 3220 rect.x = x; 3221 rect.width = maxTabWidth/* - 2*/; 3222 3223 } 3224 } 3225 3226 if (tabsOverlapBorder) { 3227 // Pad the selected tab so that it appears raised in front 3228 padSelectedTab(tabPlacement, selectedIndex); 3229 } 3230 3231 // if right to left and tab placement on the top or 3232 // the bottom, flip x positions and adjust by widths 3233 if (!leftToRight && !verticalTabRuns) { 3234 int rightMargin = size.width 3235 - (insets.right + tabAreaInsets.right); 3236 for (i = 0; i < tabCount; i++) { 3237 rects[i].x = rightMargin - rects[i].x - rects[i].width; 3238 } 3239 } 3240 tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight)); 3241 } 3242 } 3243 3244 private class ScrollableTabSupport implements ActionListener, 3245 ChangeListener { 3246 public ScrollableTabViewport viewport; 3247 public ScrollableTabPanel tabPanel; 3248 public JButton scrollForwardButton; 3249 public JButton scrollBackwardButton; 3250 public CroppedEdge croppedEdge; 3251 public int leadingTabIndex; 3252 3253 private Point tabViewPosition = new Point(0,0); 3254 3255 ScrollableTabSupport(int tabPlacement) { 3256 viewport = new ScrollableTabViewport(); 3257 tabPanel = new ScrollableTabPanel(); 3258 viewport.setView(tabPanel); 3259 viewport.addChangeListener(this); 3260 croppedEdge = new CroppedEdge(); 3261 createButtons(); 3262 } 3263 3264 /** 3265 * Recreates the scroll buttons and adds them to the TabbedPane. 3266 */ 3267 void createButtons() { 3268 if (scrollForwardButton != null) { 3269 tabPane.remove(scrollForwardButton); 3270 scrollForwardButton.removeActionListener(this); 3271 tabPane.remove(scrollBackwardButton); 3272 scrollBackwardButton.removeActionListener(this); 3273 } 3274 int tabPlacement = tabPane.getTabPlacement(); 3275 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3276 scrollForwardButton = createScrollButton(EAST); 3277 scrollBackwardButton = createScrollButton(WEST); 3278 3279 } else { // tabPlacement = LEFT || RIGHT 3280 scrollForwardButton = createScrollButton(SOUTH); 3281 scrollBackwardButton = createScrollButton(NORTH); 3282 } 3283 scrollForwardButton.addActionListener(this); 3284 scrollBackwardButton.addActionListener(this); 3285 tabPane.add(scrollForwardButton); 3286 tabPane.add(scrollBackwardButton); 3287 } 3288 3289 public void scrollForward(int tabPlacement) { 3290 Dimension viewSize = viewport.getViewSize(); 3291 Rectangle viewRect = viewport.getViewRect(); 3292 3293 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3294 if (viewRect.width >= viewSize.width - viewRect.x) { 3295 return; // no room left to scroll 3296 } 3297 } else { // tabPlacement == LEFT || tabPlacement == RIGHT 3298 if (viewRect.height >= viewSize.height - viewRect.y) { 3299 return; 3300 } 3301 } 3302 setLeadingTabIndex(tabPlacement, leadingTabIndex+1); 3303 } 3304 3305 public void scrollBackward(int tabPlacement) { 3306 if (leadingTabIndex == 0) { 3307 return; // no room left to scroll 3308 } 3309 setLeadingTabIndex(tabPlacement, leadingTabIndex-1); 3310 } 3311 3312 public void setLeadingTabIndex(int tabPlacement, int index) { 3313 leadingTabIndex = index; 3314 Dimension viewSize = viewport.getViewSize(); 3315 Rectangle viewRect = viewport.getViewRect(); 3316 3317 switch(tabPlacement) { 3318 case TOP: 3319 case BOTTOM: 3320 tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x; 3321 3322 if ((viewSize.width - tabViewPosition.x) < viewRect.width) { 3323 // We've scrolled to the end, so adjust the viewport size 3324 // to ensure the view position remains aligned on a tab boundary 3325 Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x, 3326 viewRect.height); 3327 viewport.setExtentSize(extentSize); 3328 } 3329 break; 3330 case LEFT: 3331 case RIGHT: 3332 tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y; 3333 3334 if ((viewSize.height - tabViewPosition.y) < viewRect.height) { 3335 // We've scrolled to the end, so adjust the viewport size 3336 // to ensure the view position remains aligned on a tab boundary 3337 Dimension extentSize = new Dimension(viewRect.width, 3338 viewSize.height - tabViewPosition.y); 3339 viewport.setExtentSize(extentSize); 3340 } 3341 } 3342 viewport.setViewPosition(tabViewPosition); 3343 } 3344 3345 public void stateChanged(ChangeEvent e) { 3346 updateView(); 3347 } 3348 3349 private void updateView() { 3350 int tabPlacement = tabPane.getTabPlacement(); 3351 int tabCount = tabPane.getTabCount(); 3352 Rectangle vpRect = viewport.getBounds(); 3353 Dimension viewSize = viewport.getViewSize(); 3354 Rectangle viewRect = viewport.getViewRect(); 3355 3356 leadingTabIndex = getClosestTab(viewRect.x, viewRect.y); 3357 3358 // If the tab isn't right aligned, adjust it. 3359 if (leadingTabIndex + 1 < tabCount) { 3360 switch (tabPlacement) { 3361 case TOP: 3362 case BOTTOM: 3363 if (rects[leadingTabIndex].x < viewRect.x) { 3364 leadingTabIndex++; 3365 } 3366 break; 3367 case LEFT: 3368 case RIGHT: 3369 if (rects[leadingTabIndex].y < viewRect.y) { 3370 leadingTabIndex++; 3371 } 3372 break; 3373 } 3374 } 3375 Insets contentInsets = getContentBorderInsets(tabPlacement); 3376 switch(tabPlacement) { 3377 case LEFT: 3378 tabPane.repaint(vpRect.x+vpRect.width, vpRect.y, 3379 contentInsets.left, vpRect.height); 3380 scrollBackwardButton.setEnabled( 3381 viewRect.y > 0 && leadingTabIndex > 0); 3382 scrollForwardButton.setEnabled( 3383 leadingTabIndex < tabCount-1 && 3384 viewSize.height-viewRect.y > viewRect.height); 3385 break; 3386 case RIGHT: 3387 tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y, 3388 contentInsets.right, vpRect.height); 3389 scrollBackwardButton.setEnabled( 3390 viewRect.y > 0 && leadingTabIndex > 0); 3391 scrollForwardButton.setEnabled( 3392 leadingTabIndex < tabCount-1 && 3393 viewSize.height-viewRect.y > viewRect.height); 3394 break; 3395 case BOTTOM: 3396 tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom, 3397 vpRect.width, contentInsets.bottom); 3398 scrollBackwardButton.setEnabled( 3399 viewRect.x > 0 && leadingTabIndex > 0); 3400 scrollForwardButton.setEnabled( 3401 leadingTabIndex < tabCount-1 && 3402 viewSize.width-viewRect.x > viewRect.width); 3403 break; 3404 case TOP: 3405 default: 3406 tabPane.repaint(vpRect.x, vpRect.y+vpRect.height, 3407 vpRect.width, contentInsets.top); 3408 scrollBackwardButton.setEnabled( 3409 viewRect.x > 0 && leadingTabIndex > 0); 3410 scrollForwardButton.setEnabled( 3411 leadingTabIndex < tabCount-1 && 3412 viewSize.width-viewRect.x > viewRect.width); 3413 } 3414 } 3415 3416 /** 3417 * ActionListener for the scroll buttons. 3418 */ 3419 public void actionPerformed(ActionEvent e) { 3420 ActionMap map = tabPane.getActionMap(); 3421 3422 if (map != null) { 3423 String actionKey; 3424 3425 if (e.getSource() == scrollForwardButton) { 3426 actionKey = "scrollTabsForwardAction"; 3427 } 3428 else { 3429 actionKey = "scrollTabsBackwardAction"; 3430 } 3431 Action action = map.get(actionKey); 3432 3433 if (action != null && action.isEnabled()) { 3434 action.actionPerformed(new ActionEvent(tabPane, 3435 ActionEvent.ACTION_PERFORMED, null, e.getWhen(), 3436 e.getModifiers())); 3437 } 3438 } 3439 } 3440 3441 public String toString() { 3442 return "viewport.viewSize=" + viewport.getViewSize() + "\n" + 3443 "viewport.viewRectangle="+viewport.getViewRect()+"\n"+ 3444 "leadingTabIndex="+leadingTabIndex+"\n"+ 3445 "tabViewPosition=" + tabViewPosition; 3446 } 3447 3448 } 3449 3450 private class ScrollableTabViewport extends JViewport implements UIResource { 3451 public ScrollableTabViewport() { 3452 super(); 3453 setName("TabbedPane.scrollableViewport"); 3454 setScrollMode(SIMPLE_SCROLL_MODE); 3455 setOpaque(tabPane.isOpaque()); 3456 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3457 if (bgColor == null) { 3458 bgColor = tabPane.getBackground(); 3459 } 3460 setBackground(bgColor); 3461 } 3462 } 3463 3464 private class ScrollableTabPanel extends JPanel implements UIResource { 3465 public ScrollableTabPanel() { 3466 super(null); 3467 setOpaque(tabPane.isOpaque()); 3468 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3469 if (bgColor == null) { 3470 bgColor = tabPane.getBackground(); 3471 } 3472 setBackground(bgColor); 3473 } 3474 public void paintComponent(Graphics g) { 3475 super.paintComponent(g); 3476 BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(), 3477 tabPane.getSelectedIndex()); 3478 if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) { 3479 Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()]; 3480 g.translate(croppedRect.x, croppedRect.y); 3481 tabScroller.croppedEdge.paintComponent(g); 3482 g.translate(-croppedRect.x, -croppedRect.y); 3483 } 3484 } 3485 3486 public void doLayout() { 3487 if (getComponentCount() > 0) { 3488 Component child = getComponent(0); 3489 child.setBounds(0, 0, getWidth(), getHeight()); 3490 } 3491 } 3492 } 3493 3494 private class ScrollableTabButton extends BasicArrowButton implements UIResource, 3495 SwingConstants { 3496 public ScrollableTabButton(int direction) { 3497 super(direction, 3498 UIManager.getColor("TabbedPane.selected"), 3499 UIManager.getColor("TabbedPane.shadow"), 3500 UIManager.getColor("TabbedPane.darkShadow"), 3501 UIManager.getColor("TabbedPane.highlight")); 3502 } 3503 } 3504 3505 3506 // Controller: event listeners 3507 3508 private class Handler implements ChangeListener, ContainerListener, 3509 FocusListener, MouseListener, MouseMotionListener, 3510 PropertyChangeListener { 3511 // 3512 // PropertyChangeListener 3513 // 3514 public void propertyChange(PropertyChangeEvent e) { 3515 JTabbedPane pane = (JTabbedPane)e.getSource(); 3516 String name = e.getPropertyName(); 3517 boolean isScrollLayout = scrollableTabLayoutEnabled(); 3518 if (name == "mnemonicAt") { 3519 updateMnemonics(); 3520 pane.repaint(); 3521 } 3522 else if (name == "displayedMnemonicIndexAt") { 3523 pane.repaint(); 3524 } 3525 else if (name =="indexForTitle") { 3526 calculatedBaseline = false; 3527 Integer index = (Integer) e.getNewValue(); 3528 // remove the current index 3529 // to let updateHtmlViews() insert the correct one 3530 if (htmlViews != null) { 3531 htmlViews.removeElementAt(index); 3532 } 3533 updateHtmlViews(index); 3534 } else if (name == "tabLayoutPolicy") { 3535 BasicTabbedPaneUI.this.uninstallUI(pane); 3536 BasicTabbedPaneUI.this.installUI(pane); 3537 calculatedBaseline = false; 3538 } else if (name == "tabPlacement") { 3539 if (scrollableTabLayoutEnabled()) { 3540 tabScroller.createButtons(); 3541 } 3542 calculatedBaseline = false; 3543 } else if (name == "opaque" && isScrollLayout) { 3544 boolean newVal = ((Boolean)e.getNewValue()).booleanValue(); 3545 tabScroller.tabPanel.setOpaque(newVal); 3546 tabScroller.viewport.setOpaque(newVal); 3547 } else if (name == "background" && isScrollLayout) { 3548 Color newVal = (Color)e.getNewValue(); 3549 tabScroller.tabPanel.setBackground(newVal); 3550 tabScroller.viewport.setBackground(newVal); 3551 Color newColor = selectedColor == null ? newVal : selectedColor; 3552 tabScroller.scrollForwardButton.setBackground(newColor); 3553 tabScroller.scrollBackwardButton.setBackground(newColor); 3554 } else if (name == "indexForTabComponent") { 3555 if (tabContainer != null) { 3556 tabContainer.removeUnusedTabComponents(); 3557 } 3558 Component c = tabPane.getTabComponentAt( 3559 (Integer)e.getNewValue()); 3560 if (c != null) { 3561 if (tabContainer == null) { 3562 installTabContainer(); 3563 } else { 3564 tabContainer.add(c); 3565 } 3566 } 3567 tabPane.revalidate(); 3568 tabPane.repaint(); 3569 calculatedBaseline = false; 3570 } else if (name == "indexForNullComponent") { 3571 isRunsDirty = true; 3572 updateHtmlViews((Integer)e.getNewValue()); 3573 } else if (name == "font") { 3574 calculatedBaseline = false; 3575 } 3576 } 3577 3578 private void updateHtmlViews(int index) { 3579 String title = tabPane.getTitleAt(index); 3580 boolean isHTML = BasicHTML.isHTMLString(title); 3581 if (isHTML) { 3582 if (htmlViews==null) { // Initialize vector 3583 htmlViews = createHTMLVector(); 3584 } else { // Vector already exists 3585 View v = BasicHTML.createHTMLView(tabPane, title); 3586 htmlViews.insertElementAt(v, index); 3587 } 3588 } else { // Not HTML 3589 if (htmlViews!=null) { // Add placeholder 3590 htmlViews.insertElementAt(null, index); 3591 } // else nada! 3592 } 3593 updateMnemonics(); 3594 } 3595 3596 // 3597 // ChangeListener 3598 // 3599 public void stateChanged(ChangeEvent e) { 3600 JTabbedPane tabPane = (JTabbedPane)e.getSource(); 3601 tabPane.revalidate(); 3602 tabPane.repaint(); 3603 3604 setFocusIndex(tabPane.getSelectedIndex(), false); 3605 3606 if (scrollableTabLayoutEnabled()) { 3607 int index = tabPane.getSelectedIndex(); 3608 if (index < rects.length && index != -1) { 3609 tabScroller.tabPanel.scrollRectToVisible( 3610 (Rectangle)rects[index].clone()); 3611 } 3612 } 3613 } 3614 3615 // 3616 // MouseListener 3617 // 3618 public void mouseClicked(MouseEvent e) { 3619 } 3620 3621 public void mouseReleased(MouseEvent e) { 3622 } 3623 3624 public void mouseEntered(MouseEvent e) { 3625 setRolloverTab(e.getX(), e.getY()); 3626 } 3627 3628 public void mouseExited(MouseEvent e) { 3629 setRolloverTab(-1); 3630 } 3631 3632 public void mousePressed(MouseEvent e) { 3633 if (!tabPane.isEnabled()) { 3634 return; 3635 } 3636 int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); 3637 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) { 3638 if (tabIndex != tabPane.getSelectedIndex()) { 3639 // Clicking on unselected tab, change selection, do NOT 3640 // request focus. 3641 // This will trigger the focusIndex to change by way 3642 // of stateChanged. 3643 tabPane.setSelectedIndex(tabIndex); 3644 } 3645 else if (tabPane.isRequestFocusEnabled()) { 3646 // Clicking on selected tab, try and give the tabbedpane 3647 // focus. Repaint will occur in focusGained. 3648 tabPane.requestFocus(); 3649 } 3650 } 3651 } 3652 3653 // 3654 // MouseMotionListener 3655 // 3656 public void mouseDragged(MouseEvent e) { 3657 } 3658 3659 public void mouseMoved(MouseEvent e) { 3660 setRolloverTab(e.getX(), e.getY()); 3661 } 3662 3663 // 3664 // FocusListener 3665 // 3666 public void focusGained(FocusEvent e) { 3667 setFocusIndex(tabPane.getSelectedIndex(), true); 3668 } 3669 public void focusLost(FocusEvent e) { 3670 repaintTab(focusIndex); 3671 } 3672 3673 3674 // 3675 // ContainerListener 3676 // 3677 /* GES 2/3/99: 3678 The container listener code was added to support HTML 3679 rendering of tab titles. 3680 3681 Ideally, we would be able to listen for property changes 3682 when a tab is added or its text modified. At the moment 3683 there are no such events because the Beans spec doesn't 3684 allow 'indexed' property changes (i.e. tab 2's text changed 3685 from A to B). 3686 3687 In order to get around this, we listen for tabs to be added 3688 or removed by listening for the container events. we then 3689 queue up a runnable (so the component has a chance to complete 3690 the add) which checks the tab title of the new component to see 3691 if it requires HTML rendering. 3692 3693 The Views (one per tab title requiring HTML rendering) are 3694 stored in the htmlViews Vector, which is only allocated after 3695 the first time we run into an HTML tab. Note that this vector 3696 is kept in step with the number of pages, and nulls are added 3697 for those pages whose tab title do not require HTML rendering. 3698 3699 This makes it easy for the paint and layout code to tell 3700 whether to invoke the HTML engine without having to check 3701 the string during time-sensitive operations. 3702 3703 When we have added a way to listen for tab additions and 3704 changes to tab text, this code should be removed and 3705 replaced by something which uses that. */ 3706 3707 public void componentAdded(ContainerEvent e) { 3708 JTabbedPane tp = (JTabbedPane)e.getContainer(); 3709 Component child = e.getChild(); 3710 if (child instanceof UIResource) { 3711 return; 3712 } 3713 isRunsDirty = true; 3714 updateHtmlViews(tp.indexOfComponent(child)); 3715 } 3716 public void componentRemoved(ContainerEvent e) { 3717 JTabbedPane tp = (JTabbedPane)e.getContainer(); 3718 Component child = e.getChild(); 3719 if (child instanceof UIResource) { 3720 return; 3721 } 3722 3723 // NOTE 4/15/2002 (joutwate): 3724 // This fix is implemented using client properties since there is 3725 // currently no IndexPropertyChangeEvent. Once 3726 // IndexPropertyChangeEvents have been added this code should be 3727 // modified to use it. 3728 Integer indexObj = 3729 (Integer)tp.getClientProperty("__index_to_remove__"); 3730 if (indexObj != null) { 3731 int index = indexObj.intValue(); 3732 if (htmlViews != null && htmlViews.size() > index) { 3733 htmlViews.removeElementAt(index); 3734 } 3735 tp.putClientProperty("__index_to_remove__", null); 3736 } 3737 isRunsDirty = true; 3738 updateMnemonics(); 3739 3740 validateFocusIndex(); 3741 } 3742 } 3743 3744 /** 3745 * This class should be treated as a "protected" inner class. 3746 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3747 */ 3748 public class PropertyChangeHandler implements PropertyChangeListener { 3749 // NOTE: This class exists only for backward compatability. All 3750 // its functionality has been moved into Handler. If you need to add 3751 // new functionality add it to the Handler, but make sure this 3752 // class calls into the Handler. 3753 public void propertyChange(PropertyChangeEvent e) { 3754 getHandler().propertyChange(e); 3755 } 3756 } 3757 3758 /** 3759 * This class should be treated as a "protected" inner class. 3760 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3761 */ 3762 public class TabSelectionHandler implements ChangeListener { 3763 // NOTE: This class exists only for backward compatability. All 3764 // its functionality has been moved into Handler. If you need to add 3765 // new functionality add it to the Handler, but make sure this 3766 // class calls into the Handler. 3767 public void stateChanged(ChangeEvent e) { 3768 getHandler().stateChanged(e); 3769 } 3770 } 3771 3772 /** 3773 * This class should be treated as a "protected" inner class. 3774 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3775 */ 3776 public class MouseHandler extends MouseAdapter { 3777 // NOTE: This class exists only for backward compatability. All 3778 // its functionality has been moved into Handler. If you need to add 3779 // new functionality add it to the Handler, but make sure this 3780 // class calls into the Handler. 3781 public void mousePressed(MouseEvent e) { 3782 getHandler().mousePressed(e); 3783 } 3784 } 3785 3786 /** 3787 * This class should be treated as a "protected" inner class. 3788 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3789 */ 3790 public class FocusHandler extends FocusAdapter { 3791 // NOTE: This class exists only for backward compatability. All 3792 // its functionality has been moved into Handler. If you need to add 3793 // new functionality add it to the Handler, but make sure this 3794 // class calls into the Handler. 3795 public void focusGained(FocusEvent e) { 3796 getHandler().focusGained(e); 3797 } 3798 public void focusLost(FocusEvent e) { 3799 getHandler().focusLost(e); 3800 } 3801 } 3802 3803 private Vector<View> createHTMLVector() { 3804 Vector<View> htmlViews = new Vector<View>(); 3805 int count = tabPane.getTabCount(); 3806 if (count>0) { 3807 for (int i=0 ; i<count; i++) { 3808 String title = tabPane.getTitleAt(i); 3809 if (BasicHTML.isHTMLString(title)) { 3810 htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title)); 3811 } else { 3812 htmlViews.addElement(null); 3813 } 3814 } 3815 } 3816 return htmlViews; 3817 } 3818 3819 private class TabContainer extends JPanel implements UIResource { 3820 private boolean notifyTabbedPane = true; 3821 3822 public TabContainer() { 3823 super(null); 3824 setOpaque(false); 3825 } 3826 3827 public void remove(Component comp) { 3828 int index = tabPane.indexOfTabComponent(comp); 3829 super.remove(comp); 3830 if (notifyTabbedPane && index != -1) { 3831 tabPane.setTabComponentAt(index, null); 3832 } 3833 } 3834 3835 private void removeUnusedTabComponents() { 3836 for (Component c : getComponents()) { 3837 if (!(c instanceof UIResource)) { 3838 int index = tabPane.indexOfTabComponent(c); 3839 if (index == -1) { 3840 super.remove(c); 3841 } 3842 } 3843 } 3844 } 3845 3846 public boolean isOptimizedDrawingEnabled() { 3847 return tabScroller != null && !tabScroller.croppedEdge.isParamsSet(); 3848 } 3849 3850 public void doLayout() { 3851 // We layout tabComponents in JTabbedPane's layout manager 3852 // and use this method as a hook for repainting tabs 3853 // to update tabs area e.g. when the size of tabComponent was changed 3854 if (scrollableTabLayoutEnabled()) { 3855 tabScroller.tabPanel.repaint(); 3856 tabScroller.updateView(); 3857 } else { 3858 tabPane.repaint(getBounds()); 3859 } 3860 } 3861 } 3862 3863 private class CroppedEdge extends JPanel implements UIResource { 3864 private Shape shape; 3865 private int tabIndex; 3866 private int cropline; 3867 private int cropx, cropy; 3868 3869 public CroppedEdge() { 3870 setOpaque(false); 3871 } 3872 3873 public void setParams(int tabIndex, int cropline, int cropx, int cropy) { 3874 this.tabIndex = tabIndex; 3875 this.cropline = cropline; 3876 this.cropx = cropx; 3877 this.cropy = cropy; 3878 Rectangle tabRect = rects[tabIndex]; 3879 setBounds(tabRect); 3880 shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline); 3881 if (getParent() == null && tabContainer != null) { 3882 tabContainer.add(this, 0); 3883 } 3884 } 3885 3886 public void resetParams() { 3887 shape = null; 3888 if (getParent() == tabContainer && tabContainer != null) { 3889 tabContainer.remove(this); 3890 } 3891 } 3892 3893 public boolean isParamsSet() { 3894 return shape != null; 3895 } 3896 3897 public int getTabIndex() { 3898 return tabIndex; 3899 } 3900 3901 public int getCropline() { 3902 return cropline; 3903 } 3904 3905 public int getCroppedSideWidth() { 3906 return 3; 3907 } 3908 3909 private Color getBgColor() { 3910 Component parent = tabPane.getParent(); 3911 if (parent != null) { 3912 Color bg = parent.getBackground(); 3913 if (bg != null) { 3914 return bg; 3915 } 3916 } 3917 return UIManager.getColor("control"); 3918 } 3919 3920 protected void paintComponent(Graphics g) { 3921 super.paintComponent(g); 3922 if (isParamsSet() && g instanceof Graphics2D) { 3923 Graphics2D g2 = (Graphics2D) g; 3924 g2.clipRect(0, 0, getWidth(), getHeight()); 3925 g2.setColor(getBgColor()); 3926 g2.translate(cropx, cropy); 3927 g2.fill(shape); 3928 paintCroppedTabEdge(g); 3929 g2.translate(-cropx, -cropy); 3930 } 3931 } 3932 } 3933 }