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