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