1 /* 2 * Copyright (c) 1997, 2013, 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, BasicLookAndFeel.getFocusAcceleratorKeyMask()), 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 } else if (!scrollableTabLayoutEnabled() && isHorizontalTabPlacement()) { 879 clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, textRect.width); 880 } 881 882 paintText(g, tabPlacement, font, metrics, 883 tabIndex, clippedTitle, textRect, isSelected); 884 885 paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); 886 } 887 paintFocusIndicator(g, tabPlacement, rects, tabIndex, 888 iconRect, textRect, isSelected); 889 } 890 891 private boolean isHorizontalTabPlacement() { 892 return tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM; 893 } 894 895 /* This method will create and return a polygon shape for the given tab rectangle 896 * which has been cropped at the specified cropline with a torn edge visual. 897 * e.g. A "File" tab which has cropped been cropped just after the "i": 898 * ------------- 899 * | ..... | 900 * | . | 901 * | ... . | 902 * | . . | 903 * | . . | 904 * | . . | 905 * -------------- 906 * 907 * The x, y arrays below define the pattern used to create a "torn" edge 908 * segment which is repeated to fill the edge of the tab. 909 * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by 910 * line segments which are defined by coordinates obtained by 911 * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i] 912 * to (tab.y). 913 * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by 914 * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i] 915 * to (tab.x). 916 */ 917 private static int xCropLen[] = {1,1,0,0,1,1,2,2}; 918 private static int yCropLen[] = {0,3,3,6,6,9,9,12}; 919 private static final int CROP_SEGMENT = 12; 920 921 private static Polygon createCroppedTabShape(int tabPlacement, Rectangle tabRect, int cropline) { 922 int rlen; 923 int start; 924 int end; 925 int ostart; 926 927 switch(tabPlacement) { 928 case LEFT: 929 case RIGHT: 930 rlen = tabRect.width; 931 start = tabRect.x; 932 end = tabRect.x + tabRect.width; 933 ostart = tabRect.y + tabRect.height; 934 break; 935 case TOP: 936 case BOTTOM: 937 default: 938 rlen = tabRect.height; 939 start = tabRect.y; 940 end = tabRect.y + tabRect.height; 941 ostart = tabRect.x + tabRect.width; 942 } 943 int rcnt = rlen/CROP_SEGMENT; 944 if (rlen%CROP_SEGMENT > 0) { 945 rcnt++; 946 } 947 int npts = 2 + (rcnt*8); 948 int xp[] = new int[npts]; 949 int yp[] = new int[npts]; 950 int pcnt = 0; 951 952 xp[pcnt] = ostart; 953 yp[pcnt++] = end; 954 xp[pcnt] = ostart; 955 yp[pcnt++] = start; 956 for(int i = 0; i < rcnt; i++) { 957 for(int j = 0; j < xCropLen.length; j++) { 958 xp[pcnt] = cropline - xCropLen[j]; 959 yp[pcnt] = start + (i*CROP_SEGMENT) + yCropLen[j]; 960 if (yp[pcnt] >= end) { 961 yp[pcnt] = end; 962 pcnt++; 963 break; 964 } 965 pcnt++; 966 } 967 } 968 if (tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM) { 969 return new Polygon(xp, yp, pcnt); 970 971 } else { // LEFT or RIGHT 972 return new Polygon(yp, xp, pcnt); 973 } 974 } 975 976 /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge 977 * indicating the tab is cropped in the viewport display 978 */ 979 private void paintCroppedTabEdge(Graphics g) { 980 int tabIndex = tabScroller.croppedEdge.getTabIndex(); 981 int cropline = tabScroller.croppedEdge.getCropline(); 982 int x,y; 983 switch(tabPane.getTabPlacement()) { 984 case LEFT: 985 case RIGHT: 986 x = rects[tabIndex].x; 987 y = cropline; 988 int xx = x; 989 g.setColor(shadow); 990 while(xx <= x+rects[tabIndex].width) { 991 for (int i=0; i < xCropLen.length; i+=2) { 992 g.drawLine(xx+yCropLen[i],y-xCropLen[i], 993 xx+yCropLen[i+1]-1,y-xCropLen[i+1]); 994 } 995 xx+=CROP_SEGMENT; 996 } 997 break; 998 case TOP: 999 case BOTTOM: 1000 default: 1001 x = cropline; 1002 y = rects[tabIndex].y; 1003 int yy = y; 1004 g.setColor(shadow); 1005 while(yy <= y+rects[tabIndex].height) { 1006 for (int i=0; i < xCropLen.length; i+=2) { 1007 g.drawLine(x-xCropLen[i],yy+yCropLen[i], 1008 x-xCropLen[i+1],yy+yCropLen[i+1]-1); 1009 } 1010 yy+=CROP_SEGMENT; 1011 } 1012 } 1013 } 1014 1015 protected void layoutLabel(int tabPlacement, 1016 FontMetrics metrics, int tabIndex, 1017 String title, Icon icon, 1018 Rectangle tabRect, Rectangle iconRect, 1019 Rectangle textRect, boolean isSelected ) { 1020 textRect.x = textRect.y = iconRect.x = iconRect.y = 0; 1021 1022 View v = getTextViewForTab(tabIndex); 1023 if (v != null) { 1024 tabPane.putClientProperty("html", v); 1025 } 1026 1027 SwingUtilities.layoutCompoundLabel(tabPane, 1028 metrics, title, icon, 1029 SwingUtilities.CENTER, 1030 SwingUtilities.CENTER, 1031 SwingUtilities.CENTER, 1032 SwingUtilities.TRAILING, 1033 tabRect, 1034 iconRect, 1035 textRect, 1036 textIconGap); 1037 1038 tabPane.putClientProperty("html", null); 1039 1040 int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected); 1041 int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected); 1042 iconRect.x += xNudge; 1043 iconRect.y += yNudge; 1044 textRect.x += xNudge; 1045 textRect.y += yNudge; 1046 } 1047 1048 protected void paintIcon(Graphics g, int tabPlacement, 1049 int tabIndex, Icon icon, Rectangle iconRect, 1050 boolean isSelected ) { 1051 if (icon != null) { 1052 icon.paintIcon(tabPane, g, iconRect.x, iconRect.y); 1053 } 1054 } 1055 1056 protected void paintText(Graphics g, int tabPlacement, 1057 Font font, FontMetrics metrics, int tabIndex, 1058 String title, Rectangle textRect, 1059 boolean isSelected) { 1060 1061 g.setFont(font); 1062 1063 View v = getTextViewForTab(tabIndex); 1064 if (v != null) { 1065 // html 1066 v.paint(g, textRect); 1067 } else { 1068 // plain text 1069 int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex); 1070 1071 if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) { 1072 Color fg = tabPane.getForegroundAt(tabIndex); 1073 if (isSelected && (fg instanceof UIResource)) { 1074 Color selectedFG = UIManager.getColor( 1075 "TabbedPane.selectedForeground"); 1076 if (selectedFG != null) { 1077 fg = selectedFG; 1078 } 1079 } 1080 g.setColor(fg); 1081 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, 1082 title, mnemIndex, 1083 textRect.x, textRect.y + metrics.getAscent()); 1084 1085 } else { // tab disabled 1086 g.setColor(tabPane.getBackgroundAt(tabIndex).brighter()); 1087 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, 1088 title, mnemIndex, 1089 textRect.x, textRect.y + metrics.getAscent()); 1090 g.setColor(tabPane.getBackgroundAt(tabIndex).darker()); 1091 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, 1092 title, mnemIndex, 1093 textRect.x - 1, textRect.y + metrics.getAscent() - 1); 1094 1095 } 1096 } 1097 } 1098 1099 1100 protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) { 1101 Rectangle tabRect = rects[tabIndex]; 1102 String propKey = (isSelected ? "selectedLabelShift" : "labelShift"); 1103 int nudge = DefaultLookup.getInt( 1104 tabPane, this, "TabbedPane." + propKey, 1); 1105 1106 switch (tabPlacement) { 1107 case LEFT: 1108 return nudge; 1109 case RIGHT: 1110 return -nudge; 1111 case BOTTOM: 1112 case TOP: 1113 default: 1114 return tabRect.width % 2; 1115 } 1116 } 1117 1118 protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) { 1119 Rectangle tabRect = rects[tabIndex]; 1120 int nudge = (isSelected ? DefaultLookup.getInt(tabPane, this, "TabbedPane.selectedLabelShift", -1) : 1121 DefaultLookup.getInt(tabPane, this, "TabbedPane.labelShift", 1)); 1122 1123 switch (tabPlacement) { 1124 case BOTTOM: 1125 return -nudge; 1126 case LEFT: 1127 case RIGHT: 1128 return tabRect.height % 2; 1129 case TOP: 1130 default: 1131 return nudge; 1132 } 1133 } 1134 1135 protected void paintFocusIndicator(Graphics g, int tabPlacement, 1136 Rectangle[] rects, int tabIndex, 1137 Rectangle iconRect, Rectangle textRect, 1138 boolean isSelected) { 1139 Rectangle tabRect = rects[tabIndex]; 1140 if (tabPane.hasFocus() && isSelected) { 1141 int x, y, w, h; 1142 g.setColor(focus); 1143 switch(tabPlacement) { 1144 case LEFT: 1145 x = tabRect.x + 3; 1146 y = tabRect.y + 3; 1147 w = tabRect.width - 5; 1148 h = tabRect.height - 6; 1149 break; 1150 case RIGHT: 1151 x = tabRect.x + 2; 1152 y = tabRect.y + 3; 1153 w = tabRect.width - 5; 1154 h = tabRect.height - 6; 1155 break; 1156 case BOTTOM: 1157 x = tabRect.x + 3; 1158 y = tabRect.y + 2; 1159 w = tabRect.width - 6; 1160 h = tabRect.height - 5; 1161 break; 1162 case TOP: 1163 default: 1164 x = tabRect.x + 3; 1165 y = tabRect.y + 3; 1166 w = tabRect.width - 6; 1167 h = tabRect.height - 5; 1168 } 1169 BasicGraphicsUtils.drawDashedRect(g, x, y, w, h); 1170 } 1171 } 1172 1173 /** 1174 * this function draws the border around each tab 1175 * note that this function does now draw the background of the tab. 1176 * that is done elsewhere 1177 */ 1178 protected void paintTabBorder(Graphics g, int tabPlacement, 1179 int tabIndex, 1180 int x, int y, int w, int h, 1181 boolean isSelected ) { 1182 g.setColor(lightHighlight); 1183 1184 switch (tabPlacement) { 1185 case LEFT: 1186 g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight 1187 g.drawLine(x, y+2, x, y+h-3); // left highlight 1188 g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight 1189 g.drawLine(x+2, y, x+w-1, y); // top highlight 1190 1191 g.setColor(shadow); 1192 g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow 1193 1194 g.setColor(darkShadow); 1195 g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow 1196 break; 1197 case RIGHT: 1198 g.drawLine(x, y, x+w-3, y); // top highlight 1199 1200 g.setColor(shadow); 1201 g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow 1202 g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow 1203 1204 g.setColor(darkShadow); 1205 g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow 1206 g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow 1207 g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow 1208 g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow 1209 break; 1210 case BOTTOM: 1211 g.drawLine(x, y, x, y+h-3); // left highlight 1212 g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight 1213 1214 g.setColor(shadow); 1215 g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow 1216 g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow 1217 1218 g.setColor(darkShadow); 1219 g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow 1220 g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow 1221 g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow 1222 break; 1223 case TOP: 1224 default: 1225 g.drawLine(x, y+2, x, y+h-1); // left highlight 1226 g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight 1227 g.drawLine(x+2, y, x+w-3, y); // top highlight 1228 1229 g.setColor(shadow); 1230 g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow 1231 1232 g.setColor(darkShadow); 1233 g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow 1234 g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow 1235 } 1236 } 1237 1238 protected void paintTabBackground(Graphics g, int tabPlacement, 1239 int tabIndex, 1240 int x, int y, int w, int h, 1241 boolean isSelected ) { 1242 g.setColor(!isSelected || selectedColor == null? 1243 tabPane.getBackgroundAt(tabIndex) : selectedColor); 1244 switch(tabPlacement) { 1245 case LEFT: 1246 g.fillRect(x+1, y+1, w-1, h-3); 1247 break; 1248 case RIGHT: 1249 g.fillRect(x, y+1, w-2, h-3); 1250 break; 1251 case BOTTOM: 1252 g.fillRect(x+1, y, w-3, h-1); 1253 break; 1254 case TOP: 1255 default: 1256 g.fillRect(x+1, y+1, w-3, h-1); 1257 } 1258 } 1259 1260 protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) { 1261 int width = tabPane.getWidth(); 1262 int height = tabPane.getHeight(); 1263 Insets insets = tabPane.getInsets(); 1264 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 1265 1266 int x = insets.left; 1267 int y = insets.top; 1268 int w = width - insets.right - insets.left; 1269 int h = height - insets.top - insets.bottom; 1270 1271 switch(tabPlacement) { 1272 case LEFT: 1273 x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 1274 if (tabsOverlapBorder) { 1275 x -= tabAreaInsets.right; 1276 } 1277 w -= (x - insets.left); 1278 break; 1279 case RIGHT: 1280 w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 1281 if (tabsOverlapBorder) { 1282 w += tabAreaInsets.left; 1283 } 1284 break; 1285 case BOTTOM: 1286 h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 1287 if (tabsOverlapBorder) { 1288 h += tabAreaInsets.top; 1289 } 1290 break; 1291 case TOP: 1292 default: 1293 y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 1294 if (tabsOverlapBorder) { 1295 y -= tabAreaInsets.bottom; 1296 } 1297 h -= (y - insets.top); 1298 } 1299 1300 if ( tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque()) ) { 1301 // Fill region behind content area 1302 Color color = UIManager.getColor("TabbedPane.contentAreaColor"); 1303 if (color != null) { 1304 g.setColor(color); 1305 } 1306 else if ( selectedColor == null || selectedIndex == -1 ) { 1307 g.setColor(tabPane.getBackground()); 1308 } 1309 else { 1310 g.setColor(selectedColor); 1311 } 1312 g.fillRect(x,y,w,h); 1313 } 1314 1315 paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1316 paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1317 paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1318 paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h); 1319 1320 } 1321 1322 protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, 1323 int selectedIndex, 1324 int x, int y, int w, int h) { 1325 Rectangle selRect = selectedIndex < 0? null : 1326 getTabBounds(selectedIndex, calcRect); 1327 1328 g.setColor(lightHighlight); 1329 1330 // Draw unbroken line if tabs are not on TOP, OR 1331 // selected tab is not in run adjacent to content, OR 1332 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1333 // 1334 if (tabPlacement != TOP || selectedIndex < 0 || 1335 (selRect.y + selRect.height + 1 < y) || 1336 (selRect.x < x || selRect.x > x + w)) { 1337 g.drawLine(x, y, x+w-2, y); 1338 } else { 1339 // Break line to show visual connection to selected tab 1340 g.drawLine(x, y, selRect.x - 1, y); 1341 if (selRect.x + selRect.width < x + w - 2) { 1342 g.drawLine(selRect.x + selRect.width, y, 1343 x+w-2, y); 1344 } else { 1345 g.setColor(shadow); 1346 g.drawLine(x+w-2, y, x+w-2, y); 1347 } 1348 } 1349 } 1350 1351 protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, 1352 int selectedIndex, 1353 int x, int y, int w, int h) { 1354 Rectangle selRect = selectedIndex < 0? null : 1355 getTabBounds(selectedIndex, calcRect); 1356 1357 g.setColor(lightHighlight); 1358 1359 // Draw unbroken line if tabs are not on LEFT, OR 1360 // selected tab is not in run adjacent to content, OR 1361 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1362 // 1363 if (tabPlacement != LEFT || selectedIndex < 0 || 1364 (selRect.x + selRect.width + 1 < x) || 1365 (selRect.y < y || selRect.y > y + h)) { 1366 g.drawLine(x, y, x, y+h-2); 1367 } else { 1368 // Break line to show visual connection to selected tab 1369 g.drawLine(x, y, x, selRect.y - 1); 1370 if (selRect.y + selRect.height < y + h - 2) { 1371 g.drawLine(x, selRect.y + selRect.height, 1372 x, y+h-2); 1373 } 1374 } 1375 } 1376 1377 protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, 1378 int selectedIndex, 1379 int x, int y, int w, int h) { 1380 Rectangle selRect = selectedIndex < 0? null : 1381 getTabBounds(selectedIndex, calcRect); 1382 1383 g.setColor(shadow); 1384 1385 // Draw unbroken line if tabs are not on BOTTOM, OR 1386 // selected tab is not in run adjacent to content, OR 1387 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1388 // 1389 if (tabPlacement != BOTTOM || selectedIndex < 0 || 1390 (selRect.y - 1 > h) || 1391 (selRect.x < x || selRect.x > x + w)) { 1392 g.drawLine(x+1, y+h-2, x+w-2, y+h-2); 1393 g.setColor(darkShadow); 1394 g.drawLine(x, y+h-1, x+w-1, y+h-1); 1395 } else { 1396 // Break line to show visual connection to selected tab 1397 g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2); 1398 g.setColor(darkShadow); 1399 g.drawLine(x, y+h-1, selRect.x - 1, y+h-1); 1400 if (selRect.x + selRect.width < x + w - 2) { 1401 g.setColor(shadow); 1402 g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2); 1403 g.setColor(darkShadow); 1404 g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1); 1405 } 1406 } 1407 1408 } 1409 1410 protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, 1411 int selectedIndex, 1412 int x, int y, int w, int h) { 1413 Rectangle selRect = selectedIndex < 0? null : 1414 getTabBounds(selectedIndex, calcRect); 1415 1416 g.setColor(shadow); 1417 1418 // Draw unbroken line if tabs are not on RIGHT, OR 1419 // selected tab is not in run adjacent to content, OR 1420 // selected tab is not visible (SCROLL_TAB_LAYOUT) 1421 // 1422 if (tabPlacement != RIGHT || selectedIndex < 0 || 1423 (selRect.x - 1 > w) || 1424 (selRect.y < y || selRect.y > y + h)) { 1425 g.drawLine(x+w-2, y+1, x+w-2, y+h-3); 1426 g.setColor(darkShadow); 1427 g.drawLine(x+w-1, y, x+w-1, y+h-1); 1428 } else { 1429 // Break line to show visual connection to selected tab 1430 g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1); 1431 g.setColor(darkShadow); 1432 g.drawLine(x+w-1, y, x+w-1, selRect.y - 1); 1433 1434 if (selRect.y + selRect.height < y + h - 2) { 1435 g.setColor(shadow); 1436 g.drawLine(x+w-2, selRect.y + selRect.height, 1437 x+w-2, y+h-2); 1438 g.setColor(darkShadow); 1439 g.drawLine(x+w-1, selRect.y + selRect.height, 1440 x+w-1, y+h-2); 1441 } 1442 } 1443 } 1444 1445 private void ensureCurrentLayout() { 1446 if (!tabPane.isValid()) { 1447 tabPane.validate(); 1448 } 1449 /* If tabPane doesn't have a peer yet, the validate() call will 1450 * silently fail. We handle that by forcing a layout if tabPane 1451 * is still invalid. See bug 4237677. 1452 */ 1453 if (!tabPane.isValid()) { 1454 TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout(); 1455 layout.calculateLayoutInfo(); 1456 } 1457 } 1458 1459 1460 // TabbedPaneUI methods 1461 1462 /** 1463 * Returns the bounds of the specified tab index. The bounds are 1464 * with respect to the JTabbedPane's coordinate space. 1465 */ 1466 public Rectangle getTabBounds(JTabbedPane pane, int i) { 1467 ensureCurrentLayout(); 1468 Rectangle tabRect = new Rectangle(); 1469 return getTabBounds(i, tabRect); 1470 } 1471 1472 public int getTabRunCount(JTabbedPane pane) { 1473 ensureCurrentLayout(); 1474 return runCount; 1475 } 1476 1477 /** 1478 * Returns the tab index which intersects the specified point 1479 * in the JTabbedPane's coordinate space. 1480 */ 1481 public int tabForCoordinate(JTabbedPane pane, int x, int y) { 1482 return tabForCoordinate(pane, x, y, true); 1483 } 1484 1485 private int tabForCoordinate(JTabbedPane pane, int x, int y, 1486 boolean validateIfNecessary) { 1487 if (validateIfNecessary) { 1488 ensureCurrentLayout(); 1489 } 1490 if (isRunsDirty) { 1491 // We didn't recalculate the layout, runs and tabCount may not 1492 // line up, bail. 1493 return -1; 1494 } 1495 Point p = new Point(x, y); 1496 1497 if (scrollableTabLayoutEnabled()) { 1498 translatePointToTabPanel(x, y, p); 1499 Rectangle viewRect = tabScroller.viewport.getViewRect(); 1500 if (!viewRect.contains(p)) { 1501 return -1; 1502 } 1503 } 1504 int tabCount = tabPane.getTabCount(); 1505 for (int i = 0; i < tabCount; i++) { 1506 if (rects[i].contains(p.x, p.y)) { 1507 return i; 1508 } 1509 } 1510 return -1; 1511 } 1512 1513 /** 1514 * Returns the bounds of the specified tab in the coordinate space 1515 * of the JTabbedPane component. This is required because the tab rects 1516 * are by default defined in the coordinate space of the component where 1517 * they are rendered, which could be the JTabbedPane 1518 * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT). 1519 * This method should be used whenever the tab rectangle must be relative 1520 * to the JTabbedPane itself and the result should be placed in a 1521 * designated Rectangle object (rather than instantiating and returning 1522 * a new Rectangle each time). The tab index parameter must be a valid 1523 * tabbed pane tab index (0 to tab count - 1, inclusive). The destination 1524 * rectangle parameter must be a valid <code>Rectangle</code> instance. 1525 * The handling of invalid parameters is unspecified. 1526 * 1527 * @param tabIndex the index of the tab 1528 * @param dest the rectangle where the result should be placed 1529 * @return the resulting rectangle 1530 * 1531 * @since 1.4 1532 */ 1533 protected Rectangle getTabBounds(int tabIndex, Rectangle dest) { 1534 dest.width = rects[tabIndex].width; 1535 dest.height = rects[tabIndex].height; 1536 1537 if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT 1538 // Need to translate coordinates based on viewport location & 1539 // view position 1540 Point vpp = tabScroller.viewport.getLocation(); 1541 Point viewp = tabScroller.viewport.getViewPosition(); 1542 dest.x = rects[tabIndex].x + vpp.x - viewp.x; 1543 dest.y = rects[tabIndex].y + vpp.y - viewp.y; 1544 1545 } else { // WRAP_TAB_LAYOUT 1546 dest.x = rects[tabIndex].x; 1547 dest.y = rects[tabIndex].y; 1548 } 1549 return dest; 1550 } 1551 1552 /** 1553 * Returns the index of the tab closest to the passed in location, note 1554 * that the returned tab may not contain the location x,y. 1555 */ 1556 private int getClosestTab(int x, int y) { 1557 int min = 0; 1558 int tabCount = Math.min(rects.length, tabPane.getTabCount()); 1559 int max = tabCount; 1560 int tabPlacement = tabPane.getTabPlacement(); 1561 boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM); 1562 int want = (useX) ? x : y; 1563 1564 while (min != max) { 1565 int current = (max + min) / 2; 1566 int minLoc; 1567 int maxLoc; 1568 1569 if (useX) { 1570 minLoc = rects[current].x; 1571 maxLoc = minLoc + rects[current].width; 1572 } 1573 else { 1574 minLoc = rects[current].y; 1575 maxLoc = minLoc + rects[current].height; 1576 } 1577 if (want < minLoc) { 1578 max = current; 1579 if (min == max) { 1580 return Math.max(0, current - 1); 1581 } 1582 } 1583 else if (want >= maxLoc) { 1584 min = current; 1585 if (max - min <= 1) { 1586 return Math.max(current + 1, tabCount - 1); 1587 } 1588 } 1589 else { 1590 return current; 1591 } 1592 } 1593 return min; 1594 } 1595 1596 /** 1597 * Returns a point which is translated from the specified point in the 1598 * JTabbedPane's coordinate space to the coordinate space of the 1599 * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY. 1600 */ 1601 private Point translatePointToTabPanel(int srcx, int srcy, Point dest) { 1602 Point vpp = tabScroller.viewport.getLocation(); 1603 Point viewp = tabScroller.viewport.getViewPosition(); 1604 dest.x = srcx - vpp.x + viewp.x; 1605 dest.y = srcy - vpp.y + viewp.y; 1606 return dest; 1607 } 1608 1609 // BasicTabbedPaneUI methods 1610 1611 protected Component getVisibleComponent() { 1612 return visibleComponent; 1613 } 1614 1615 protected void setVisibleComponent(Component component) { 1616 if (visibleComponent != null 1617 && visibleComponent != component 1618 && visibleComponent.getParent() == tabPane 1619 && visibleComponent.isVisible()) { 1620 1621 visibleComponent.setVisible(false); 1622 } 1623 if (component != null && !component.isVisible()) { 1624 component.setVisible(true); 1625 } 1626 visibleComponent = component; 1627 } 1628 1629 protected void assureRectsCreated(int tabCount) { 1630 int rectArrayLen = rects.length; 1631 if (tabCount != rectArrayLen ) { 1632 Rectangle[] tempRectArray = new Rectangle[tabCount]; 1633 System.arraycopy(rects, 0, tempRectArray, 0, 1634 Math.min(rectArrayLen, tabCount)); 1635 rects = tempRectArray; 1636 for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) { 1637 rects[rectIndex] = new Rectangle(); 1638 } 1639 } 1640 1641 } 1642 1643 protected void expandTabRunsArray() { 1644 int rectLen = tabRuns.length; 1645 int[] newArray = new int[rectLen+10]; 1646 System.arraycopy(tabRuns, 0, newArray, 0, runCount); 1647 tabRuns = newArray; 1648 } 1649 1650 protected int getRunForTab(int tabCount, int tabIndex) { 1651 for (int i = 0; i < runCount; i++) { 1652 int first = tabRuns[i]; 1653 int last = lastTabInRun(tabCount, i); 1654 if (tabIndex >= first && tabIndex <= last) { 1655 return i; 1656 } 1657 } 1658 return 0; 1659 } 1660 1661 protected int lastTabInRun(int tabCount, int run) { 1662 if (runCount == 1) { 1663 return tabCount - 1; 1664 } 1665 int nextRun = (run == runCount - 1? 0 : run + 1); 1666 if (tabRuns[nextRun] == 0) { 1667 return tabCount - 1; 1668 } 1669 return tabRuns[nextRun]-1; 1670 } 1671 1672 protected int getTabRunOverlay(int tabPlacement) { 1673 return tabRunOverlay; 1674 } 1675 1676 protected int getTabRunIndent(int tabPlacement, int run) { 1677 return 0; 1678 } 1679 1680 protected boolean shouldPadTabRun(int tabPlacement, int run) { 1681 return runCount > 1; 1682 } 1683 1684 protected boolean shouldRotateTabRuns(int tabPlacement) { 1685 return true; 1686 } 1687 1688 protected Icon getIconForTab(int tabIndex) { 1689 return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))? 1690 tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex); 1691 } 1692 1693 /** 1694 * Returns the text View object required to render stylized text (HTML) for 1695 * the specified tab or null if no specialized text rendering is needed 1696 * for this tab. This is provided to support html rendering inside tabs. 1697 * 1698 * @param tabIndex the index of the tab 1699 * @return the text view to render the tab's text or null if no 1700 * specialized rendering is required 1701 * 1702 * @since 1.4 1703 */ 1704 protected View getTextViewForTab(int tabIndex) { 1705 if (htmlViews != null) { 1706 return htmlViews.elementAt(tabIndex); 1707 } 1708 return null; 1709 } 1710 1711 protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) { 1712 int height = 0; 1713 Component c = tabPane.getTabComponentAt(tabIndex); 1714 if (c != null) { 1715 height = c.getPreferredSize().height; 1716 } else { 1717 View v = getTextViewForTab(tabIndex); 1718 if (v != null) { 1719 // html 1720 height += (int) v.getPreferredSpan(View.Y_AXIS); 1721 } else { 1722 // plain text 1723 height += fontHeight; 1724 } 1725 Icon icon = getIconForTab(tabIndex); 1726 1727 if (icon != null) { 1728 height = Math.max(height, icon.getIconHeight()); 1729 } 1730 } 1731 Insets tabInsets = getTabInsets(tabPlacement, tabIndex); 1732 height += tabInsets.top + tabInsets.bottom + 2; 1733 return height; 1734 } 1735 1736 protected int calculateMaxTabHeight(int tabPlacement) { 1737 FontMetrics metrics = getFontMetrics(); 1738 int tabCount = tabPane.getTabCount(); 1739 int result = 0; 1740 int fontHeight = metrics.getHeight(); 1741 for(int i = 0; i < tabCount; i++) { 1742 result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result); 1743 } 1744 return result; 1745 } 1746 1747 protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) { 1748 Insets tabInsets = getTabInsets(tabPlacement, tabIndex); 1749 int width = tabInsets.left + tabInsets.right + 3; 1750 Component tabComponent = tabPane.getTabComponentAt(tabIndex); 1751 if (tabComponent != null) { 1752 width += tabComponent.getPreferredSize().width; 1753 } else { 1754 Icon icon = getIconForTab(tabIndex); 1755 if (icon != null) { 1756 width += icon.getIconWidth() + textIconGap; 1757 } 1758 View v = getTextViewForTab(tabIndex); 1759 if (v != null) { 1760 // html 1761 width += (int) v.getPreferredSpan(View.X_AXIS); 1762 } else { 1763 // plain text 1764 String title = tabPane.getTitleAt(tabIndex); 1765 width += SwingUtilities2.stringWidth(tabPane, metrics, title); 1766 } 1767 } 1768 return width; 1769 } 1770 1771 protected int calculateMaxTabWidth(int tabPlacement) { 1772 FontMetrics metrics = getFontMetrics(); 1773 int tabCount = tabPane.getTabCount(); 1774 int result = 0; 1775 for(int i = 0; i < tabCount; i++) { 1776 result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result); 1777 } 1778 return result; 1779 } 1780 1781 protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) { 1782 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 1783 int tabRunOverlay = getTabRunOverlay(tabPlacement); 1784 return (horizRunCount > 0? 1785 horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay + 1786 tabAreaInsets.top + tabAreaInsets.bottom : 1787 0); 1788 } 1789 1790 protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) { 1791 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 1792 int tabRunOverlay = getTabRunOverlay(tabPlacement); 1793 return (vertRunCount > 0? 1794 vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay + 1795 tabAreaInsets.left + tabAreaInsets.right : 1796 0); 1797 } 1798 1799 protected Insets getTabInsets(int tabPlacement, int tabIndex) { 1800 return tabInsets; 1801 } 1802 1803 protected Insets getSelectedTabPadInsets(int tabPlacement) { 1804 rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement); 1805 return currentPadInsets; 1806 } 1807 1808 protected Insets getTabAreaInsets(int tabPlacement) { 1809 rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement); 1810 return currentTabAreaInsets; 1811 } 1812 1813 protected Insets getContentBorderInsets(int tabPlacement) { 1814 return contentBorderInsets; 1815 } 1816 1817 protected FontMetrics getFontMetrics() { 1818 Font font = tabPane.getFont(); 1819 return tabPane.getFontMetrics(font); 1820 } 1821 1822 1823 // Tab Navigation methods 1824 1825 protected void navigateSelectedTab(int direction) { 1826 int tabPlacement = tabPane.getTabPlacement(); 1827 int current = DefaultLookup.getBoolean(tabPane, this, 1828 "TabbedPane.selectionFollowsFocus", true) ? 1829 tabPane.getSelectedIndex() : getFocusIndex(); 1830 int tabCount = tabPane.getTabCount(); 1831 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 1832 1833 // If we have no tabs then don't navigate. 1834 if (tabCount <= 0) { 1835 return; 1836 } 1837 1838 int offset; 1839 switch(tabPlacement) { 1840 case LEFT: 1841 case RIGHT: 1842 switch(direction) { 1843 case NEXT: 1844 selectNextTab(current); 1845 break; 1846 case PREVIOUS: 1847 selectPreviousTab(current); 1848 break; 1849 case NORTH: 1850 selectPreviousTabInRun(current); 1851 break; 1852 case SOUTH: 1853 selectNextTabInRun(current); 1854 break; 1855 case WEST: 1856 offset = getTabRunOffset(tabPlacement, tabCount, current, false); 1857 selectAdjacentRunTab(tabPlacement, current, offset); 1858 break; 1859 case EAST: 1860 offset = getTabRunOffset(tabPlacement, tabCount, current, true); 1861 selectAdjacentRunTab(tabPlacement, current, offset); 1862 break; 1863 default: 1864 } 1865 break; 1866 case BOTTOM: 1867 case TOP: 1868 default: 1869 switch(direction) { 1870 case NEXT: 1871 selectNextTab(current); 1872 break; 1873 case PREVIOUS: 1874 selectPreviousTab(current); 1875 break; 1876 case NORTH: 1877 offset = getTabRunOffset(tabPlacement, tabCount, current, false); 1878 selectAdjacentRunTab(tabPlacement, current, offset); 1879 break; 1880 case SOUTH: 1881 offset = getTabRunOffset(tabPlacement, tabCount, current, true); 1882 selectAdjacentRunTab(tabPlacement, current, offset); 1883 break; 1884 case EAST: 1885 if (leftToRight) { 1886 selectNextTabInRun(current); 1887 } else { 1888 selectPreviousTabInRun(current); 1889 } 1890 break; 1891 case WEST: 1892 if (leftToRight) { 1893 selectPreviousTabInRun(current); 1894 } else { 1895 selectNextTabInRun(current); 1896 } 1897 break; 1898 default: 1899 } 1900 } 1901 } 1902 1903 protected void selectNextTabInRun(int current) { 1904 int tabCount = tabPane.getTabCount(); 1905 int tabIndex = getNextTabIndexInRun(tabCount, current); 1906 1907 while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1908 tabIndex = getNextTabIndexInRun(tabCount, tabIndex); 1909 } 1910 navigateTo(tabIndex); 1911 } 1912 1913 protected void selectPreviousTabInRun(int current) { 1914 int tabCount = tabPane.getTabCount(); 1915 int tabIndex = getPreviousTabIndexInRun(tabCount, current); 1916 1917 while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1918 tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex); 1919 } 1920 navigateTo(tabIndex); 1921 } 1922 1923 protected void selectNextTab(int current) { 1924 int tabIndex = getNextTabIndex(current); 1925 1926 while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1927 tabIndex = getNextTabIndex(tabIndex); 1928 } 1929 navigateTo(tabIndex); 1930 } 1931 1932 protected void selectPreviousTab(int current) { 1933 int tabIndex = getPreviousTabIndex(current); 1934 1935 while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) { 1936 tabIndex = getPreviousTabIndex(tabIndex); 1937 } 1938 navigateTo(tabIndex); 1939 } 1940 1941 protected void selectAdjacentRunTab(int tabPlacement, 1942 int tabIndex, int offset) { 1943 if ( runCount < 2 ) { 1944 return; 1945 } 1946 int newIndex; 1947 Rectangle r = rects[tabIndex]; 1948 switch(tabPlacement) { 1949 case LEFT: 1950 case RIGHT: 1951 newIndex = tabForCoordinate(tabPane, r.x + r.width/2 + offset, 1952 r.y + r.height/2); 1953 break; 1954 case BOTTOM: 1955 case TOP: 1956 default: 1957 newIndex = tabForCoordinate(tabPane, r.x + r.width/2, 1958 r.y + r.height/2 + offset); 1959 } 1960 if (newIndex != -1) { 1961 while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) { 1962 newIndex = getNextTabIndex(newIndex); 1963 } 1964 navigateTo(newIndex); 1965 } 1966 } 1967 1968 private void navigateTo(int index) { 1969 if (DefaultLookup.getBoolean(tabPane, this, 1970 "TabbedPane.selectionFollowsFocus", true)) { 1971 tabPane.setSelectedIndex(index); 1972 } else { 1973 // Just move focus (not selection) 1974 setFocusIndex(index, true); 1975 } 1976 } 1977 1978 void setFocusIndex(int index, boolean repaint) { 1979 if (repaint && !isRunsDirty) { 1980 repaintTab(focusIndex); 1981 focusIndex = index; 1982 repaintTab(focusIndex); 1983 } 1984 else { 1985 focusIndex = index; 1986 } 1987 } 1988 1989 /** 1990 * Repaints the specified tab. 1991 */ 1992 private void repaintTab(int index) { 1993 // If we're not valid that means we will shortly be validated and 1994 // painted, which means we don't have to do anything here. 1995 if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) { 1996 tabPane.repaint(getTabBounds(tabPane, index)); 1997 } 1998 } 1999 2000 /** 2001 * Makes sure the focusIndex is valid. 2002 */ 2003 private void validateFocusIndex() { 2004 if (focusIndex >= tabPane.getTabCount()) { 2005 setFocusIndex(tabPane.getSelectedIndex(), false); 2006 } 2007 } 2008 2009 /** 2010 * Returns the index of the tab that has focus. 2011 * 2012 * @return index of tab that has focus 2013 * @since 1.5 2014 */ 2015 protected int getFocusIndex() { 2016 return focusIndex; 2017 } 2018 2019 protected int getTabRunOffset(int tabPlacement, int tabCount, 2020 int tabIndex, boolean forward) { 2021 int run = getRunForTab(tabCount, tabIndex); 2022 int offset; 2023 switch(tabPlacement) { 2024 case LEFT: { 2025 if (run == 0) { 2026 offset = (forward? 2027 -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) : 2028 -maxTabWidth); 2029 2030 } else if (run == runCount - 1) { 2031 offset = (forward? 2032 maxTabWidth : 2033 calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth); 2034 } else { 2035 offset = (forward? maxTabWidth : -maxTabWidth); 2036 } 2037 break; 2038 } 2039 case RIGHT: { 2040 if (run == 0) { 2041 offset = (forward? 2042 maxTabWidth : 2043 calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth); 2044 } else if (run == runCount - 1) { 2045 offset = (forward? 2046 -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) : 2047 -maxTabWidth); 2048 } else { 2049 offset = (forward? maxTabWidth : -maxTabWidth); 2050 } 2051 break; 2052 } 2053 case BOTTOM: { 2054 if (run == 0) { 2055 offset = (forward? 2056 maxTabHeight : 2057 calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight); 2058 } else if (run == runCount - 1) { 2059 offset = (forward? 2060 -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) : 2061 -maxTabHeight); 2062 } else { 2063 offset = (forward? maxTabHeight : -maxTabHeight); 2064 } 2065 break; 2066 } 2067 case TOP: 2068 default: { 2069 if (run == 0) { 2070 offset = (forward? 2071 -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) : 2072 -maxTabHeight); 2073 } else if (run == runCount - 1) { 2074 offset = (forward? 2075 maxTabHeight : 2076 calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight); 2077 } else { 2078 offset = (forward? maxTabHeight : -maxTabHeight); 2079 } 2080 } 2081 } 2082 return offset; 2083 } 2084 2085 protected int getPreviousTabIndex(int base) { 2086 int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1); 2087 return (tabIndex >= 0? tabIndex : 0); 2088 } 2089 2090 protected int getNextTabIndex(int base) { 2091 return (base+1)%tabPane.getTabCount(); 2092 } 2093 2094 protected int getNextTabIndexInRun(int tabCount, int base) { 2095 if (runCount < 2) { 2096 return getNextTabIndex(base); 2097 } 2098 int currentRun = getRunForTab(tabCount, base); 2099 int next = getNextTabIndex(base); 2100 if (next == tabRuns[getNextTabRun(currentRun)]) { 2101 return tabRuns[currentRun]; 2102 } 2103 return next; 2104 } 2105 2106 protected int getPreviousTabIndexInRun(int tabCount, int base) { 2107 if (runCount < 2) { 2108 return getPreviousTabIndex(base); 2109 } 2110 int currentRun = getRunForTab(tabCount, base); 2111 if (base == tabRuns[currentRun]) { 2112 int previous = tabRuns[getNextTabRun(currentRun)]-1; 2113 return (previous != -1? previous : tabCount-1); 2114 } 2115 return getPreviousTabIndex(base); 2116 } 2117 2118 protected int getPreviousTabRun(int baseRun) { 2119 int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1); 2120 return (runIndex >= 0? runIndex : 0); 2121 } 2122 2123 protected int getNextTabRun(int baseRun) { 2124 return (baseRun+1)%runCount; 2125 } 2126 2127 protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) { 2128 2129 switch(targetPlacement) { 2130 case LEFT: 2131 targetInsets.top = topInsets.left; 2132 targetInsets.left = topInsets.top; 2133 targetInsets.bottom = topInsets.right; 2134 targetInsets.right = topInsets.bottom; 2135 break; 2136 case BOTTOM: 2137 targetInsets.top = topInsets.bottom; 2138 targetInsets.left = topInsets.left; 2139 targetInsets.bottom = topInsets.top; 2140 targetInsets.right = topInsets.right; 2141 break; 2142 case RIGHT: 2143 targetInsets.top = topInsets.left; 2144 targetInsets.left = topInsets.bottom; 2145 targetInsets.bottom = topInsets.right; 2146 targetInsets.right = topInsets.top; 2147 break; 2148 case TOP: 2149 default: 2150 targetInsets.top = topInsets.top; 2151 targetInsets.left = topInsets.left; 2152 targetInsets.bottom = topInsets.bottom; 2153 targetInsets.right = topInsets.right; 2154 } 2155 } 2156 2157 // REMIND(aim,7/29/98): This method should be made 2158 // protected in the next release where 2159 // API changes are allowed 2160 boolean requestFocusForVisibleComponent() { 2161 return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent()); 2162 } 2163 2164 private static class Actions extends UIAction { 2165 final static String NEXT = "navigateNext"; 2166 final static String PREVIOUS = "navigatePrevious"; 2167 final static String RIGHT = "navigateRight"; 2168 final static String LEFT = "navigateLeft"; 2169 final static String UP = "navigateUp"; 2170 final static String DOWN = "navigateDown"; 2171 final static String PAGE_UP = "navigatePageUp"; 2172 final static String PAGE_DOWN = "navigatePageDown"; 2173 final static String REQUEST_FOCUS = "requestFocus"; 2174 final static String REQUEST_FOCUS_FOR_VISIBLE = 2175 "requestFocusForVisibleComponent"; 2176 final static String SET_SELECTED = "setSelectedIndex"; 2177 final static String SELECT_FOCUSED = "selectTabWithFocus"; 2178 final static String SCROLL_FORWARD = "scrollTabsForwardAction"; 2179 final static String SCROLL_BACKWARD = "scrollTabsBackwardAction"; 2180 2181 Actions(String key) { 2182 super(key); 2183 } 2184 2185 public void actionPerformed(ActionEvent e) { 2186 String key = getName(); 2187 JTabbedPane pane = (JTabbedPane)e.getSource(); 2188 BasicTabbedPaneUI ui = (BasicTabbedPaneUI)BasicLookAndFeel. 2189 getUIOfType(pane.getUI(), BasicTabbedPaneUI.class); 2190 2191 if (ui == null) { 2192 return; 2193 } 2194 if (key == NEXT) { 2195 ui.navigateSelectedTab(SwingConstants.NEXT); 2196 } 2197 else if (key == PREVIOUS) { 2198 ui.navigateSelectedTab(SwingConstants.PREVIOUS); 2199 } 2200 else if (key == RIGHT) { 2201 ui.navigateSelectedTab(SwingConstants.EAST); 2202 } 2203 else if (key == LEFT) { 2204 ui.navigateSelectedTab(SwingConstants.WEST); 2205 } 2206 else if (key == UP) { 2207 ui.navigateSelectedTab(SwingConstants.NORTH); 2208 } 2209 else if (key == DOWN) { 2210 ui.navigateSelectedTab(SwingConstants.SOUTH); 2211 } 2212 else if (key == PAGE_UP) { 2213 int tabPlacement = pane.getTabPlacement(); 2214 if (tabPlacement == TOP|| tabPlacement == BOTTOM) { 2215 ui.navigateSelectedTab(SwingConstants.WEST); 2216 } else { 2217 ui.navigateSelectedTab(SwingConstants.NORTH); 2218 } 2219 } 2220 else if (key == PAGE_DOWN) { 2221 int tabPlacement = pane.getTabPlacement(); 2222 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 2223 ui.navigateSelectedTab(SwingConstants.EAST); 2224 } else { 2225 ui.navigateSelectedTab(SwingConstants.SOUTH); 2226 } 2227 } 2228 else if (key == REQUEST_FOCUS) { 2229 pane.requestFocus(); 2230 } 2231 else if (key == REQUEST_FOCUS_FOR_VISIBLE) { 2232 ui.requestFocusForVisibleComponent(); 2233 } 2234 else if (key == SET_SELECTED) { 2235 String command = e.getActionCommand(); 2236 2237 if (command != null && command.length() > 0) { 2238 int mnemonic = (int)e.getActionCommand().charAt(0); 2239 if (mnemonic >= 'a' && mnemonic <='z') { 2240 mnemonic -= ('a' - 'A'); 2241 } 2242 Integer index = ui.mnemonicToIndexMap.get(Integer.valueOf(mnemonic)); 2243 if (index != null && pane.isEnabledAt(index.intValue())) { 2244 pane.setSelectedIndex(index.intValue()); 2245 } 2246 } 2247 } 2248 else if (key == SELECT_FOCUSED) { 2249 int focusIndex = ui.getFocusIndex(); 2250 if (focusIndex != -1) { 2251 pane.setSelectedIndex(focusIndex); 2252 } 2253 } 2254 else if (key == SCROLL_FORWARD) { 2255 if (ui.scrollableTabLayoutEnabled()) { 2256 ui.tabScroller.scrollForward(pane.getTabPlacement()); 2257 } 2258 } 2259 else if (key == SCROLL_BACKWARD) { 2260 if (ui.scrollableTabLayoutEnabled()) { 2261 ui.tabScroller.scrollBackward(pane.getTabPlacement()); 2262 } 2263 } 2264 } 2265 } 2266 2267 /** 2268 * This class should be treated as a "protected" inner class. 2269 * Instantiate it only within subclasses of BasicTabbedPaneUI. 2270 */ 2271 public class TabbedPaneLayout implements LayoutManager { 2272 2273 public void addLayoutComponent(String name, Component comp) {} 2274 2275 public void removeLayoutComponent(Component comp) {} 2276 2277 public Dimension preferredLayoutSize(Container parent) { 2278 return calculateSize(false); 2279 } 2280 2281 public Dimension minimumLayoutSize(Container parent) { 2282 return calculateSize(true); 2283 } 2284 2285 protected Dimension calculateSize(boolean minimum) { 2286 int tabPlacement = tabPane.getTabPlacement(); 2287 Insets insets = tabPane.getInsets(); 2288 Insets contentInsets = getContentBorderInsets(tabPlacement); 2289 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 2290 2291 Dimension zeroSize = new Dimension(0,0); 2292 int height = 0; 2293 int width = 0; 2294 int cWidth = 0; 2295 int cHeight = 0; 2296 2297 // Determine minimum size required to display largest 2298 // child in each dimension 2299 // 2300 for (int i = 0; i < tabPane.getTabCount(); i++) { 2301 Component component = tabPane.getComponentAt(i); 2302 if (component != null) { 2303 Dimension size = minimum ? component.getMinimumSize() : 2304 component.getPreferredSize(); 2305 2306 if (size != null) { 2307 cHeight = Math.max(size.height, cHeight); 2308 cWidth = Math.max(size.width, cWidth); 2309 } 2310 } 2311 } 2312 // Add content border insets to minimum size 2313 width += cWidth; 2314 height += cHeight; 2315 int tabExtent; 2316 2317 // Calculate how much space the tabs will need, based on the 2318 // minimum size required to display largest child + content border 2319 // 2320 switch(tabPlacement) { 2321 case LEFT: 2322 case RIGHT: 2323 height = Math.max(height, calculateMaxTabHeight(tabPlacement)); 2324 tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom); 2325 width += tabExtent; 2326 break; 2327 case TOP: 2328 case BOTTOM: 2329 default: 2330 width = Math.max(width, calculateMaxTabWidth(tabPlacement)); 2331 tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right); 2332 height += tabExtent; 2333 } 2334 return new Dimension(width + insets.left + insets.right + contentInsets.left + contentInsets.right, 2335 height + insets.bottom + insets.top + contentInsets.top + contentInsets.bottom); 2336 2337 } 2338 2339 protected int preferredTabAreaHeight(int tabPlacement, int width) { 2340 FontMetrics metrics = getFontMetrics(); 2341 int tabCount = tabPane.getTabCount(); 2342 int total = 0; 2343 if (tabCount > 0) { 2344 int rows = 1; 2345 int x = 0; 2346 2347 int maxTabHeight = calculateMaxTabHeight(tabPlacement); 2348 2349 for (int i = 0; i < tabCount; i++) { 2350 int tabWidth = calculateTabWidth(tabPlacement, i, metrics); 2351 2352 if (x != 0 && x + tabWidth > width) { 2353 rows++; 2354 x = 0; 2355 } 2356 x += tabWidth; 2357 } 2358 total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight); 2359 } 2360 return total; 2361 } 2362 2363 protected int preferredTabAreaWidth(int tabPlacement, int height) { 2364 FontMetrics metrics = getFontMetrics(); 2365 int tabCount = tabPane.getTabCount(); 2366 int total = 0; 2367 if (tabCount > 0) { 2368 int columns = 1; 2369 int y = 0; 2370 int fontHeight = metrics.getHeight(); 2371 2372 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2373 2374 for (int i = 0; i < tabCount; i++) { 2375 int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight); 2376 2377 if (y != 0 && y + tabHeight > height) { 2378 columns++; 2379 y = 0; 2380 } 2381 y += tabHeight; 2382 } 2383 total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth); 2384 } 2385 return total; 2386 } 2387 2388 public void layoutContainer(Container parent) { 2389 /* Some of the code in this method deals with changing the 2390 * visibility of components to hide and show the contents for the 2391 * selected tab. This is older code that has since been duplicated 2392 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2393 * changes to happen sooner (see the note there). This code remains 2394 * for backward compatibility as there are some cases, such as 2395 * subclasses that don't fireStateChanged() where it may be used. 2396 * Any changes here need to be kept in synch with 2397 * JTabbedPane.fireStateChanged(). 2398 */ 2399 2400 setRolloverTab(-1); 2401 2402 int tabPlacement = tabPane.getTabPlacement(); 2403 Insets insets = tabPane.getInsets(); 2404 int selectedIndex = tabPane.getSelectedIndex(); 2405 Component visibleComponent = getVisibleComponent(); 2406 2407 calculateLayoutInfo(); 2408 2409 Component selectedComponent = null; 2410 if (selectedIndex < 0) { 2411 if (visibleComponent != null) { 2412 // The last tab was removed, so remove the component 2413 setVisibleComponent(null); 2414 } 2415 } else { 2416 selectedComponent = tabPane.getComponentAt(selectedIndex); 2417 } 2418 int cx, cy, cw, ch; 2419 int totalTabWidth = 0; 2420 int totalTabHeight = 0; 2421 Insets contentInsets = getContentBorderInsets(tabPlacement); 2422 2423 boolean shouldChangeFocus = false; 2424 2425 // In order to allow programs to use a single component 2426 // as the display for multiple tabs, we will not change 2427 // the visible compnent if the currently selected tab 2428 // has a null component. This is a bit dicey, as we don't 2429 // explicitly state we support this in the spec, but since 2430 // programs are now depending on this, we're making it work. 2431 // 2432 if(selectedComponent != null) { 2433 if(selectedComponent != visibleComponent && 2434 visibleComponent != null) { 2435 if(SwingUtilities.findFocusOwner(visibleComponent) != null) { 2436 shouldChangeFocus = true; 2437 } 2438 } 2439 setVisibleComponent(selectedComponent); 2440 } 2441 2442 Rectangle bounds = tabPane.getBounds(); 2443 int numChildren = tabPane.getComponentCount(); 2444 2445 if(numChildren > 0) { 2446 2447 switch(tabPlacement) { 2448 case LEFT: 2449 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2450 cx = insets.left + totalTabWidth + contentInsets.left; 2451 cy = insets.top + contentInsets.top; 2452 break; 2453 case RIGHT: 2454 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2455 cx = insets.left + contentInsets.left; 2456 cy = insets.top + contentInsets.top; 2457 break; 2458 case BOTTOM: 2459 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2460 cx = insets.left + contentInsets.left; 2461 cy = insets.top + contentInsets.top; 2462 break; 2463 case TOP: 2464 default: 2465 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2466 cx = insets.left + contentInsets.left; 2467 cy = insets.top + totalTabHeight + contentInsets.top; 2468 } 2469 2470 cw = bounds.width - totalTabWidth - 2471 insets.left - insets.right - 2472 contentInsets.left - contentInsets.right; 2473 ch = bounds.height - totalTabHeight - 2474 insets.top - insets.bottom - 2475 contentInsets.top - contentInsets.bottom; 2476 2477 for(int i = 0; i < numChildren; i++) { 2478 Component child = tabPane.getComponent(i); 2479 if(child == tabContainer) { 2480 2481 int tabContainerWidth = totalTabWidth == 0 ? bounds.width : 2482 totalTabWidth + insets.left + insets.right + 2483 contentInsets.left + contentInsets.right; 2484 int tabContainerHeight = totalTabHeight == 0 ? bounds.height : 2485 totalTabHeight + insets.top + insets.bottom + 2486 contentInsets.top + contentInsets.bottom; 2487 2488 int tabContainerX = 0; 2489 int tabContainerY = 0; 2490 if(tabPlacement == BOTTOM) { 2491 tabContainerY = bounds.height - tabContainerHeight; 2492 } else if(tabPlacement == RIGHT) { 2493 tabContainerX = bounds.width - tabContainerWidth; 2494 } 2495 child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight); 2496 } else { 2497 child.setBounds(cx, cy, cw, ch); 2498 } 2499 } 2500 } 2501 layoutTabComponents(); 2502 if(shouldChangeFocus) { 2503 if(!requestFocusForVisibleComponent()) { 2504 tabPane.requestFocus(); 2505 } 2506 } 2507 } 2508 2509 public void calculateLayoutInfo() { 2510 int tabCount = tabPane.getTabCount(); 2511 assureRectsCreated(tabCount); 2512 calculateTabRects(tabPane.getTabPlacement(), tabCount); 2513 isRunsDirty = false; 2514 } 2515 2516 private void layoutTabComponents() { 2517 if (tabContainer == null) { 2518 return; 2519 } 2520 Rectangle rect = new Rectangle(); 2521 Point delta = new Point(-tabContainer.getX(), -tabContainer.getY()); 2522 if (scrollableTabLayoutEnabled()) { 2523 translatePointToTabPanel(0, 0, delta); 2524 } 2525 for (int i = 0; i < tabPane.getTabCount(); i++) { 2526 Component c = tabPane.getTabComponentAt(i); 2527 if (c == null) { 2528 continue; 2529 } 2530 getTabBounds(i, rect); 2531 Dimension preferredSize = c.getPreferredSize(); 2532 Insets insets = getTabInsets(tabPane.getTabPlacement(), i); 2533 int outerX = rect.x + insets.left + delta.x; 2534 int outerY = rect.y + insets.top + delta.y; 2535 int outerWidth = rect.width - insets.left - insets.right; 2536 int outerHeight = rect.height - insets.top - insets.bottom; 2537 //centralize component 2538 int x = outerX + (outerWidth - preferredSize.width) / 2; 2539 int y = outerY + (outerHeight - preferredSize.height) / 2; 2540 int tabPlacement = tabPane.getTabPlacement(); 2541 boolean isSeleceted = i == tabPane.getSelectedIndex(); 2542 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted), 2543 y + getTabLabelShiftY(tabPlacement, i, isSeleceted), 2544 preferredSize.width, preferredSize.height); 2545 } 2546 } 2547 2548 protected void calculateTabRects(int tabPlacement, int tabCount) { 2549 FontMetrics metrics = getFontMetrics(); 2550 Dimension size = tabPane.getSize(); 2551 Insets insets = tabPane.getInsets(); 2552 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 2553 int fontHeight = metrics.getHeight(); 2554 int selectedIndex = tabPane.getSelectedIndex(); 2555 int tabRunOverlay; 2556 int i, j; 2557 int x, y; 2558 int returnAt; 2559 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2560 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 2561 2562 // 2563 // Calculate bounds within which a tab run must fit 2564 // 2565 switch(tabPlacement) { 2566 case LEFT: 2567 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2568 x = insets.left + tabAreaInsets.left; 2569 y = insets.top + tabAreaInsets.top; 2570 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2571 break; 2572 case RIGHT: 2573 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2574 x = size.width - insets.right - tabAreaInsets.right - maxTabWidth; 2575 y = insets.top + tabAreaInsets.top; 2576 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2577 break; 2578 case BOTTOM: 2579 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2580 x = insets.left + tabAreaInsets.left; 2581 y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight; 2582 returnAt = size.width - (insets.right + tabAreaInsets.right); 2583 break; 2584 case TOP: 2585 default: 2586 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2587 x = insets.left + tabAreaInsets.left; 2588 y = insets.top + tabAreaInsets.top; 2589 returnAt = size.width - (insets.right + tabAreaInsets.right); 2590 break; 2591 } 2592 2593 tabRunOverlay = getTabRunOverlay(tabPlacement); 2594 2595 runCount = 0; 2596 selectedRun = -1; 2597 2598 if (tabCount == 0) { 2599 return; 2600 } 2601 2602 // Run through tabs and partition them into runs 2603 Rectangle rect; 2604 for (i = 0; i < tabCount; i++) { 2605 rect = rects[i]; 2606 2607 if (!verticalTabRuns) { 2608 // Tabs on TOP or BOTTOM.... 2609 if (i > 0) { 2610 rect.x = rects[i-1].x + rects[i-1].width; 2611 } else { 2612 tabRuns[0] = 0; 2613 runCount = 1; 2614 maxTabWidth = 0; 2615 rect.x = x; 2616 } 2617 rect.width = calculateTabWidth(tabPlacement, i, metrics); 2618 maxTabWidth = Math.max(maxTabWidth, rect.width); 2619 2620 // Never move a TAB down a run if it is in the first column. 2621 // Even if there isn't enough room, moving it to a fresh 2622 // line won't help. 2623 if (rect.x != x && rect.x + rect.width > returnAt) { 2624 if (runCount > tabRuns.length - 1) { 2625 expandTabRunsArray(); 2626 } 2627 tabRuns[runCount] = i; 2628 runCount++; 2629 rect.x = x; 2630 } 2631 // Initialize y position in case there's just one run 2632 rect.y = y; 2633 rect.height = maxTabHeight/* - 2*/; 2634 2635 } else { 2636 // Tabs on LEFT or RIGHT... 2637 if (i > 0) { 2638 rect.y = rects[i-1].y + rects[i-1].height; 2639 } else { 2640 tabRuns[0] = 0; 2641 runCount = 1; 2642 maxTabHeight = 0; 2643 rect.y = y; 2644 } 2645 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 2646 maxTabHeight = Math.max(maxTabHeight, rect.height); 2647 2648 // Never move a TAB over a run if it is in the first run. 2649 // Even if there isn't enough room, moving it to a fresh 2650 // column won't help. 2651 if (rect.y != y && rect.y + rect.height > returnAt) { 2652 if (runCount > tabRuns.length - 1) { 2653 expandTabRunsArray(); 2654 } 2655 tabRuns[runCount] = i; 2656 runCount++; 2657 rect.y = y; 2658 } 2659 // Initialize x position in case there's just one column 2660 rect.x = x; 2661 rect.width = maxTabWidth/* - 2*/; 2662 2663 } 2664 if (i == selectedIndex) { 2665 selectedRun = runCount - 1; 2666 } 2667 } 2668 2669 if (runCount > 1) { 2670 // Re-distribute tabs in case last run has leftover space 2671 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt); 2672 2673 selectedRun = getRunForTab(tabCount, selectedIndex); 2674 2675 // Rotate run array so that selected run is first 2676 if (shouldRotateTabRuns(tabPlacement)) { 2677 rotateTabRuns(tabPlacement, selectedRun); 2678 } 2679 } 2680 2681 // Step through runs from back to front to calculate 2682 // tab y locations and to pad runs appropriately 2683 for (i = runCount - 1; i >= 0; i--) { 2684 int start = tabRuns[i]; 2685 int next = tabRuns[i == (runCount - 1)? 0 : i + 1]; 2686 int end = (next != 0? next - 1 : tabCount - 1); 2687 if (!verticalTabRuns) { 2688 for (j = start; j <= end; j++) { 2689 rect = rects[j]; 2690 rect.y = y; 2691 rect.x += getTabRunIndent(tabPlacement, i); 2692 } 2693 if (shouldPadTabRun(tabPlacement, i)) { 2694 padTabRun(tabPlacement, start, end, returnAt); 2695 } 2696 if (tabPlacement == BOTTOM) { 2697 y -= (maxTabHeight - tabRunOverlay); 2698 } else { 2699 y += (maxTabHeight - tabRunOverlay); 2700 } 2701 } else { 2702 for (j = start; j <= end; j++) { 2703 rect = rects[j]; 2704 rect.x = x; 2705 rect.y += getTabRunIndent(tabPlacement, i); 2706 } 2707 if (shouldPadTabRun(tabPlacement, i)) { 2708 padTabRun(tabPlacement, start, end, returnAt); 2709 } 2710 if (tabPlacement == RIGHT) { 2711 x -= (maxTabWidth - tabRunOverlay); 2712 } else { 2713 x += (maxTabWidth - tabRunOverlay); 2714 } 2715 } 2716 } 2717 2718 // Pad the selected tab so that it appears raised in front 2719 padSelectedTab(tabPlacement, selectedIndex); 2720 2721 // if right to left and tab placement on the top or 2722 // the bottom, flip x positions and adjust by widths 2723 if (!leftToRight && !verticalTabRuns) { 2724 int rightMargin = size.width 2725 - (insets.right + tabAreaInsets.right); 2726 for (i = 0; i < tabCount; i++) { 2727 rects[i].x = rightMargin - rects[i].x - rects[i].width; 2728 } 2729 } 2730 } 2731 2732 2733 /* 2734 * Rotates the run-index array so that the selected run is run[0] 2735 */ 2736 protected void rotateTabRuns(int tabPlacement, int selectedRun) { 2737 for (int i = 0; i < selectedRun; i++) { 2738 int save = tabRuns[0]; 2739 for (int j = 1; j < runCount; j++) { 2740 tabRuns[j - 1] = tabRuns[j]; 2741 } 2742 tabRuns[runCount-1] = save; 2743 } 2744 } 2745 2746 protected void normalizeTabRuns(int tabPlacement, int tabCount, 2747 int start, int max) { 2748 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2749 int run = runCount - 1; 2750 boolean keepAdjusting = true; 2751 double weight = 1.25; 2752 2753 // At this point the tab runs are packed to fit as many 2754 // tabs as possible, which can leave the last run with a lot 2755 // of extra space (resulting in very fat tabs on the last run). 2756 // So we'll attempt to distribute this extra space more evenly 2757 // across the runs in order to make the runs look more consistent. 2758 // 2759 // Starting with the last run, determine whether the last tab in 2760 // the previous run would fit (generously) in this run; if so, 2761 // move tab to current run and shift tabs accordingly. Cycle 2762 // through remaining runs using the same algorithm. 2763 // 2764 while (keepAdjusting) { 2765 int last = lastTabInRun(tabCount, run); 2766 int prevLast = lastTabInRun(tabCount, run-1); 2767 int end; 2768 int prevLastLen; 2769 2770 if (!verticalTabRuns) { 2771 end = rects[last].x + rects[last].width; 2772 prevLastLen = (int)(maxTabWidth*weight); 2773 } else { 2774 end = rects[last].y + rects[last].height; 2775 prevLastLen = (int)(maxTabHeight*weight*2); 2776 } 2777 2778 // Check if the run has enough extra space to fit the last tab 2779 // from the previous row... 2780 if (max - end > prevLastLen) { 2781 2782 // Insert tab from previous row and shift rest over 2783 tabRuns[run] = prevLast; 2784 if (!verticalTabRuns) { 2785 rects[prevLast].x = start; 2786 } else { 2787 rects[prevLast].y = start; 2788 } 2789 for (int i = prevLast+1; i <= last; i++) { 2790 if (!verticalTabRuns) { 2791 rects[i].x = rects[i-1].x + rects[i-1].width; 2792 } else { 2793 rects[i].y = rects[i-1].y + rects[i-1].height; 2794 } 2795 } 2796 2797 } else if (run == runCount - 1) { 2798 // no more room left in last run, so we're done! 2799 keepAdjusting = false; 2800 } 2801 if (run - 1 > 0) { 2802 // check previous run next... 2803 run -= 1; 2804 } else { 2805 // check last run again...but require a higher ratio 2806 // of extraspace-to-tabsize because we don't want to 2807 // end up with too many tabs on the last run! 2808 run = runCount - 1; 2809 weight += .25; 2810 } 2811 } 2812 } 2813 2814 protected void padTabRun(int tabPlacement, int start, int end, int max) { 2815 Rectangle lastRect = rects[end]; 2816 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 2817 int runWidth = (lastRect.x + lastRect.width) - rects[start].x; 2818 int deltaWidth = max - (lastRect.x + lastRect.width); 2819 float factor = (float)deltaWidth / (float)runWidth; 2820 2821 for (int j = start; j <= end; j++) { 2822 Rectangle pastRect = rects[j]; 2823 if (j > start) { 2824 pastRect.x = rects[j-1].x + rects[j-1].width; 2825 } 2826 pastRect.width += Math.round((float)pastRect.width * factor); 2827 } 2828 lastRect.width = max - lastRect.x; 2829 } else { 2830 int runHeight = (lastRect.y + lastRect.height) - rects[start].y; 2831 int deltaHeight = max - (lastRect.y + lastRect.height); 2832 float factor = (float)deltaHeight / (float)runHeight; 2833 2834 for (int j = start; j <= end; j++) { 2835 Rectangle pastRect = rects[j]; 2836 if (j > start) { 2837 pastRect.y = rects[j-1].y + rects[j-1].height; 2838 } 2839 pastRect.height += Math.round((float)pastRect.height * factor); 2840 } 2841 lastRect.height = max - lastRect.y; 2842 } 2843 } 2844 2845 protected void padSelectedTab(int tabPlacement, int selectedIndex) { 2846 2847 if (selectedIndex >= 0) { 2848 Rectangle selRect = rects[selectedIndex]; 2849 Insets padInsets = getSelectedTabPadInsets(tabPlacement); 2850 selRect.x -= padInsets.left; 2851 selRect.width += (padInsets.left + padInsets.right); 2852 selRect.y -= padInsets.top; 2853 selRect.height += (padInsets.top + padInsets.bottom); 2854 2855 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT 2856 // do not expand selected tab more then necessary 2857 Dimension size = tabPane.getSize(); 2858 Insets insets = tabPane.getInsets(); 2859 2860 if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) { 2861 int top = insets.top - selRect.y; 2862 if (top > 0) { 2863 selRect.y += top; 2864 selRect.height -= top; 2865 } 2866 int bottom = (selRect.y + selRect.height) + insets.bottom - size.height; 2867 if (bottom > 0) { 2868 selRect.height -= bottom; 2869 } 2870 } else { 2871 int left = insets.left - selRect.x; 2872 if (left > 0) { 2873 selRect.x += left; 2874 selRect.width -= left; 2875 } 2876 int right = (selRect.x + selRect.width) + insets.right - size.width; 2877 if (right > 0) { 2878 selRect.width -= right; 2879 } 2880 } 2881 } 2882 } 2883 } 2884 } 2885 2886 private class TabbedPaneScrollLayout extends TabbedPaneLayout { 2887 2888 protected int preferredTabAreaHeight(int tabPlacement, int width) { 2889 return calculateMaxTabHeight(tabPlacement); 2890 } 2891 2892 protected int preferredTabAreaWidth(int tabPlacement, int height) { 2893 return calculateMaxTabWidth(tabPlacement); 2894 } 2895 2896 public void layoutContainer(Container parent) { 2897 /* Some of the code in this method deals with changing the 2898 * visibility of components to hide and show the contents for the 2899 * selected tab. This is older code that has since been duplicated 2900 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2901 * changes to happen sooner (see the note there). This code remains 2902 * for backward compatibility as there are some cases, such as 2903 * subclasses that don't fireStateChanged() where it may be used. 2904 * Any changes here need to be kept in synch with 2905 * JTabbedPane.fireStateChanged(). 2906 */ 2907 2908 setRolloverTab(-1); 2909 2910 int tabPlacement = tabPane.getTabPlacement(); 2911 int tabCount = tabPane.getTabCount(); 2912 Insets insets = tabPane.getInsets(); 2913 int selectedIndex = tabPane.getSelectedIndex(); 2914 Component visibleComponent = getVisibleComponent(); 2915 2916 calculateLayoutInfo(); 2917 2918 Component selectedComponent = null; 2919 if (selectedIndex < 0) { 2920 if (visibleComponent != null) { 2921 // The last tab was removed, so remove the component 2922 setVisibleComponent(null); 2923 } 2924 } else { 2925 selectedComponent = tabPane.getComponentAt(selectedIndex); 2926 } 2927 2928 if (tabPane.getTabCount() == 0) { 2929 tabScroller.croppedEdge.resetParams(); 2930 tabScroller.scrollForwardButton.setVisible(false); 2931 tabScroller.scrollBackwardButton.setVisible(false); 2932 return; 2933 } 2934 2935 boolean shouldChangeFocus = false; 2936 2937 // In order to allow programs to use a single component 2938 // as the display for multiple tabs, we will not change 2939 // the visible compnent if the currently selected tab 2940 // has a null component. This is a bit dicey, as we don't 2941 // explicitly state we support this in the spec, but since 2942 // programs are now depending on this, we're making it work. 2943 // 2944 if(selectedComponent != null) { 2945 if(selectedComponent != visibleComponent && 2946 visibleComponent != null) { 2947 if(SwingUtilities.findFocusOwner(visibleComponent) != null) { 2948 shouldChangeFocus = true; 2949 } 2950 } 2951 setVisibleComponent(selectedComponent); 2952 } 2953 int tx, ty, tw, th; // tab area bounds 2954 int cx, cy, cw, ch; // content area bounds 2955 Insets contentInsets = getContentBorderInsets(tabPlacement); 2956 Rectangle bounds = tabPane.getBounds(); 2957 int numChildren = tabPane.getComponentCount(); 2958 2959 if(numChildren > 0) { 2960 switch(tabPlacement) { 2961 case LEFT: 2962 // calculate tab area bounds 2963 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2964 th = bounds.height - insets.top - insets.bottom; 2965 tx = insets.left; 2966 ty = insets.top; 2967 2968 // calculate content area bounds 2969 cx = tx + tw + contentInsets.left; 2970 cy = ty + contentInsets.top; 2971 cw = bounds.width - insets.left - insets.right - tw - 2972 contentInsets.left - contentInsets.right; 2973 ch = bounds.height - insets.top - insets.bottom - 2974 contentInsets.top - contentInsets.bottom; 2975 break; 2976 case RIGHT: 2977 // calculate tab area bounds 2978 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2979 th = bounds.height - insets.top - insets.bottom; 2980 tx = bounds.width - insets.right - tw; 2981 ty = insets.top; 2982 2983 // calculate content area bounds 2984 cx = insets.left + contentInsets.left; 2985 cy = insets.top + contentInsets.top; 2986 cw = bounds.width - insets.left - insets.right - tw - 2987 contentInsets.left - contentInsets.right; 2988 ch = bounds.height - insets.top - insets.bottom - 2989 contentInsets.top - contentInsets.bottom; 2990 break; 2991 case BOTTOM: 2992 // calculate tab area bounds 2993 tw = bounds.width - insets.left - insets.right; 2994 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2995 tx = insets.left; 2996 ty = bounds.height - insets.bottom - th; 2997 2998 // calculate content area bounds 2999 cx = insets.left + contentInsets.left; 3000 cy = insets.top + contentInsets.top; 3001 cw = bounds.width - insets.left - insets.right - 3002 contentInsets.left - contentInsets.right; 3003 ch = bounds.height - insets.top - insets.bottom - th - 3004 contentInsets.top - contentInsets.bottom; 3005 break; 3006 case TOP: 3007 default: 3008 // calculate tab area bounds 3009 tw = bounds.width - insets.left - insets.right; 3010 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 3011 tx = insets.left; 3012 ty = insets.top; 3013 3014 // calculate content area bounds 3015 cx = tx + contentInsets.left; 3016 cy = ty + th + contentInsets.top; 3017 cw = bounds.width - insets.left - insets.right - 3018 contentInsets.left - contentInsets.right; 3019 ch = bounds.height - insets.top - insets.bottom - th - 3020 contentInsets.top - contentInsets.bottom; 3021 } 3022 3023 for(int i = 0; i < numChildren; i++) { 3024 Component child = tabPane.getComponent(i); 3025 3026 if(tabScroller != null && child == tabScroller.viewport) { 3027 JViewport viewport = (JViewport) child; 3028 Rectangle viewRect = viewport.getViewRect(); 3029 int vw = tw; 3030 int vh = th; 3031 Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize(); 3032 switch(tabPlacement) { 3033 case LEFT: 3034 case RIGHT: 3035 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 3036 if(totalTabHeight > th) { 3037 // Allow space for scrollbuttons 3038 vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0; 3039 if(totalTabHeight - viewRect.y <= vh) { 3040 // Scrolled to the end, so ensure the viewport size is 3041 // such that the scroll offset aligns with a tab 3042 vh = totalTabHeight - viewRect.y; 3043 } 3044 } 3045 break; 3046 case BOTTOM: 3047 case TOP: 3048 default: 3049 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 3050 if(totalTabWidth > tw) { 3051 // Need to allow space for scrollbuttons 3052 vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0; 3053 if(totalTabWidth - viewRect.x <= vw) { 3054 // Scrolled to the end, so ensure the viewport size is 3055 // such that the scroll offset aligns with a tab 3056 vw = totalTabWidth - viewRect.x; 3057 } 3058 } 3059 } 3060 child.setBounds(tx, ty, vw, vh); 3061 3062 } else if(tabScroller != null && 3063 (child == tabScroller.scrollForwardButton || 3064 child == tabScroller.scrollBackwardButton)) { 3065 Component scrollbutton = child; 3066 Dimension bsize = scrollbutton.getPreferredSize(); 3067 int bx = 0; 3068 int by = 0; 3069 int bw = bsize.width; 3070 int bh = bsize.height; 3071 boolean visible = false; 3072 3073 switch(tabPlacement) { 3074 case LEFT: 3075 case RIGHT: 3076 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 3077 if(totalTabHeight > th) { 3078 visible = true; 3079 bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx); 3080 by = (child == tabScroller.scrollForwardButton) ? 3081 bounds.height - insets.bottom - bsize.height : 3082 bounds.height - insets.bottom - 2 * bsize.height; 3083 } 3084 break; 3085 3086 case BOTTOM: 3087 case TOP: 3088 default: 3089 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 3090 3091 if(totalTabWidth > tw) { 3092 visible = true; 3093 bx = (child == tabScroller.scrollForwardButton) ? 3094 bounds.width - insets.left - bsize.width : 3095 bounds.width - insets.left - 2 * bsize.width; 3096 by = (tabPlacement == TOP ? ty + th - bsize.height : ty); 3097 } 3098 } 3099 child.setVisible(visible); 3100 if(visible) { 3101 child.setBounds(bx, by, bw, bh); 3102 } 3103 3104 } else { 3105 // All content children... 3106 child.setBounds(cx, cy, cw, ch); 3107 } 3108 } 3109 super.layoutTabComponents(); 3110 layoutCroppedEdge(); 3111 if(shouldChangeFocus) { 3112 if(!requestFocusForVisibleComponent()) { 3113 tabPane.requestFocus(); 3114 } 3115 } 3116 } 3117 } 3118 3119 private void layoutCroppedEdge() { 3120 tabScroller.croppedEdge.resetParams(); 3121 Rectangle viewRect = tabScroller.viewport.getViewRect(); 3122 int cropline; 3123 for (int i = 0; i < rects.length; i++) { 3124 Rectangle tabRect = rects[i]; 3125 switch (tabPane.getTabPlacement()) { 3126 case LEFT: 3127 case RIGHT: 3128 cropline = viewRect.y + viewRect.height; 3129 if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) { 3130 tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1, 3131 -currentTabAreaInsets.left, 0); 3132 } 3133 break; 3134 case TOP: 3135 case BOTTOM: 3136 default: 3137 cropline = viewRect.x + viewRect.width; 3138 if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) { 3139 tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1, 3140 0, -currentTabAreaInsets.top); 3141 } 3142 } 3143 } 3144 } 3145 3146 protected void calculateTabRects(int tabPlacement, int tabCount) { 3147 FontMetrics metrics = getFontMetrics(); 3148 Dimension size = tabPane.getSize(); 3149 Insets insets = tabPane.getInsets(); 3150 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 3151 int fontHeight = metrics.getHeight(); 3152 int selectedIndex = tabPane.getSelectedIndex(); 3153 int i; 3154 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 3155 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 3156 int x = tabAreaInsets.left; 3157 int y = tabAreaInsets.top; 3158 int totalWidth = 0; 3159 int totalHeight = 0; 3160 3161 // 3162 // Calculate bounds within which a tab run must fit 3163 // 3164 switch(tabPlacement) { 3165 case LEFT: 3166 case RIGHT: 3167 maxTabWidth = calculateMaxTabWidth(tabPlacement); 3168 break; 3169 case BOTTOM: 3170 case TOP: 3171 default: 3172 maxTabHeight = calculateMaxTabHeight(tabPlacement); 3173 } 3174 3175 runCount = 0; 3176 selectedRun = -1; 3177 3178 if (tabCount == 0) { 3179 return; 3180 } 3181 3182 selectedRun = 0; 3183 runCount = 1; 3184 3185 // Run through tabs and lay them out in a single run 3186 Rectangle rect; 3187 for (i = 0; i < tabCount; i++) { 3188 rect = rects[i]; 3189 3190 if (!verticalTabRuns) { 3191 // Tabs on TOP or BOTTOM.... 3192 if (i > 0) { 3193 rect.x = rects[i-1].x + rects[i-1].width; 3194 } else { 3195 tabRuns[0] = 0; 3196 maxTabWidth = 0; 3197 totalHeight += maxTabHeight; 3198 rect.x = x; 3199 } 3200 rect.width = calculateTabWidth(tabPlacement, i, metrics); 3201 totalWidth = rect.x + rect.width; 3202 maxTabWidth = Math.max(maxTabWidth, rect.width); 3203 3204 rect.y = y; 3205 rect.height = maxTabHeight/* - 2*/; 3206 3207 } else { 3208 // Tabs on LEFT or RIGHT... 3209 if (i > 0) { 3210 rect.y = rects[i-1].y + rects[i-1].height; 3211 } else { 3212 tabRuns[0] = 0; 3213 maxTabHeight = 0; 3214 totalWidth = maxTabWidth; 3215 rect.y = y; 3216 } 3217 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 3218 totalHeight = rect.y + rect.height; 3219 maxTabHeight = Math.max(maxTabHeight, rect.height); 3220 3221 rect.x = x; 3222 rect.width = maxTabWidth/* - 2*/; 3223 3224 } 3225 } 3226 3227 if (tabsOverlapBorder) { 3228 // Pad the selected tab so that it appears raised in front 3229 padSelectedTab(tabPlacement, selectedIndex); 3230 } 3231 3232 // if right to left and tab placement on the top or 3233 // the bottom, flip x positions and adjust by widths 3234 if (!leftToRight && !verticalTabRuns) { 3235 int rightMargin = size.width 3236 - (insets.right + tabAreaInsets.right); 3237 for (i = 0; i < tabCount; i++) { 3238 rects[i].x = rightMargin - rects[i].x - rects[i].width; 3239 } 3240 } 3241 tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight)); 3242 } 3243 } 3244 3245 private class ScrollableTabSupport implements ActionListener, 3246 ChangeListener { 3247 public ScrollableTabViewport viewport; 3248 public ScrollableTabPanel tabPanel; 3249 public JButton scrollForwardButton; 3250 public JButton scrollBackwardButton; 3251 public CroppedEdge croppedEdge; 3252 public int leadingTabIndex; 3253 3254 private Point tabViewPosition = new Point(0,0); 3255 3256 ScrollableTabSupport(int tabPlacement) { 3257 viewport = new ScrollableTabViewport(); 3258 tabPanel = new ScrollableTabPanel(); 3259 viewport.setView(tabPanel); 3260 viewport.addChangeListener(this); 3261 croppedEdge = new CroppedEdge(); 3262 createButtons(); 3263 } 3264 3265 /** 3266 * Recreates the scroll buttons and adds them to the TabbedPane. 3267 */ 3268 void createButtons() { 3269 if (scrollForwardButton != null) { 3270 tabPane.remove(scrollForwardButton); 3271 scrollForwardButton.removeActionListener(this); 3272 tabPane.remove(scrollBackwardButton); 3273 scrollBackwardButton.removeActionListener(this); 3274 } 3275 int tabPlacement = tabPane.getTabPlacement(); 3276 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3277 scrollForwardButton = createScrollButton(EAST); 3278 scrollBackwardButton = createScrollButton(WEST); 3279 3280 } else { // tabPlacement = LEFT || RIGHT 3281 scrollForwardButton = createScrollButton(SOUTH); 3282 scrollBackwardButton = createScrollButton(NORTH); 3283 } 3284 scrollForwardButton.addActionListener(this); 3285 scrollBackwardButton.addActionListener(this); 3286 tabPane.add(scrollForwardButton); 3287 tabPane.add(scrollBackwardButton); 3288 } 3289 3290 public void scrollForward(int tabPlacement) { 3291 Dimension viewSize = viewport.getViewSize(); 3292 Rectangle viewRect = viewport.getViewRect(); 3293 3294 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3295 if (viewRect.width >= viewSize.width - viewRect.x) { 3296 return; // no room left to scroll 3297 } 3298 } else { // tabPlacement == LEFT || tabPlacement == RIGHT 3299 if (viewRect.height >= viewSize.height - viewRect.y) { 3300 return; 3301 } 3302 } 3303 setLeadingTabIndex(tabPlacement, leadingTabIndex+1); 3304 } 3305 3306 public void scrollBackward(int tabPlacement) { 3307 if (leadingTabIndex == 0) { 3308 return; // no room left to scroll 3309 } 3310 setLeadingTabIndex(tabPlacement, leadingTabIndex-1); 3311 } 3312 3313 public void setLeadingTabIndex(int tabPlacement, int index) { 3314 leadingTabIndex = index; 3315 Dimension viewSize = viewport.getViewSize(); 3316 Rectangle viewRect = viewport.getViewRect(); 3317 3318 switch(tabPlacement) { 3319 case TOP: 3320 case BOTTOM: 3321 tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x; 3322 3323 if ((viewSize.width - tabViewPosition.x) < viewRect.width) { 3324 // We've scrolled to the end, so adjust the viewport size 3325 // to ensure the view position remains aligned on a tab boundary 3326 Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x, 3327 viewRect.height); 3328 viewport.setExtentSize(extentSize); 3329 } 3330 break; 3331 case LEFT: 3332 case RIGHT: 3333 tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y; 3334 3335 if ((viewSize.height - tabViewPosition.y) < viewRect.height) { 3336 // We've scrolled to the end, so adjust the viewport size 3337 // to ensure the view position remains aligned on a tab boundary 3338 Dimension extentSize = new Dimension(viewRect.width, 3339 viewSize.height - tabViewPosition.y); 3340 viewport.setExtentSize(extentSize); 3341 } 3342 } 3343 viewport.setViewPosition(tabViewPosition); 3344 } 3345 3346 public void stateChanged(ChangeEvent e) { 3347 updateView(); 3348 } 3349 3350 private void updateView() { 3351 int tabPlacement = tabPane.getTabPlacement(); 3352 int tabCount = tabPane.getTabCount(); 3353 assureRectsCreated(tabCount); 3354 Rectangle vpRect = viewport.getBounds(); 3355 Dimension viewSize = viewport.getViewSize(); 3356 Rectangle viewRect = viewport.getViewRect(); 3357 3358 leadingTabIndex = getClosestTab(viewRect.x, viewRect.y); 3359 3360 // If the tab isn't right aligned, adjust it. 3361 if (leadingTabIndex + 1 < tabCount) { 3362 switch (tabPlacement) { 3363 case TOP: 3364 case BOTTOM: 3365 if (rects[leadingTabIndex].x < viewRect.x) { 3366 leadingTabIndex++; 3367 } 3368 break; 3369 case LEFT: 3370 case RIGHT: 3371 if (rects[leadingTabIndex].y < viewRect.y) { 3372 leadingTabIndex++; 3373 } 3374 break; 3375 } 3376 } 3377 Insets contentInsets = getContentBorderInsets(tabPlacement); 3378 switch(tabPlacement) { 3379 case LEFT: 3380 tabPane.repaint(vpRect.x+vpRect.width, vpRect.y, 3381 contentInsets.left, vpRect.height); 3382 scrollBackwardButton.setEnabled( 3383 viewRect.y > 0 && leadingTabIndex > 0); 3384 scrollForwardButton.setEnabled( 3385 leadingTabIndex < tabCount-1 && 3386 viewSize.height-viewRect.y > viewRect.height); 3387 break; 3388 case RIGHT: 3389 tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y, 3390 contentInsets.right, vpRect.height); 3391 scrollBackwardButton.setEnabled( 3392 viewRect.y > 0 && leadingTabIndex > 0); 3393 scrollForwardButton.setEnabled( 3394 leadingTabIndex < tabCount-1 && 3395 viewSize.height-viewRect.y > viewRect.height); 3396 break; 3397 case BOTTOM: 3398 tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom, 3399 vpRect.width, contentInsets.bottom); 3400 scrollBackwardButton.setEnabled( 3401 viewRect.x > 0 && leadingTabIndex > 0); 3402 scrollForwardButton.setEnabled( 3403 leadingTabIndex < tabCount-1 && 3404 viewSize.width-viewRect.x > viewRect.width); 3405 break; 3406 case TOP: 3407 default: 3408 tabPane.repaint(vpRect.x, vpRect.y+vpRect.height, 3409 vpRect.width, contentInsets.top); 3410 scrollBackwardButton.setEnabled( 3411 viewRect.x > 0 && leadingTabIndex > 0); 3412 scrollForwardButton.setEnabled( 3413 leadingTabIndex < tabCount-1 && 3414 viewSize.width-viewRect.x > viewRect.width); 3415 } 3416 } 3417 3418 /** 3419 * ActionListener for the scroll buttons. 3420 */ 3421 public void actionPerformed(ActionEvent e) { 3422 ActionMap map = tabPane.getActionMap(); 3423 3424 if (map != null) { 3425 String actionKey; 3426 3427 if (e.getSource() == scrollForwardButton) { 3428 actionKey = "scrollTabsForwardAction"; 3429 } 3430 else { 3431 actionKey = "scrollTabsBackwardAction"; 3432 } 3433 Action action = map.get(actionKey); 3434 3435 if (action != null && action.isEnabled()) { 3436 action.actionPerformed(new ActionEvent(tabPane, 3437 ActionEvent.ACTION_PERFORMED, null, e.getWhen(), 3438 e.getModifiers())); 3439 } 3440 } 3441 } 3442 3443 public String toString() { 3444 return "viewport.viewSize=" + viewport.getViewSize() + "\n" + 3445 "viewport.viewRectangle="+viewport.getViewRect()+"\n"+ 3446 "leadingTabIndex="+leadingTabIndex+"\n"+ 3447 "tabViewPosition=" + tabViewPosition; 3448 } 3449 3450 } 3451 3452 private class ScrollableTabViewport extends JViewport implements UIResource { 3453 public ScrollableTabViewport() { 3454 super(); 3455 setName("TabbedPane.scrollableViewport"); 3456 setScrollMode(SIMPLE_SCROLL_MODE); 3457 setOpaque(tabPane.isOpaque()); 3458 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3459 if (bgColor == null) { 3460 bgColor = tabPane.getBackground(); 3461 } 3462 setBackground(bgColor); 3463 } 3464 } 3465 3466 private class ScrollableTabPanel extends JPanel implements UIResource { 3467 public ScrollableTabPanel() { 3468 super(null); 3469 setOpaque(tabPane.isOpaque()); 3470 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3471 if (bgColor == null) { 3472 bgColor = tabPane.getBackground(); 3473 } 3474 setBackground(bgColor); 3475 } 3476 public void paintComponent(Graphics g) { 3477 super.paintComponent(g); 3478 BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(), 3479 tabPane.getSelectedIndex()); 3480 if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) { 3481 Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()]; 3482 g.translate(croppedRect.x, croppedRect.y); 3483 tabScroller.croppedEdge.paintComponent(g); 3484 g.translate(-croppedRect.x, -croppedRect.y); 3485 } 3486 } 3487 3488 public void doLayout() { 3489 if (getComponentCount() > 0) { 3490 Component child = getComponent(0); 3491 child.setBounds(0, 0, getWidth(), getHeight()); 3492 } 3493 } 3494 } 3495 3496 private class ScrollableTabButton extends BasicArrowButton implements UIResource, 3497 SwingConstants { 3498 public ScrollableTabButton(int direction) { 3499 super(direction, 3500 UIManager.getColor("TabbedPane.selected"), 3501 UIManager.getColor("TabbedPane.shadow"), 3502 UIManager.getColor("TabbedPane.darkShadow"), 3503 UIManager.getColor("TabbedPane.highlight")); 3504 } 3505 } 3506 3507 3508 // Controller: event listeners 3509 3510 private class Handler implements ChangeListener, ContainerListener, 3511 FocusListener, MouseListener, MouseMotionListener, 3512 PropertyChangeListener { 3513 // 3514 // PropertyChangeListener 3515 // 3516 public void propertyChange(PropertyChangeEvent e) { 3517 JTabbedPane pane = (JTabbedPane)e.getSource(); 3518 String name = e.getPropertyName(); 3519 boolean isScrollLayout = scrollableTabLayoutEnabled(); 3520 if (name == "mnemonicAt") { 3521 updateMnemonics(); 3522 pane.repaint(); 3523 } 3524 else if (name == "displayedMnemonicIndexAt") { 3525 pane.repaint(); 3526 } 3527 else if (name =="indexForTitle") { 3528 calculatedBaseline = false; 3529 Integer index = (Integer) e.getNewValue(); 3530 // remove the current index 3531 // to let updateHtmlViews() insert the correct one 3532 if (htmlViews != null) { 3533 htmlViews.removeElementAt(index); 3534 } 3535 updateHtmlViews(index); 3536 } else if (name == "tabLayoutPolicy") { 3537 BasicTabbedPaneUI.this.uninstallUI(pane); 3538 BasicTabbedPaneUI.this.installUI(pane); 3539 calculatedBaseline = false; 3540 } else if (name == "tabPlacement") { 3541 if (scrollableTabLayoutEnabled()) { 3542 tabScroller.createButtons(); 3543 } 3544 calculatedBaseline = false; 3545 } else if (name == "opaque" && isScrollLayout) { 3546 boolean newVal = ((Boolean)e.getNewValue()).booleanValue(); 3547 tabScroller.tabPanel.setOpaque(newVal); 3548 tabScroller.viewport.setOpaque(newVal); 3549 } else if (name == "background" && isScrollLayout) { 3550 Color newVal = (Color)e.getNewValue(); 3551 tabScroller.tabPanel.setBackground(newVal); 3552 tabScroller.viewport.setBackground(newVal); 3553 Color newColor = selectedColor == null ? newVal : selectedColor; 3554 tabScroller.scrollForwardButton.setBackground(newColor); 3555 tabScroller.scrollBackwardButton.setBackground(newColor); 3556 } else if (name == "indexForTabComponent") { 3557 if (tabContainer != null) { 3558 tabContainer.removeUnusedTabComponents(); 3559 } 3560 Component c = tabPane.getTabComponentAt( 3561 (Integer)e.getNewValue()); 3562 if (c != null) { 3563 if (tabContainer == null) { 3564 installTabContainer(); 3565 } else { 3566 tabContainer.add(c); 3567 } 3568 } 3569 tabPane.revalidate(); 3570 tabPane.repaint(); 3571 calculatedBaseline = false; 3572 } else if (name == "indexForNullComponent") { 3573 isRunsDirty = true; 3574 updateHtmlViews((Integer)e.getNewValue()); 3575 } else if (name == "font") { 3576 calculatedBaseline = false; 3577 } 3578 } 3579 3580 private void updateHtmlViews(int index) { 3581 String title = tabPane.getTitleAt(index); 3582 boolean isHTML = BasicHTML.isHTMLString(title); 3583 if (isHTML) { 3584 if (htmlViews==null) { // Initialize vector 3585 htmlViews = createHTMLVector(); 3586 } else { // Vector already exists 3587 View v = BasicHTML.createHTMLView(tabPane, title); 3588 htmlViews.insertElementAt(v, index); 3589 } 3590 } else { // Not HTML 3591 if (htmlViews!=null) { // Add placeholder 3592 htmlViews.insertElementAt(null, index); 3593 } // else nada! 3594 } 3595 updateMnemonics(); 3596 } 3597 3598 // 3599 // ChangeListener 3600 // 3601 public void stateChanged(ChangeEvent e) { 3602 JTabbedPane tabPane = (JTabbedPane)e.getSource(); 3603 tabPane.revalidate(); 3604 tabPane.repaint(); 3605 3606 setFocusIndex(tabPane.getSelectedIndex(), false); 3607 3608 if (scrollableTabLayoutEnabled()) { 3609 int index = tabPane.getSelectedIndex(); 3610 if (index < rects.length && index != -1) { 3611 tabScroller.tabPanel.scrollRectToVisible( 3612 (Rectangle)rects[index].clone()); 3613 } 3614 } 3615 } 3616 3617 // 3618 // MouseListener 3619 // 3620 public void mouseClicked(MouseEvent e) { 3621 } 3622 3623 public void mouseReleased(MouseEvent e) { 3624 } 3625 3626 public void mouseEntered(MouseEvent e) { 3627 setRolloverTab(e.getX(), e.getY()); 3628 } 3629 3630 public void mouseExited(MouseEvent e) { 3631 setRolloverTab(-1); 3632 } 3633 3634 public void mousePressed(MouseEvent e) { 3635 if (!tabPane.isEnabled()) { 3636 return; 3637 } 3638 int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); 3639 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) { 3640 if (tabIndex != tabPane.getSelectedIndex()) { 3641 // Clicking on unselected tab, change selection, do NOT 3642 // request focus. 3643 // This will trigger the focusIndex to change by way 3644 // of stateChanged. 3645 tabPane.setSelectedIndex(tabIndex); 3646 } 3647 else if (tabPane.isRequestFocusEnabled()) { 3648 // Clicking on selected tab, try and give the tabbedpane 3649 // focus. Repaint will occur in focusGained. 3650 tabPane.requestFocus(); 3651 } 3652 } 3653 } 3654 3655 // 3656 // MouseMotionListener 3657 // 3658 public void mouseDragged(MouseEvent e) { 3659 } 3660 3661 public void mouseMoved(MouseEvent e) { 3662 setRolloverTab(e.getX(), e.getY()); 3663 } 3664 3665 // 3666 // FocusListener 3667 // 3668 public void focusGained(FocusEvent e) { 3669 setFocusIndex(tabPane.getSelectedIndex(), true); 3670 } 3671 public void focusLost(FocusEvent e) { 3672 repaintTab(focusIndex); 3673 } 3674 3675 3676 // 3677 // ContainerListener 3678 // 3679 /* GES 2/3/99: 3680 The container listener code was added to support HTML 3681 rendering of tab titles. 3682 3683 Ideally, we would be able to listen for property changes 3684 when a tab is added or its text modified. At the moment 3685 there are no such events because the Beans spec doesn't 3686 allow 'indexed' property changes (i.e. tab 2's text changed 3687 from A to B). 3688 3689 In order to get around this, we listen for tabs to be added 3690 or removed by listening for the container events. we then 3691 queue up a runnable (so the component has a chance to complete 3692 the add) which checks the tab title of the new component to see 3693 if it requires HTML rendering. 3694 3695 The Views (one per tab title requiring HTML rendering) are 3696 stored in the htmlViews Vector, which is only allocated after 3697 the first time we run into an HTML tab. Note that this vector 3698 is kept in step with the number of pages, and nulls are added 3699 for those pages whose tab title do not require HTML rendering. 3700 3701 This makes it easy for the paint and layout code to tell 3702 whether to invoke the HTML engine without having to check 3703 the string during time-sensitive operations. 3704 3705 When we have added a way to listen for tab additions and 3706 changes to tab text, this code should be removed and 3707 replaced by something which uses that. */ 3708 3709 public void componentAdded(ContainerEvent e) { 3710 JTabbedPane tp = (JTabbedPane)e.getContainer(); 3711 Component child = e.getChild(); 3712 if (child instanceof UIResource) { 3713 return; 3714 } 3715 isRunsDirty = true; 3716 updateHtmlViews(tp.indexOfComponent(child)); 3717 } 3718 public void componentRemoved(ContainerEvent e) { 3719 JTabbedPane tp = (JTabbedPane)e.getContainer(); 3720 Component child = e.getChild(); 3721 if (child instanceof UIResource) { 3722 return; 3723 } 3724 3725 // NOTE 4/15/2002 (joutwate): 3726 // This fix is implemented using client properties since there is 3727 // currently no IndexPropertyChangeEvent. Once 3728 // IndexPropertyChangeEvents have been added this code should be 3729 // modified to use it. 3730 Integer indexObj = 3731 (Integer)tp.getClientProperty("__index_to_remove__"); 3732 if (indexObj != null) { 3733 int index = indexObj.intValue(); 3734 if (htmlViews != null && htmlViews.size() > index) { 3735 htmlViews.removeElementAt(index); 3736 } 3737 tp.putClientProperty("__index_to_remove__", null); 3738 } 3739 isRunsDirty = true; 3740 updateMnemonics(); 3741 3742 validateFocusIndex(); 3743 } 3744 } 3745 3746 /** 3747 * This class should be treated as a "protected" inner class. 3748 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3749 */ 3750 public class PropertyChangeHandler implements PropertyChangeListener { 3751 // NOTE: This class exists only for backward compatibility. All 3752 // its functionality has been moved into Handler. If you need to add 3753 // new functionality add it to the Handler, but make sure this 3754 // class calls into the Handler. 3755 public void propertyChange(PropertyChangeEvent e) { 3756 getHandler().propertyChange(e); 3757 } 3758 } 3759 3760 /** 3761 * This class should be treated as a "protected" inner class. 3762 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3763 */ 3764 public class TabSelectionHandler implements ChangeListener { 3765 // NOTE: This class exists only for backward compatibility. All 3766 // its functionality has been moved into Handler. If you need to add 3767 // new functionality add it to the Handler, but make sure this 3768 // class calls into the Handler. 3769 public void stateChanged(ChangeEvent e) { 3770 getHandler().stateChanged(e); 3771 } 3772 } 3773 3774 /** 3775 * This class should be treated as a "protected" inner class. 3776 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3777 */ 3778 public class MouseHandler extends MouseAdapter { 3779 // NOTE: This class exists only for backward compatibility. All 3780 // its functionality has been moved into Handler. If you need to add 3781 // new functionality add it to the Handler, but make sure this 3782 // class calls into the Handler. 3783 public void mousePressed(MouseEvent e) { 3784 getHandler().mousePressed(e); 3785 } 3786 } 3787 3788 /** 3789 * This class should be treated as a "protected" inner class. 3790 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3791 */ 3792 public class FocusHandler extends FocusAdapter { 3793 // NOTE: This class exists only for backward compatibility. All 3794 // its functionality has been moved into Handler. If you need to add 3795 // new functionality add it to the Handler, but make sure this 3796 // class calls into the Handler. 3797 public void focusGained(FocusEvent e) { 3798 getHandler().focusGained(e); 3799 } 3800 public void focusLost(FocusEvent e) { 3801 getHandler().focusLost(e); 3802 } 3803 } 3804 3805 private Vector<View> createHTMLVector() { 3806 Vector<View> htmlViews = new Vector<View>(); 3807 int count = tabPane.getTabCount(); 3808 if (count>0) { 3809 for (int i=0 ; i<count; i++) { 3810 String title = tabPane.getTitleAt(i); 3811 if (BasicHTML.isHTMLString(title)) { 3812 htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title)); 3813 } else { 3814 htmlViews.addElement(null); 3815 } 3816 } 3817 } 3818 return htmlViews; 3819 } 3820 3821 private class TabContainer extends JPanel implements UIResource { 3822 private boolean notifyTabbedPane = true; 3823 3824 public TabContainer() { 3825 super(null); 3826 setOpaque(false); 3827 } 3828 3829 public void remove(Component comp) { 3830 int index = tabPane.indexOfTabComponent(comp); 3831 super.remove(comp); 3832 if (notifyTabbedPane && index != -1) { 3833 tabPane.setTabComponentAt(index, null); 3834 } 3835 } 3836 3837 private void removeUnusedTabComponents() { 3838 for (Component c : getComponents()) { 3839 if (!(c instanceof UIResource)) { 3840 int index = tabPane.indexOfTabComponent(c); 3841 if (index == -1) { 3842 super.remove(c); 3843 } 3844 } 3845 } 3846 } 3847 3848 public boolean isOptimizedDrawingEnabled() { 3849 return tabScroller != null && !tabScroller.croppedEdge.isParamsSet(); 3850 } 3851 3852 public void doLayout() { 3853 // We layout tabComponents in JTabbedPane's layout manager 3854 // and use this method as a hook for repainting tabs 3855 // to update tabs area e.g. when the size of tabComponent was changed 3856 if (scrollableTabLayoutEnabled()) { 3857 tabScroller.tabPanel.repaint(); 3858 tabScroller.updateView(); 3859 } else { 3860 tabPane.repaint(getBounds()); 3861 } 3862 } 3863 } 3864 3865 private class CroppedEdge extends JPanel implements UIResource { 3866 private Shape shape; 3867 private int tabIndex; 3868 private int cropline; 3869 private int cropx, cropy; 3870 3871 public CroppedEdge() { 3872 setOpaque(false); 3873 } 3874 3875 public void setParams(int tabIndex, int cropline, int cropx, int cropy) { 3876 this.tabIndex = tabIndex; 3877 this.cropline = cropline; 3878 this.cropx = cropx; 3879 this.cropy = cropy; 3880 Rectangle tabRect = rects[tabIndex]; 3881 setBounds(tabRect); 3882 shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline); 3883 if (getParent() == null && tabContainer != null) { 3884 tabContainer.add(this, 0); 3885 } 3886 } 3887 3888 public void resetParams() { 3889 shape = null; 3890 if (getParent() == tabContainer && tabContainer != null) { 3891 tabContainer.remove(this); 3892 } 3893 } 3894 3895 public boolean isParamsSet() { 3896 return shape != null; 3897 } 3898 3899 public int getTabIndex() { 3900 return tabIndex; 3901 } 3902 3903 public int getCropline() { 3904 return cropline; 3905 } 3906 3907 public int getCroppedSideWidth() { 3908 return 3; 3909 } 3910 3911 private Color getBgColor() { 3912 Component parent = tabPane.getParent(); 3913 if (parent != null) { 3914 Color bg = parent.getBackground(); 3915 if (bg != null) { 3916 return bg; 3917 } 3918 } 3919 return UIManager.getColor("control"); 3920 } 3921 3922 protected void paintComponent(Graphics g) { 3923 super.paintComponent(g); 3924 if (isParamsSet() && g instanceof Graphics2D) { 3925 Graphics2D g2 = (Graphics2D) g; 3926 g2.clipRect(0, 0, getWidth(), getHeight()); 3927 g2.setColor(getBgColor()); 3928 g2.translate(cropx, cropy); 3929 g2.fill(shape); 3930 paintCroppedTabEdge(g); 3931 g2.translate(-cropx, -cropy); 3932 } 3933 } 3934 } 3935 }