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