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