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