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 @SuppressWarnings("deprecation") 2399 public void layoutContainer(Container parent) { 2400 /* Some of the code in this method deals with changing the 2401 * visibility of components to hide and show the contents for the 2402 * selected tab. This is older code that has since been duplicated 2403 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2404 * changes to happen sooner (see the note there). This code remains 2405 * for backward compatibility as there are some cases, such as 2406 * subclasses that don't fireStateChanged() where it may be used. 2407 * Any changes here need to be kept in synch with 2408 * JTabbedPane.fireStateChanged(). 2409 */ 2410 2411 setRolloverTab(-1); 2412 2413 int tabPlacement = tabPane.getTabPlacement(); 2414 Insets insets = tabPane.getInsets(); 2415 int selectedIndex = tabPane.getSelectedIndex(); 2416 Component visibleComponent = getVisibleComponent(); 2417 2418 calculateLayoutInfo(); 2419 2420 Component selectedComponent = null; 2421 if (selectedIndex < 0) { 2422 if (visibleComponent != null) { 2423 // The last tab was removed, so remove the component 2424 setVisibleComponent(null); 2425 } 2426 } else { 2427 selectedComponent = tabPane.getComponentAt(selectedIndex); 2428 } 2429 int cx, cy, cw, ch; 2430 int totalTabWidth = 0; 2431 int totalTabHeight = 0; 2432 Insets contentInsets = getContentBorderInsets(tabPlacement); 2433 2434 boolean shouldChangeFocus = false; 2435 2436 // In order to allow programs to use a single component 2437 // as the display for multiple tabs, we will not change 2438 // the visible compnent if the currently selected tab 2439 // has a null component. This is a bit dicey, as we don't 2440 // explicitly state we support this in the spec, but since 2441 // programs are now depending on this, we're making it work. 2442 // 2443 if(selectedComponent != null) { 2444 if(selectedComponent != visibleComponent && 2445 visibleComponent != null) { 2446 if(SwingUtilities.findFocusOwner(visibleComponent) != null) { 2447 shouldChangeFocus = true; 2448 } 2449 } 2450 setVisibleComponent(selectedComponent); 2451 } 2452 2453 Rectangle bounds = tabPane.getBounds(); 2454 int numChildren = tabPane.getComponentCount(); 2455 2456 if(numChildren > 0) { 2457 2458 switch(tabPlacement) { 2459 case LEFT: 2460 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2461 cx = insets.left + totalTabWidth + contentInsets.left; 2462 cy = insets.top + contentInsets.top; 2463 break; 2464 case RIGHT: 2465 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2466 cx = insets.left + contentInsets.left; 2467 cy = insets.top + contentInsets.top; 2468 break; 2469 case BOTTOM: 2470 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2471 cx = insets.left + contentInsets.left; 2472 cy = insets.top + contentInsets.top; 2473 break; 2474 case TOP: 2475 default: 2476 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2477 cx = insets.left + contentInsets.left; 2478 cy = insets.top + totalTabHeight + contentInsets.top; 2479 } 2480 2481 cw = bounds.width - totalTabWidth - 2482 insets.left - insets.right - 2483 contentInsets.left - contentInsets.right; 2484 ch = bounds.height - totalTabHeight - 2485 insets.top - insets.bottom - 2486 contentInsets.top - contentInsets.bottom; 2487 2488 for(int i = 0; i < numChildren; i++) { 2489 Component child = tabPane.getComponent(i); 2490 if(child == tabContainer) { 2491 2492 int tabContainerWidth = totalTabWidth == 0 ? bounds.width : 2493 totalTabWidth + insets.left + insets.right + 2494 contentInsets.left + contentInsets.right; 2495 int tabContainerHeight = totalTabHeight == 0 ? bounds.height : 2496 totalTabHeight + insets.top + insets.bottom + 2497 contentInsets.top + contentInsets.bottom; 2498 2499 int tabContainerX = 0; 2500 int tabContainerY = 0; 2501 if(tabPlacement == BOTTOM) { 2502 tabContainerY = bounds.height - tabContainerHeight; 2503 } else if(tabPlacement == RIGHT) { 2504 tabContainerX = bounds.width - tabContainerWidth; 2505 } 2506 child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight); 2507 } else { 2508 child.setBounds(cx, cy, cw, ch); 2509 } 2510 } 2511 } 2512 layoutTabComponents(); 2513 if(shouldChangeFocus) { 2514 if(!requestFocusForVisibleComponent()) { 2515 tabPane.requestFocus(); 2516 } 2517 } 2518 } 2519 2520 public void calculateLayoutInfo() { 2521 int tabCount = tabPane.getTabCount(); 2522 assureRectsCreated(tabCount); 2523 calculateTabRects(tabPane.getTabPlacement(), tabCount); 2524 isRunsDirty = false; 2525 } 2526 2527 private void layoutTabComponents() { 2528 if (tabContainer == null) { 2529 return; 2530 } 2531 Rectangle rect = new Rectangle(); 2532 Point delta = new Point(-tabContainer.getX(), -tabContainer.getY()); 2533 if (scrollableTabLayoutEnabled()) { 2534 translatePointToTabPanel(0, 0, delta); 2535 } 2536 for (int i = 0; i < tabPane.getTabCount(); i++) { 2537 Component c = tabPane.getTabComponentAt(i); 2538 if (c == null) { 2539 continue; 2540 } 2541 getTabBounds(i, rect); 2542 Dimension preferredSize = c.getPreferredSize(); 2543 Insets insets = getTabInsets(tabPane.getTabPlacement(), i); 2544 int outerX = rect.x + insets.left + delta.x; 2545 int outerY = rect.y + insets.top + delta.y; 2546 int outerWidth = rect.width - insets.left - insets.right; 2547 int outerHeight = rect.height - insets.top - insets.bottom; 2548 //centralize component 2549 int x = outerX + (outerWidth - preferredSize.width) / 2; 2550 int y = outerY + (outerHeight - preferredSize.height) / 2; 2551 int tabPlacement = tabPane.getTabPlacement(); 2552 boolean isSeleceted = i == tabPane.getSelectedIndex(); 2553 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted), 2554 y + getTabLabelShiftY(tabPlacement, i, isSeleceted), 2555 preferredSize.width, preferredSize.height); 2556 } 2557 } 2558 2559 protected void calculateTabRects(int tabPlacement, int tabCount) { 2560 FontMetrics metrics = getFontMetrics(); 2561 Dimension size = tabPane.getSize(); 2562 Insets insets = tabPane.getInsets(); 2563 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 2564 int fontHeight = metrics.getHeight(); 2565 int selectedIndex = tabPane.getSelectedIndex(); 2566 int tabRunOverlay; 2567 int i, j; 2568 int x, y; 2569 int returnAt; 2570 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2571 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 2572 2573 // 2574 // Calculate bounds within which a tab run must fit 2575 // 2576 switch(tabPlacement) { 2577 case LEFT: 2578 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2579 x = insets.left + tabAreaInsets.left; 2580 y = insets.top + tabAreaInsets.top; 2581 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2582 break; 2583 case RIGHT: 2584 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2585 x = size.width - insets.right - tabAreaInsets.right - maxTabWidth; 2586 y = insets.top + tabAreaInsets.top; 2587 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2588 break; 2589 case BOTTOM: 2590 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2591 x = insets.left + tabAreaInsets.left; 2592 y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight; 2593 returnAt = size.width - (insets.right + tabAreaInsets.right); 2594 break; 2595 case TOP: 2596 default: 2597 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2598 x = insets.left + tabAreaInsets.left; 2599 y = insets.top + tabAreaInsets.top; 2600 returnAt = size.width - (insets.right + tabAreaInsets.right); 2601 break; 2602 } 2603 2604 tabRunOverlay = getTabRunOverlay(tabPlacement); 2605 2606 runCount = 0; 2607 selectedRun = -1; 2608 2609 if (tabCount == 0) { 2610 return; 2611 } 2612 2613 // Run through tabs and partition them into runs 2614 Rectangle rect; 2615 for (i = 0; i < tabCount; i++) { 2616 rect = rects[i]; 2617 2618 if (!verticalTabRuns) { 2619 // Tabs on TOP or BOTTOM.... 2620 if (i > 0) { 2621 rect.x = rects[i-1].x + rects[i-1].width; 2622 } else { 2623 tabRuns[0] = 0; 2624 runCount = 1; 2625 maxTabWidth = 0; 2626 rect.x = x; 2627 } 2628 rect.width = calculateTabWidth(tabPlacement, i, metrics); 2629 maxTabWidth = Math.max(maxTabWidth, rect.width); 2630 2631 // Never move a TAB down a run if it is in the first column. 2632 // Even if there isn't enough room, moving it to a fresh 2633 // line won't help. 2634 if (rect.x != x && rect.x + rect.width > returnAt) { 2635 if (runCount > tabRuns.length - 1) { 2636 expandTabRunsArray(); 2637 } 2638 tabRuns[runCount] = i; 2639 runCount++; 2640 rect.x = x; 2641 } 2642 // Initialize y position in case there's just one run 2643 rect.y = y; 2644 rect.height = maxTabHeight/* - 2*/; 2645 2646 } else { 2647 // Tabs on LEFT or RIGHT... 2648 if (i > 0) { 2649 rect.y = rects[i-1].y + rects[i-1].height; 2650 } else { 2651 tabRuns[0] = 0; 2652 runCount = 1; 2653 maxTabHeight = 0; 2654 rect.y = y; 2655 } 2656 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 2657 maxTabHeight = Math.max(maxTabHeight, rect.height); 2658 2659 // Never move a TAB over a run if it is in the first run. 2660 // Even if there isn't enough room, moving it to a fresh 2661 // column won't help. 2662 if (rect.y != y && rect.y + rect.height > returnAt) { 2663 if (runCount > tabRuns.length - 1) { 2664 expandTabRunsArray(); 2665 } 2666 tabRuns[runCount] = i; 2667 runCount++; 2668 rect.y = y; 2669 } 2670 // Initialize x position in case there's just one column 2671 rect.x = x; 2672 rect.width = maxTabWidth/* - 2*/; 2673 2674 } 2675 if (i == selectedIndex) { 2676 selectedRun = runCount - 1; 2677 } 2678 } 2679 2680 if (runCount > 1) { 2681 // Re-distribute tabs in case last run has leftover space 2682 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt); 2683 2684 selectedRun = getRunForTab(tabCount, selectedIndex); 2685 2686 // Rotate run array so that selected run is first 2687 if (shouldRotateTabRuns(tabPlacement)) { 2688 rotateTabRuns(tabPlacement, selectedRun); 2689 } 2690 } 2691 2692 // Step through runs from back to front to calculate 2693 // tab y locations and to pad runs appropriately 2694 for (i = runCount - 1; i >= 0; i--) { 2695 int start = tabRuns[i]; 2696 int next = tabRuns[i == (runCount - 1)? 0 : i + 1]; 2697 int end = (next != 0? next - 1 : tabCount - 1); 2698 if (!verticalTabRuns) { 2699 for (j = start; j <= end; j++) { 2700 rect = rects[j]; 2701 rect.y = y; 2702 rect.x += getTabRunIndent(tabPlacement, i); 2703 } 2704 if (shouldPadTabRun(tabPlacement, i)) { 2705 padTabRun(tabPlacement, start, end, returnAt); 2706 } 2707 if (tabPlacement == BOTTOM) { 2708 y -= (maxTabHeight - tabRunOverlay); 2709 } else { 2710 y += (maxTabHeight - tabRunOverlay); 2711 } 2712 } else { 2713 for (j = start; j <= end; j++) { 2714 rect = rects[j]; 2715 rect.x = x; 2716 rect.y += getTabRunIndent(tabPlacement, i); 2717 } 2718 if (shouldPadTabRun(tabPlacement, i)) { 2719 padTabRun(tabPlacement, start, end, returnAt); 2720 } 2721 if (tabPlacement == RIGHT) { 2722 x -= (maxTabWidth - tabRunOverlay); 2723 } else { 2724 x += (maxTabWidth - tabRunOverlay); 2725 } 2726 } 2727 } 2728 2729 // Pad the selected tab so that it appears raised in front 2730 padSelectedTab(tabPlacement, selectedIndex); 2731 2732 // if right to left and tab placement on the top or 2733 // the bottom, flip x positions and adjust by widths 2734 if (!leftToRight && !verticalTabRuns) { 2735 int rightMargin = size.width 2736 - (insets.right + tabAreaInsets.right); 2737 for (i = 0; i < tabCount; i++) { 2738 rects[i].x = rightMargin - rects[i].x - rects[i].width; 2739 } 2740 } 2741 } 2742 2743 2744 /* 2745 * Rotates the run-index array so that the selected run is run[0] 2746 */ 2747 protected void rotateTabRuns(int tabPlacement, int selectedRun) { 2748 for (int i = 0; i < selectedRun; i++) { 2749 int save = tabRuns[0]; 2750 for (int j = 1; j < runCount; j++) { 2751 tabRuns[j - 1] = tabRuns[j]; 2752 } 2753 tabRuns[runCount-1] = save; 2754 } 2755 } 2756 2757 protected void normalizeTabRuns(int tabPlacement, int tabCount, 2758 int start, int max) { 2759 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2760 int run = runCount - 1; 2761 boolean keepAdjusting = true; 2762 double weight = 1.25; 2763 2764 // At this point the tab runs are packed to fit as many 2765 // tabs as possible, which can leave the last run with a lot 2766 // of extra space (resulting in very fat tabs on the last run). 2767 // So we'll attempt to distribute this extra space more evenly 2768 // across the runs in order to make the runs look more consistent. 2769 // 2770 // Starting with the last run, determine whether the last tab in 2771 // the previous run would fit (generously) in this run; if so, 2772 // move tab to current run and shift tabs accordingly. Cycle 2773 // through remaining runs using the same algorithm. 2774 // 2775 while (keepAdjusting) { 2776 int last = lastTabInRun(tabCount, run); 2777 int prevLast = lastTabInRun(tabCount, run-1); 2778 int end; 2779 int prevLastLen; 2780 2781 if (!verticalTabRuns) { 2782 end = rects[last].x + rects[last].width; 2783 prevLastLen = (int)(maxTabWidth*weight); 2784 } else { 2785 end = rects[last].y + rects[last].height; 2786 prevLastLen = (int)(maxTabHeight*weight*2); 2787 } 2788 2789 // Check if the run has enough extra space to fit the last tab 2790 // from the previous row... 2791 if (max - end > prevLastLen) { 2792 2793 // Insert tab from previous row and shift rest over 2794 tabRuns[run] = prevLast; 2795 if (!verticalTabRuns) { 2796 rects[prevLast].x = start; 2797 } else { 2798 rects[prevLast].y = start; 2799 } 2800 for (int i = prevLast+1; i <= last; i++) { 2801 if (!verticalTabRuns) { 2802 rects[i].x = rects[i-1].x + rects[i-1].width; 2803 } else { 2804 rects[i].y = rects[i-1].y + rects[i-1].height; 2805 } 2806 } 2807 2808 } else if (run == runCount - 1) { 2809 // no more room left in last run, so we're done! 2810 keepAdjusting = false; 2811 } 2812 if (run - 1 > 0) { 2813 // check previous run next... 2814 run -= 1; 2815 } else { 2816 // check last run again...but require a higher ratio 2817 // of extraspace-to-tabsize because we don't want to 2818 // end up with too many tabs on the last run! 2819 run = runCount - 1; 2820 weight += .25; 2821 } 2822 } 2823 } 2824 2825 protected void padTabRun(int tabPlacement, int start, int end, int max) { 2826 Rectangle lastRect = rects[end]; 2827 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 2828 int runWidth = (lastRect.x + lastRect.width) - rects[start].x; 2829 int deltaWidth = max - (lastRect.x + lastRect.width); 2830 float factor = (float)deltaWidth / (float)runWidth; 2831 2832 for (int j = start; j <= end; j++) { 2833 Rectangle pastRect = rects[j]; 2834 if (j > start) { 2835 pastRect.x = rects[j-1].x + rects[j-1].width; 2836 } 2837 pastRect.width += Math.round((float)pastRect.width * factor); 2838 } 2839 lastRect.width = max - lastRect.x; 2840 } else { 2841 int runHeight = (lastRect.y + lastRect.height) - rects[start].y; 2842 int deltaHeight = max - (lastRect.y + lastRect.height); 2843 float factor = (float)deltaHeight / (float)runHeight; 2844 2845 for (int j = start; j <= end; j++) { 2846 Rectangle pastRect = rects[j]; 2847 if (j > start) { 2848 pastRect.y = rects[j-1].y + rects[j-1].height; 2849 } 2850 pastRect.height += Math.round((float)pastRect.height * factor); 2851 } 2852 lastRect.height = max - lastRect.y; 2853 } 2854 } 2855 2856 protected void padSelectedTab(int tabPlacement, int selectedIndex) { 2857 2858 if (selectedIndex >= 0) { 2859 Rectangle selRect = rects[selectedIndex]; 2860 Insets padInsets = getSelectedTabPadInsets(tabPlacement); 2861 selRect.x -= padInsets.left; 2862 selRect.width += (padInsets.left + padInsets.right); 2863 selRect.y -= padInsets.top; 2864 selRect.height += (padInsets.top + padInsets.bottom); 2865 2866 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT 2867 // do not expand selected tab more then necessary 2868 Dimension size = tabPane.getSize(); 2869 Insets insets = tabPane.getInsets(); 2870 2871 if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) { 2872 int top = insets.top - selRect.y; 2873 if (top > 0) { 2874 selRect.y += top; 2875 selRect.height -= top; 2876 } 2877 int bottom = (selRect.y + selRect.height) + insets.bottom - size.height; 2878 if (bottom > 0) { 2879 selRect.height -= bottom; 2880 } 2881 } else { 2882 int left = insets.left - selRect.x; 2883 if (left > 0) { 2884 selRect.x += left; 2885 selRect.width -= left; 2886 } 2887 int right = (selRect.x + selRect.width) + insets.right - size.width; 2888 if (right > 0) { 2889 selRect.width -= right; 2890 } 2891 } 2892 } 2893 } 2894 } 2895 } 2896 2897 private class TabbedPaneScrollLayout extends TabbedPaneLayout { 2898 2899 protected int preferredTabAreaHeight(int tabPlacement, int width) { 2900 return calculateMaxTabHeight(tabPlacement); 2901 } 2902 2903 protected int preferredTabAreaWidth(int tabPlacement, int height) { 2904 return calculateMaxTabWidth(tabPlacement); 2905 } 2906 2907 @SuppressWarnings("deprecation") 2908 public void layoutContainer(Container parent) { 2909 /* Some of the code in this method deals with changing the 2910 * visibility of components to hide and show the contents for the 2911 * selected tab. This is older code that has since been duplicated 2912 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2913 * changes to happen sooner (see the note there). This code remains 2914 * for backward compatibility as there are some cases, such as 2915 * subclasses that don't fireStateChanged() where it may be used. 2916 * Any changes here need to be kept in synch with 2917 * JTabbedPane.fireStateChanged(). 2918 */ 2919 2920 setRolloverTab(-1); 2921 2922 int tabPlacement = tabPane.getTabPlacement(); 2923 int tabCount = tabPane.getTabCount(); 2924 Insets insets = tabPane.getInsets(); 2925 int selectedIndex = tabPane.getSelectedIndex(); 2926 Component visibleComponent = getVisibleComponent(); 2927 2928 calculateLayoutInfo(); 2929 2930 Component selectedComponent = null; 2931 if (selectedIndex < 0) { 2932 if (visibleComponent != null) { 2933 // The last tab was removed, so remove the component 2934 setVisibleComponent(null); 2935 } 2936 } else { 2937 selectedComponent = tabPane.getComponentAt(selectedIndex); 2938 } 2939 2940 if (tabPane.getTabCount() == 0) { 2941 tabScroller.croppedEdge.resetParams(); 2942 tabScroller.scrollForwardButton.setVisible(false); 2943 tabScroller.scrollBackwardButton.setVisible(false); 2944 return; 2945 } 2946 2947 boolean shouldChangeFocus = false; 2948 2949 // In order to allow programs to use a single component 2950 // as the display for multiple tabs, we will not change 2951 // the visible compnent if the currently selected tab 2952 // has a null component. This is a bit dicey, as we don't 2953 // explicitly state we support this in the spec, but since 2954 // programs are now depending on this, we're making it work. 2955 // 2956 if(selectedComponent != null) { 2957 if(selectedComponent != visibleComponent && 2958 visibleComponent != null) { 2959 if(SwingUtilities.findFocusOwner(visibleComponent) != null) { 2960 shouldChangeFocus = true; 2961 } 2962 } 2963 setVisibleComponent(selectedComponent); 2964 } 2965 int tx, ty, tw, th; // tab area bounds 2966 int cx, cy, cw, ch; // content area bounds 2967 Insets contentInsets = getContentBorderInsets(tabPlacement); 2968 Rectangle bounds = tabPane.getBounds(); 2969 int numChildren = tabPane.getComponentCount(); 2970 2971 if(numChildren > 0) { 2972 switch(tabPlacement) { 2973 case LEFT: 2974 // calculate tab area bounds 2975 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2976 th = bounds.height - insets.top - insets.bottom; 2977 tx = insets.left; 2978 ty = insets.top; 2979 2980 // calculate content area bounds 2981 cx = tx + tw + contentInsets.left; 2982 cy = ty + contentInsets.top; 2983 cw = bounds.width - insets.left - insets.right - tw - 2984 contentInsets.left - contentInsets.right; 2985 ch = bounds.height - insets.top - insets.bottom - 2986 contentInsets.top - contentInsets.bottom; 2987 break; 2988 case RIGHT: 2989 // calculate tab area bounds 2990 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2991 th = bounds.height - insets.top - insets.bottom; 2992 tx = bounds.width - insets.right - tw; 2993 ty = insets.top; 2994 2995 // calculate content area bounds 2996 cx = insets.left + contentInsets.left; 2997 cy = insets.top + contentInsets.top; 2998 cw = bounds.width - insets.left - insets.right - tw - 2999 contentInsets.left - contentInsets.right; 3000 ch = bounds.height - insets.top - insets.bottom - 3001 contentInsets.top - contentInsets.bottom; 3002 break; 3003 case BOTTOM: 3004 // calculate tab area bounds 3005 tw = bounds.width - insets.left - insets.right; 3006 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 3007 tx = insets.left; 3008 ty = bounds.height - insets.bottom - th; 3009 3010 // calculate content area bounds 3011 cx = insets.left + contentInsets.left; 3012 cy = insets.top + contentInsets.top; 3013 cw = bounds.width - insets.left - insets.right - 3014 contentInsets.left - contentInsets.right; 3015 ch = bounds.height - insets.top - insets.bottom - th - 3016 contentInsets.top - contentInsets.bottom; 3017 break; 3018 case TOP: 3019 default: 3020 // calculate tab area bounds 3021 tw = bounds.width - insets.left - insets.right; 3022 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 3023 tx = insets.left; 3024 ty = insets.top; 3025 3026 // calculate content area bounds 3027 cx = tx + contentInsets.left; 3028 cy = ty + th + contentInsets.top; 3029 cw = bounds.width - insets.left - insets.right - 3030 contentInsets.left - contentInsets.right; 3031 ch = bounds.height - insets.top - insets.bottom - th - 3032 contentInsets.top - contentInsets.bottom; 3033 } 3034 3035 for(int i = 0; i < numChildren; i++) { 3036 Component child = tabPane.getComponent(i); 3037 3038 if(tabScroller != null && child == tabScroller.viewport) { 3039 JViewport viewport = (JViewport) child; 3040 Rectangle viewRect = viewport.getViewRect(); 3041 int vw = tw; 3042 int vh = th; 3043 Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize(); 3044 switch(tabPlacement) { 3045 case LEFT: 3046 case RIGHT: 3047 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 3048 if(totalTabHeight > th) { 3049 // Allow space for scrollbuttons 3050 vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0; 3051 if(totalTabHeight - viewRect.y <= vh) { 3052 // Scrolled to the end, so ensure the viewport size is 3053 // such that the scroll offset aligns with a tab 3054 vh = totalTabHeight - viewRect.y; 3055 } 3056 } 3057 break; 3058 case BOTTOM: 3059 case TOP: 3060 default: 3061 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 3062 if(totalTabWidth > tw) { 3063 // Need to allow space for scrollbuttons 3064 vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0; 3065 if(totalTabWidth - viewRect.x <= vw) { 3066 // Scrolled to the end, so ensure the viewport size is 3067 // such that the scroll offset aligns with a tab 3068 vw = totalTabWidth - viewRect.x; 3069 } 3070 } 3071 } 3072 child.setBounds(tx, ty, vw, vh); 3073 3074 } else if(tabScroller != null && 3075 (child == tabScroller.scrollForwardButton || 3076 child == tabScroller.scrollBackwardButton)) { 3077 Component scrollbutton = child; 3078 Dimension bsize = scrollbutton.getPreferredSize(); 3079 int bx = 0; 3080 int by = 0; 3081 int bw = bsize.width; 3082 int bh = bsize.height; 3083 boolean visible = false; 3084 3085 switch(tabPlacement) { 3086 case LEFT: 3087 case RIGHT: 3088 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 3089 if(totalTabHeight > th) { 3090 visible = true; 3091 bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx); 3092 by = (child == tabScroller.scrollForwardButton) ? 3093 bounds.height - insets.bottom - bsize.height : 3094 bounds.height - insets.bottom - 2 * bsize.height; 3095 } 3096 break; 3097 3098 case BOTTOM: 3099 case TOP: 3100 default: 3101 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 3102 3103 if(totalTabWidth > tw) { 3104 visible = true; 3105 bx = (child == tabScroller.scrollForwardButton) ? 3106 bounds.width - insets.left - bsize.width : 3107 bounds.width - insets.left - 2 * bsize.width; 3108 by = (tabPlacement == TOP ? ty + th - bsize.height : ty); 3109 } 3110 } 3111 child.setVisible(visible); 3112 if(visible) { 3113 child.setBounds(bx, by, bw, bh); 3114 } 3115 3116 } else { 3117 // All content children... 3118 child.setBounds(cx, cy, cw, ch); 3119 } 3120 } 3121 super.layoutTabComponents(); 3122 layoutCroppedEdge(); 3123 if(shouldChangeFocus) { 3124 if(!requestFocusForVisibleComponent()) { 3125 tabPane.requestFocus(); 3126 } 3127 } 3128 } 3129 } 3130 3131 private void layoutCroppedEdge() { 3132 tabScroller.croppedEdge.resetParams(); 3133 Rectangle viewRect = tabScroller.viewport.getViewRect(); 3134 int cropline; 3135 for (int i = 0; i < rects.length; i++) { 3136 Rectangle tabRect = rects[i]; 3137 switch (tabPane.getTabPlacement()) { 3138 case LEFT: 3139 case RIGHT: 3140 cropline = viewRect.y + viewRect.height; 3141 if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) { 3142 tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1, 3143 -currentTabAreaInsets.left, 0); 3144 } 3145 break; 3146 case TOP: 3147 case BOTTOM: 3148 default: 3149 cropline = viewRect.x + viewRect.width; 3150 if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) { 3151 tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1, 3152 0, -currentTabAreaInsets.top); 3153 } 3154 } 3155 } 3156 } 3157 3158 protected void calculateTabRects(int tabPlacement, int tabCount) { 3159 FontMetrics metrics = getFontMetrics(); 3160 Dimension size = tabPane.getSize(); 3161 Insets insets = tabPane.getInsets(); 3162 Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 3163 int fontHeight = metrics.getHeight(); 3164 int selectedIndex = tabPane.getSelectedIndex(); 3165 int i; 3166 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 3167 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane); 3168 int x = tabAreaInsets.left; 3169 int y = tabAreaInsets.top; 3170 int totalWidth = 0; 3171 int totalHeight = 0; 3172 3173 // 3174 // Calculate bounds within which a tab run must fit 3175 // 3176 switch(tabPlacement) { 3177 case LEFT: 3178 case RIGHT: 3179 maxTabWidth = calculateMaxTabWidth(tabPlacement); 3180 break; 3181 case BOTTOM: 3182 case TOP: 3183 default: 3184 maxTabHeight = calculateMaxTabHeight(tabPlacement); 3185 } 3186 3187 runCount = 0; 3188 selectedRun = -1; 3189 3190 if (tabCount == 0) { 3191 return; 3192 } 3193 3194 selectedRun = 0; 3195 runCount = 1; 3196 3197 // Run through tabs and lay them out in a single run 3198 Rectangle rect; 3199 for (i = 0; i < tabCount; i++) { 3200 rect = rects[i]; 3201 3202 if (!verticalTabRuns) { 3203 // Tabs on TOP or BOTTOM.... 3204 if (i > 0) { 3205 rect.x = rects[i-1].x + rects[i-1].width; 3206 } else { 3207 tabRuns[0] = 0; 3208 maxTabWidth = 0; 3209 totalHeight += maxTabHeight; 3210 rect.x = x; 3211 } 3212 rect.width = calculateTabWidth(tabPlacement, i, metrics); 3213 totalWidth = rect.x + rect.width; 3214 maxTabWidth = Math.max(maxTabWidth, rect.width); 3215 3216 rect.y = y; 3217 rect.height = maxTabHeight/* - 2*/; 3218 3219 } else { 3220 // Tabs on LEFT or RIGHT... 3221 if (i > 0) { 3222 rect.y = rects[i-1].y + rects[i-1].height; 3223 } else { 3224 tabRuns[0] = 0; 3225 maxTabHeight = 0; 3226 totalWidth = maxTabWidth; 3227 rect.y = y; 3228 } 3229 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 3230 totalHeight = rect.y + rect.height; 3231 maxTabHeight = Math.max(maxTabHeight, rect.height); 3232 3233 rect.x = x; 3234 rect.width = maxTabWidth/* - 2*/; 3235 3236 } 3237 } 3238 3239 if (tabsOverlapBorder) { 3240 // Pad the selected tab so that it appears raised in front 3241 padSelectedTab(tabPlacement, selectedIndex); 3242 } 3243 3244 // if right to left and tab placement on the top or 3245 // the bottom, flip x positions and adjust by widths 3246 if (!leftToRight && !verticalTabRuns) { 3247 int rightMargin = size.width 3248 - (insets.right + tabAreaInsets.right); 3249 for (i = 0; i < tabCount; i++) { 3250 rects[i].x = rightMargin - rects[i].x - rects[i].width; 3251 } 3252 } 3253 tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight)); 3254 tabScroller.tabPanel.invalidate(); 3255 } 3256 } 3257 3258 private class ScrollableTabSupport implements ActionListener, 3259 ChangeListener { 3260 public ScrollableTabViewport viewport; 3261 public ScrollableTabPanel tabPanel; 3262 public JButton scrollForwardButton; 3263 public JButton scrollBackwardButton; 3264 public CroppedEdge croppedEdge; 3265 public int leadingTabIndex; 3266 3267 private Point tabViewPosition = new Point(0,0); 3268 3269 ScrollableTabSupport(int tabPlacement) { 3270 viewport = new ScrollableTabViewport(); 3271 tabPanel = new ScrollableTabPanel(); 3272 viewport.setView(tabPanel); 3273 viewport.addChangeListener(this); 3274 croppedEdge = new CroppedEdge(); 3275 createButtons(); 3276 } 3277 3278 /** 3279 * Recreates the scroll buttons and adds them to the TabbedPane. 3280 */ 3281 void createButtons() { 3282 if (scrollForwardButton != null) { 3283 tabPane.remove(scrollForwardButton); 3284 scrollForwardButton.removeActionListener(this); 3285 tabPane.remove(scrollBackwardButton); 3286 scrollBackwardButton.removeActionListener(this); 3287 } 3288 int tabPlacement = tabPane.getTabPlacement(); 3289 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3290 scrollForwardButton = createScrollButton(EAST); 3291 scrollBackwardButton = createScrollButton(WEST); 3292 3293 } else { // tabPlacement = LEFT || RIGHT 3294 scrollForwardButton = createScrollButton(SOUTH); 3295 scrollBackwardButton = createScrollButton(NORTH); 3296 } 3297 scrollForwardButton.addActionListener(this); 3298 scrollBackwardButton.addActionListener(this); 3299 tabPane.add(scrollForwardButton); 3300 tabPane.add(scrollBackwardButton); 3301 } 3302 3303 public void scrollForward(int tabPlacement) { 3304 Dimension viewSize = viewport.getViewSize(); 3305 Rectangle viewRect = viewport.getViewRect(); 3306 3307 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3308 if (viewRect.width >= viewSize.width - viewRect.x) { 3309 return; // no room left to scroll 3310 } 3311 } else { // tabPlacement == LEFT || tabPlacement == RIGHT 3312 if (viewRect.height >= viewSize.height - viewRect.y) { 3313 return; 3314 } 3315 } 3316 setLeadingTabIndex(tabPlacement, leadingTabIndex+1); 3317 } 3318 3319 public void scrollBackward(int tabPlacement) { 3320 if (leadingTabIndex == 0) { 3321 return; // no room left to scroll 3322 } 3323 setLeadingTabIndex(tabPlacement, leadingTabIndex-1); 3324 } 3325 3326 public void setLeadingTabIndex(int tabPlacement, int index) { 3327 leadingTabIndex = index; 3328 Dimension viewSize = viewport.getViewSize(); 3329 Rectangle viewRect = viewport.getViewRect(); 3330 3331 switch(tabPlacement) { 3332 case TOP: 3333 case BOTTOM: 3334 tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x; 3335 3336 if ((viewSize.width - tabViewPosition.x) < viewRect.width) { 3337 // We've scrolled to the end, so adjust the viewport size 3338 // to ensure the view position remains aligned on a tab boundary 3339 Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x, 3340 viewRect.height); 3341 viewport.setExtentSize(extentSize); 3342 } 3343 break; 3344 case LEFT: 3345 case RIGHT: 3346 tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y; 3347 3348 if ((viewSize.height - tabViewPosition.y) < viewRect.height) { 3349 // We've scrolled to the end, so adjust the viewport size 3350 // to ensure the view position remains aligned on a tab boundary 3351 Dimension extentSize = new Dimension(viewRect.width, 3352 viewSize.height - tabViewPosition.y); 3353 viewport.setExtentSize(extentSize); 3354 } 3355 } 3356 viewport.setViewPosition(tabViewPosition); 3357 } 3358 3359 public void stateChanged(ChangeEvent e) { 3360 updateView(); 3361 } 3362 3363 private void updateView() { 3364 int tabPlacement = tabPane.getTabPlacement(); 3365 int tabCount = tabPane.getTabCount(); 3366 assureRectsCreated(tabCount); 3367 Rectangle vpRect = viewport.getBounds(); 3368 Dimension viewSize = viewport.getViewSize(); 3369 Rectangle viewRect = viewport.getViewRect(); 3370 3371 leadingTabIndex = getClosestTab(viewRect.x, viewRect.y); 3372 3373 // If the tab isn't right aligned, adjust it. 3374 if (leadingTabIndex + 1 < tabCount) { 3375 switch (tabPlacement) { 3376 case TOP: 3377 case BOTTOM: 3378 if (rects[leadingTabIndex].x < viewRect.x) { 3379 leadingTabIndex++; 3380 } 3381 break; 3382 case LEFT: 3383 case RIGHT: 3384 if (rects[leadingTabIndex].y < viewRect.y) { 3385 leadingTabIndex++; 3386 } 3387 break; 3388 } 3389 } 3390 Insets contentInsets = getContentBorderInsets(tabPlacement); 3391 switch(tabPlacement) { 3392 case LEFT: 3393 tabPane.repaint(vpRect.x+vpRect.width, vpRect.y, 3394 contentInsets.left, vpRect.height); 3395 scrollBackwardButton.setEnabled( 3396 viewRect.y > 0 && leadingTabIndex > 0); 3397 scrollForwardButton.setEnabled( 3398 leadingTabIndex < tabCount-1 && 3399 viewSize.height-viewRect.y > viewRect.height); 3400 break; 3401 case RIGHT: 3402 tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y, 3403 contentInsets.right, vpRect.height); 3404 scrollBackwardButton.setEnabled( 3405 viewRect.y > 0 && leadingTabIndex > 0); 3406 scrollForwardButton.setEnabled( 3407 leadingTabIndex < tabCount-1 && 3408 viewSize.height-viewRect.y > viewRect.height); 3409 break; 3410 case BOTTOM: 3411 tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom, 3412 vpRect.width, contentInsets.bottom); 3413 scrollBackwardButton.setEnabled( 3414 viewRect.x > 0 && leadingTabIndex > 0); 3415 scrollForwardButton.setEnabled( 3416 leadingTabIndex < tabCount-1 && 3417 viewSize.width-viewRect.x > viewRect.width); 3418 break; 3419 case TOP: 3420 default: 3421 tabPane.repaint(vpRect.x, vpRect.y+vpRect.height, 3422 vpRect.width, contentInsets.top); 3423 scrollBackwardButton.setEnabled( 3424 viewRect.x > 0 && leadingTabIndex > 0); 3425 scrollForwardButton.setEnabled( 3426 leadingTabIndex < tabCount-1 && 3427 viewSize.width-viewRect.x > viewRect.width); 3428 } 3429 } 3430 3431 /** 3432 * ActionListener for the scroll buttons. 3433 */ 3434 public void actionPerformed(ActionEvent e) { 3435 ActionMap map = tabPane.getActionMap(); 3436 3437 if (map != null) { 3438 String actionKey; 3439 3440 if (e.getSource() == scrollForwardButton) { 3441 actionKey = "scrollTabsForwardAction"; 3442 } 3443 else { 3444 actionKey = "scrollTabsBackwardAction"; 3445 } 3446 Action action = map.get(actionKey); 3447 3448 if (action != null && action.isEnabled()) { 3449 action.actionPerformed(new ActionEvent(tabPane, 3450 ActionEvent.ACTION_PERFORMED, null, e.getWhen(), 3451 e.getModifiers())); 3452 } 3453 } 3454 } 3455 3456 public String toString() { 3457 return "viewport.viewSize=" + viewport.getViewSize() + "\n" + 3458 "viewport.viewRectangle="+viewport.getViewRect()+"\n"+ 3459 "leadingTabIndex="+leadingTabIndex+"\n"+ 3460 "tabViewPosition=" + tabViewPosition; 3461 } 3462 3463 } 3464 3465 @SuppressWarnings("serial") // Superclass is not serializable across versions 3466 private class ScrollableTabViewport extends JViewport implements UIResource { 3467 public ScrollableTabViewport() { 3468 super(); 3469 setName("TabbedPane.scrollableViewport"); 3470 setScrollMode(SIMPLE_SCROLL_MODE); 3471 setOpaque(tabPane.isOpaque()); 3472 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3473 if (bgColor == null) { 3474 bgColor = tabPane.getBackground(); 3475 } 3476 setBackground(bgColor); 3477 } 3478 } 3479 3480 @SuppressWarnings("serial") // Superclass is not serializable across versions 3481 private class ScrollableTabPanel extends JPanel implements UIResource { 3482 public ScrollableTabPanel() { 3483 super(null); 3484 setOpaque(tabPane.isOpaque()); 3485 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3486 if (bgColor == null) { 3487 bgColor = tabPane.getBackground(); 3488 } 3489 setBackground(bgColor); 3490 } 3491 public void paintComponent(Graphics g) { 3492 super.paintComponent(g); 3493 BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(), 3494 tabPane.getSelectedIndex()); 3495 if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) { 3496 Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()]; 3497 g.translate(croppedRect.x, croppedRect.y); 3498 tabScroller.croppedEdge.paintComponent(g); 3499 g.translate(-croppedRect.x, -croppedRect.y); 3500 } 3501 } 3502 3503 public void doLayout() { 3504 if (getComponentCount() > 0) { 3505 Component child = getComponent(0); 3506 child.setBounds(0, 0, getWidth(), getHeight()); 3507 } 3508 } 3509 } 3510 3511 @SuppressWarnings("serial") // Superclass is not serializable across versions 3512 private class ScrollableTabButton extends BasicArrowButton implements UIResource, 3513 SwingConstants { 3514 public ScrollableTabButton(int direction) { 3515 super(direction, 3516 UIManager.getColor("TabbedPane.selected"), 3517 UIManager.getColor("TabbedPane.shadow"), 3518 UIManager.getColor("TabbedPane.darkShadow"), 3519 UIManager.getColor("TabbedPane.highlight")); 3520 } 3521 } 3522 3523 3524 // Controller: event listeners 3525 3526 private class Handler implements ChangeListener, ContainerListener, 3527 FocusListener, MouseListener, MouseMotionListener, 3528 PropertyChangeListener { 3529 // 3530 // PropertyChangeListener 3531 // 3532 public void propertyChange(PropertyChangeEvent e) { 3533 JTabbedPane pane = (JTabbedPane)e.getSource(); 3534 String name = e.getPropertyName(); 3535 boolean isScrollLayout = scrollableTabLayoutEnabled(); 3536 if (name == "mnemonicAt") { 3537 updateMnemonics(); 3538 pane.repaint(); 3539 } 3540 else if (name == "displayedMnemonicIndexAt") { 3541 pane.repaint(); 3542 } 3543 else if (name =="indexForTitle") { 3544 calculatedBaseline = false; 3545 Integer index = (Integer) e.getNewValue(); 3546 updateHtmlViews(index, false); 3547 } else if (name == "tabLayoutPolicy") { 3548 BasicTabbedPaneUI.this.uninstallUI(pane); 3549 BasicTabbedPaneUI.this.installUI(pane); 3550 calculatedBaseline = false; 3551 } else if (name == "tabPlacement") { 3552 if (scrollableTabLayoutEnabled()) { 3553 tabScroller.createButtons(); 3554 } 3555 calculatedBaseline = false; 3556 } else if (name == "opaque" && isScrollLayout) { 3557 boolean newVal = ((Boolean)e.getNewValue()).booleanValue(); 3558 tabScroller.tabPanel.setOpaque(newVal); 3559 tabScroller.viewport.setOpaque(newVal); 3560 } else if (name == "background" && isScrollLayout) { 3561 Color newVal = (Color)e.getNewValue(); 3562 tabScroller.tabPanel.setBackground(newVal); 3563 tabScroller.viewport.setBackground(newVal); 3564 Color newColor = selectedColor == null ? newVal : selectedColor; 3565 tabScroller.scrollForwardButton.setBackground(newColor); 3566 tabScroller.scrollBackwardButton.setBackground(newColor); 3567 } else if (name == "indexForTabComponent") { 3568 if (tabContainer != null) { 3569 tabContainer.removeUnusedTabComponents(); 3570 } 3571 Component c = tabPane.getTabComponentAt( 3572 (Integer)e.getNewValue()); 3573 if (c != null) { 3574 if (tabContainer == null) { 3575 installTabContainer(); 3576 } else { 3577 tabContainer.add(c); 3578 } 3579 } 3580 tabPane.revalidate(); 3581 tabPane.repaint(); 3582 calculatedBaseline = false; 3583 } else if (name == "indexForNullComponent") { 3584 isRunsDirty = true; 3585 updateHtmlViews((Integer)e.getNewValue(), true); 3586 } else if (name == "font") { 3587 calculatedBaseline = false; 3588 } 3589 } 3590 3591 private void updateHtmlViews(int index, boolean inserted) { 3592 String title = tabPane.getTitleAt(index); 3593 boolean isHTML = BasicHTML.isHTMLString(title); 3594 if (isHTML) { 3595 if (htmlViews==null) { // Initialize vector 3596 htmlViews = createHTMLVector(); 3597 } else { // Vector already exists 3598 View v = BasicHTML.createHTMLView(tabPane, title); 3599 setHtmlView(v, inserted, index); 3600 } 3601 } else { // Not HTML 3602 if (htmlViews!=null) { // Add placeholder 3603 setHtmlView(null, inserted, index); 3604 } // else nada! 3605 } 3606 updateMnemonics(); 3607 } 3608 3609 private void setHtmlView(View v, boolean inserted, int index) { 3610 if (inserted || index >= htmlViews.size()) { 3611 htmlViews.insertElementAt(v, index); 3612 } else { 3613 htmlViews.setElementAt(v, index); 3614 } 3615 } 3616 3617 // 3618 // ChangeListener 3619 // 3620 public void stateChanged(ChangeEvent e) { 3621 JTabbedPane tabPane = (JTabbedPane)e.getSource(); 3622 tabPane.revalidate(); 3623 tabPane.repaint(); 3624 3625 setFocusIndex(tabPane.getSelectedIndex(), false); 3626 3627 if (scrollableTabLayoutEnabled()) { 3628 ensureCurrentLayout(); 3629 int index = tabPane.getSelectedIndex(); 3630 if (index < rects.length && index != -1) { 3631 tabScroller.tabPanel.scrollRectToVisible( 3632 (Rectangle)rects[index].clone()); 3633 } 3634 } 3635 } 3636 3637 // 3638 // MouseListener 3639 // 3640 public void mouseClicked(MouseEvent e) { 3641 } 3642 3643 public void mouseReleased(MouseEvent e) { 3644 } 3645 3646 public void mouseEntered(MouseEvent e) { 3647 setRolloverTab(e.getX(), e.getY()); 3648 } 3649 3650 public void mouseExited(MouseEvent e) { 3651 setRolloverTab(-1); 3652 } 3653 3654 public void mousePressed(MouseEvent e) { 3655 if (!tabPane.isEnabled()) { 3656 return; 3657 } 3658 int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); 3659 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) { 3660 if (tabIndex != tabPane.getSelectedIndex()) { 3661 // Clicking on unselected tab, change selection, do NOT 3662 // request focus. 3663 // This will trigger the focusIndex to change by way 3664 // of stateChanged. 3665 tabPane.setSelectedIndex(tabIndex); 3666 } 3667 else if (tabPane.isRequestFocusEnabled()) { 3668 // Clicking on selected tab, try and give the tabbedpane 3669 // focus. Repaint will occur in focusGained. 3670 tabPane.requestFocus(); 3671 } 3672 } 3673 } 3674 3675 // 3676 // MouseMotionListener 3677 // 3678 public void mouseDragged(MouseEvent e) { 3679 } 3680 3681 public void mouseMoved(MouseEvent e) { 3682 setRolloverTab(e.getX(), e.getY()); 3683 } 3684 3685 // 3686 // FocusListener 3687 // 3688 public void focusGained(FocusEvent e) { 3689 setFocusIndex(tabPane.getSelectedIndex(), true); 3690 } 3691 public void focusLost(FocusEvent e) { 3692 repaintTab(focusIndex); 3693 } 3694 3695 3696 // 3697 // ContainerListener 3698 // 3699 /* GES 2/3/99: 3700 The container listener code was added to support HTML 3701 rendering of tab titles. 3702 3703 Ideally, we would be able to listen for property changes 3704 when a tab is added or its text modified. At the moment 3705 there are no such events because the Beans spec doesn't 3706 allow 'indexed' property changes (i.e. tab 2's text changed 3707 from A to B). 3708 3709 In order to get around this, we listen for tabs to be added 3710 or removed by listening for the container events. we then 3711 queue up a runnable (so the component has a chance to complete 3712 the add) which checks the tab title of the new component to see 3713 if it requires HTML rendering. 3714 3715 The Views (one per tab title requiring HTML rendering) are 3716 stored in the htmlViews Vector, which is only allocated after 3717 the first time we run into an HTML tab. Note that this vector 3718 is kept in step with the number of pages, and nulls are added 3719 for those pages whose tab title do not require HTML rendering. 3720 3721 This makes it easy for the paint and layout code to tell 3722 whether to invoke the HTML engine without having to check 3723 the string during time-sensitive operations. 3724 3725 When we have added a way to listen for tab additions and 3726 changes to tab text, this code should be removed and 3727 replaced by something which uses that. */ 3728 3729 public void componentAdded(ContainerEvent e) { 3730 JTabbedPane tp = (JTabbedPane)e.getContainer(); 3731 Component child = e.getChild(); 3732 if (child instanceof UIResource) { 3733 return; 3734 } 3735 isRunsDirty = true; 3736 updateHtmlViews(tp.indexOfComponent(child), true); 3737 } 3738 public void componentRemoved(ContainerEvent e) { 3739 JTabbedPane tp = (JTabbedPane)e.getContainer(); 3740 Component child = e.getChild(); 3741 if (child instanceof UIResource) { 3742 return; 3743 } 3744 3745 // NOTE 4/15/2002 (joutwate): 3746 // This fix is implemented using client properties since there is 3747 // currently no IndexPropertyChangeEvent. Once 3748 // IndexPropertyChangeEvents have been added this code should be 3749 // modified to use it. 3750 Integer indexObj = 3751 (Integer)tp.getClientProperty("__index_to_remove__"); 3752 if (indexObj != null) { 3753 int index = indexObj.intValue(); 3754 if (htmlViews != null && htmlViews.size() > index) { 3755 htmlViews.removeElementAt(index); 3756 } 3757 tp.putClientProperty("__index_to_remove__", null); 3758 } 3759 isRunsDirty = true; 3760 updateMnemonics(); 3761 3762 validateFocusIndex(); 3763 } 3764 } 3765 3766 /** 3767 * This class should be treated as a "protected" inner class. 3768 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3769 */ 3770 public class PropertyChangeHandler implements PropertyChangeListener { 3771 // NOTE: This class exists only for backward compatibility. All 3772 // its functionality has been moved into Handler. If you need to add 3773 // new functionality add it to the Handler, but make sure this 3774 // class calls into the Handler. 3775 public void propertyChange(PropertyChangeEvent e) { 3776 getHandler().propertyChange(e); 3777 } 3778 } 3779 3780 /** 3781 * This class should be treated as a "protected" inner class. 3782 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3783 */ 3784 public class TabSelectionHandler implements ChangeListener { 3785 // NOTE: This class exists only for backward compatibility. All 3786 // its functionality has been moved into Handler. If you need to add 3787 // new functionality add it to the Handler, but make sure this 3788 // class calls into the Handler. 3789 public void stateChanged(ChangeEvent e) { 3790 getHandler().stateChanged(e); 3791 } 3792 } 3793 3794 /** 3795 * This class should be treated as a "protected" inner class. 3796 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3797 */ 3798 public class MouseHandler extends MouseAdapter { 3799 // NOTE: This class exists only for backward compatibility. All 3800 // its functionality has been moved into Handler. If you need to add 3801 // new functionality add it to the Handler, but make sure this 3802 // class calls into the Handler. 3803 public void mousePressed(MouseEvent e) { 3804 getHandler().mousePressed(e); 3805 } 3806 } 3807 3808 /** 3809 * This class should be treated as a "protected" inner class. 3810 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3811 */ 3812 public class FocusHandler extends FocusAdapter { 3813 // NOTE: This class exists only for backward compatibility. All 3814 // its functionality has been moved into Handler. If you need to add 3815 // new functionality add it to the Handler, but make sure this 3816 // class calls into the Handler. 3817 public void focusGained(FocusEvent e) { 3818 getHandler().focusGained(e); 3819 } 3820 public void focusLost(FocusEvent e) { 3821 getHandler().focusLost(e); 3822 } 3823 } 3824 3825 private Vector<View> createHTMLVector() { 3826 Vector<View> htmlViews = new Vector<View>(); 3827 int count = tabPane.getTabCount(); 3828 if (count>0) { 3829 for (int i=0 ; i<count; i++) { 3830 String title = tabPane.getTitleAt(i); 3831 if (BasicHTML.isHTMLString(title)) { 3832 htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title)); 3833 } else { 3834 htmlViews.addElement(null); 3835 } 3836 } 3837 } 3838 return htmlViews; 3839 } 3840 3841 @SuppressWarnings("serial") // Superclass is not serializable across versions 3842 private class TabContainer extends JPanel implements UIResource { 3843 private boolean notifyTabbedPane = true; 3844 3845 public TabContainer() { 3846 super(null); 3847 setOpaque(false); 3848 } 3849 3850 public void remove(Component comp) { 3851 int index = tabPane.indexOfTabComponent(comp); 3852 super.remove(comp); 3853 if (notifyTabbedPane && index != -1) { 3854 tabPane.setTabComponentAt(index, null); 3855 } 3856 } 3857 3858 private void removeUnusedTabComponents() { 3859 for (Component c : getComponents()) { 3860 if (!(c instanceof UIResource)) { 3861 int index = tabPane.indexOfTabComponent(c); 3862 if (index == -1) { 3863 super.remove(c); 3864 } 3865 } 3866 } 3867 } 3868 3869 public boolean isOptimizedDrawingEnabled() { 3870 return tabScroller != null && !tabScroller.croppedEdge.isParamsSet(); 3871 } 3872 3873 public void doLayout() { 3874 // We layout tabComponents in JTabbedPane's layout manager 3875 // and use this method as a hook for repainting tabs 3876 // to update tabs area e.g. when the size of tabComponent was changed 3877 if (scrollableTabLayoutEnabled()) { 3878 tabScroller.tabPanel.repaint(); 3879 tabScroller.updateView(); 3880 } else { 3881 tabPane.repaint(getBounds()); 3882 } 3883 } 3884 } 3885 3886 @SuppressWarnings("serial") // Superclass is not serializable across versions 3887 private class CroppedEdge extends JPanel implements UIResource { 3888 private Shape shape; 3889 private int tabIndex; 3890 private int cropline; 3891 private int cropx, cropy; 3892 3893 public CroppedEdge() { 3894 setOpaque(false); 3895 } 3896 3897 public void setParams(int tabIndex, int cropline, int cropx, int cropy) { 3898 this.tabIndex = tabIndex; 3899 this.cropline = cropline; 3900 this.cropx = cropx; 3901 this.cropy = cropy; 3902 Rectangle tabRect = rects[tabIndex]; 3903 setBounds(tabRect); 3904 shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline); 3905 if (getParent() == null && tabContainer != null) { 3906 tabContainer.add(this, 0); 3907 } 3908 } 3909 3910 public void resetParams() { 3911 shape = null; 3912 if (getParent() == tabContainer && tabContainer != null) { 3913 tabContainer.remove(this); 3914 } 3915 } 3916 3917 public boolean isParamsSet() { 3918 return shape != null; 3919 } 3920 3921 public int getTabIndex() { 3922 return tabIndex; 3923 } 3924 3925 public int getCropline() { 3926 return cropline; 3927 } 3928 3929 public int getCroppedSideWidth() { 3930 return 3; 3931 } 3932 3933 private Color getBgColor() { 3934 Component parent = tabPane.getParent(); 3935 if (parent != null) { 3936 Color bg = parent.getBackground(); 3937 if (bg != null) { 3938 return bg; 3939 } 3940 } 3941 return UIManager.getColor("control"); 3942 } 3943 3944 protected void paintComponent(Graphics g) { 3945 super.paintComponent(g); 3946 if (isParamsSet() && g instanceof Graphics2D) { 3947 Graphics2D g2 = (Graphics2D) g; 3948 g2.clipRect(0, 0, getWidth(), getHeight()); 3949 g2.setColor(getBgColor()); 3950 g2.translate(cropx, cropy); 3951 g2.fill(shape); 3952 paintCroppedTabEdge(g); 3953 g2.translate(-cropx, -cropy); 3954 } 3955 } 3956 } 3957 }