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