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