1 /* 2 * Copyright (c) 2011, 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 private class ScrollableTabViewport extends JViewport implements UIResource { 3241 public ScrollableTabViewport() { 3242 super(); 3243 setName("TabbedPane.scrollableViewport"); 3244 setScrollMode(SIMPLE_SCROLL_MODE); 3245 setOpaque(tabPane.isOpaque()); 3246 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3247 if (bgColor == null) { 3248 bgColor = tabPane.getBackground(); 3249 } 3250 setBackground(bgColor); 3251 } 3252 } 3253 3254 private class ScrollableTabPanel extends JPanel implements UIResource { 3255 public ScrollableTabPanel() { 3256 super(null); 3257 setOpaque(tabPane.isOpaque()); 3258 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3259 if (bgColor == null) { 3260 bgColor = tabPane.getBackground(); 3261 } 3262 setBackground(bgColor); 3263 } 3264 3265 public void paintComponent(final Graphics g) { 3266 super.paintComponent(g); 3267 AquaTabbedPaneCopyFromBasicUI.this.paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); 3268 if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) { 3269 final Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()]; 3270 g.translate(croppedRect.x, croppedRect.y); 3271 tabScroller.croppedEdge.paintComponent(g); 3272 g.translate(-croppedRect.x, -croppedRect.y); 3273 } 3274 } 3275 3276 public void doLayout() { 3277 if (getComponentCount() > 0) { 3278 final Component child = getComponent(0); 3279 child.setBounds(0, 0, getWidth(), getHeight()); 3280 } 3281 } 3282 } 3283 3284 private class ScrollableTabButton extends javax.swing.plaf.basic.BasicArrowButton implements UIResource, SwingConstants { 3285 public ScrollableTabButton(final int direction) { 3286 super(direction, UIManager.getColor("TabbedPane.selected"), UIManager.getColor("TabbedPane.shadow"), UIManager.getColor("TabbedPane.darkShadow"), UIManager.getColor("TabbedPane.highlight")); 3287 } 3288 } 3289 3290 // Controller: event listeners 3291 3292 private class Handler implements ChangeListener, ContainerListener, FocusListener, MouseListener, MouseMotionListener, PropertyChangeListener { 3293 // 3294 // PropertyChangeListener 3295 // 3296 public void propertyChange(final PropertyChangeEvent e) { 3297 final JTabbedPane pane = (JTabbedPane)e.getSource(); 3298 final String name = e.getPropertyName(); 3299 final boolean isScrollLayout = scrollableTabLayoutEnabled(); 3300 if (name == "mnemonicAt") { 3301 updateMnemonics(); 3302 pane.repaint(); 3303 } else if (name == "displayedMnemonicIndexAt") { 3304 pane.repaint(); 3305 } else if (name == "indexForTitle") { 3306 calculatedBaseline = false; 3307 updateHtmlViews((Integer) e.getNewValue()); 3308 } else if (name == "tabLayoutPolicy") { 3309 AquaTabbedPaneCopyFromBasicUI.this.uninstallUI(pane); 3310 AquaTabbedPaneCopyFromBasicUI.this.installUI(pane); 3311 calculatedBaseline = false; 3312 } else if (name == "tabPlacement") { 3313 if (scrollableTabLayoutEnabled()) { 3314 tabScroller.createButtons(); 3315 } 3316 calculatedBaseline = false; 3317 } else if (name == "opaque" && isScrollLayout) { 3318 final boolean newVal = ((Boolean)e.getNewValue()).booleanValue(); 3319 tabScroller.tabPanel.setOpaque(newVal); 3320 tabScroller.viewport.setOpaque(newVal); 3321 } else if (name == "background" && isScrollLayout) { 3322 final Color newVal = (Color)e.getNewValue(); 3323 tabScroller.tabPanel.setBackground(newVal); 3324 tabScroller.viewport.setBackground(newVal); 3325 final Color newColor = selectedColor == null ? newVal : selectedColor; 3326 tabScroller.scrollForwardButton.setBackground(newColor); 3327 tabScroller.scrollBackwardButton.setBackground(newColor); 3328 } else if (name == "indexForTabComponent") { 3329 if (tabContainer != null) { 3330 tabContainer.removeUnusedTabComponents(); 3331 } 3332 final Component c = tabPane.getTabComponentAt((Integer)e.getNewValue()); 3333 if (c != null) { 3334 if (tabContainer == null) { 3335 installTabContainer(); 3336 } else { 3337 tabContainer.add(c); 3338 } 3339 } 3340 tabPane.revalidate(); 3341 tabPane.repaint(); 3342 calculatedBaseline = false; 3343 } else if (name == "indexForNullComponent") { 3344 isRunsDirty = true; 3345 updateHtmlViews((Integer) e.getNewValue()); 3346 } else if (name == "font") { 3347 calculatedBaseline = false; 3348 } 3349 } 3350 3351 // 3352 // ChangeListener 3353 // 3354 public void stateChanged(final ChangeEvent e) { 3355 final JTabbedPane tabPane = (JTabbedPane)e.getSource(); 3356 tabPane.revalidate(); 3357 tabPane.repaint(); 3358 3359 setFocusIndex(tabPane.getSelectedIndex(), false); 3360 3361 if (scrollableTabLayoutEnabled()) { 3362 final int index = tabPane.getSelectedIndex(); 3363 if (index < rects.length && index != -1) { 3364 tabScroller.tabPanel.scrollRectToVisible((Rectangle)rects[index].clone()); 3365 } 3366 } 3367 } 3368 3369 // 3370 // MouseListener 3371 // 3372 public void mouseClicked(final MouseEvent e) {} 3373 3374 public void mouseReleased(final MouseEvent e) {} 3375 3376 public void mouseEntered(final MouseEvent e) { 3377 setRolloverTab(e.getX(), e.getY()); 3378 } 3379 3380 public void mouseExited(final MouseEvent e) { 3381 setRolloverTab(-1); 3382 } 3383 3384 public void mousePressed(final MouseEvent e) { 3385 if (!tabPane.isEnabled()) { 3386 return; 3387 } 3388 final int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); 3389 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) { 3390 if (tabIndex != tabPane.getSelectedIndex()) { 3391 // Clicking on unselected tab, change selection, do NOT 3392 // request focus. 3393 // This will trigger the focusIndex to change by way 3394 // of stateChanged. 3395 tabPane.setSelectedIndex(tabIndex); 3396 } else if (tabPane.isRequestFocusEnabled()) { 3397 // Clicking on selected tab, try and give the tabbedpane 3398 // focus. Repaint will occur in focusGained. 3399 tabPane.requestFocus(); 3400 } 3401 } 3402 } 3403 3404 // 3405 // MouseMotionListener 3406 // 3407 public void mouseDragged(final MouseEvent e) {} 3408 3409 public void mouseMoved(final MouseEvent e) { 3410 setRolloverTab(e.getX(), e.getY()); 3411 } 3412 3413 // 3414 // FocusListener 3415 // 3416 public void focusGained(final FocusEvent e) { 3417 setFocusIndex(tabPane.getSelectedIndex(), true); 3418 } 3419 3420 public void focusLost(final FocusEvent e) { 3421 repaintTab(focusIndex); 3422 } 3423 3424 // 3425 // ContainerListener 3426 // 3427 /* GES 2/3/99: 3428 The container listener code was added to support HTML 3429 rendering of tab titles. 3430 3431 Ideally, we would be able to listen for property changes 3432 when a tab is added or its text modified. At the moment 3433 there are no such events because the Beans spec doesn't 3434 allow 'indexed' property changes (i.e. tab 2's text changed 3435 from A to B). 3436 3437 In order to get around this, we listen for tabs to be added 3438 or removed by listening for the container events. we then 3439 queue up a runnable (so the component has a chance to complete 3440 the add) which checks the tab title of the new component to see 3441 if it requires HTML rendering. 3442 3443 The Views (one per tab title requiring HTML rendering) are 3444 stored in the htmlViews Vector, which is only allocated after 3445 the first time we run into an HTML tab. Note that this vector 3446 is kept in step with the number of pages, and nulls are added 3447 for those pages whose tab title do not require HTML rendering. 3448 3449 This makes it easy for the paint and layout code to tell 3450 whether to invoke the HTML engine without having to check 3451 the string during time-sensitive operations. 3452 3453 When we have added a way to listen for tab additions and 3454 changes to tab text, this code should be removed and 3455 replaced by something which uses that. */ 3456 3457 public void componentAdded(final ContainerEvent e) { 3458 final JTabbedPane tp = (JTabbedPane)e.getContainer(); 3459 final Component child = e.getChild(); 3460 if (child instanceof UIResource) { 3461 return; 3462 } 3463 isRunsDirty = true; 3464 updateHtmlViews(tp.indexOfComponent(child)); 3465 } 3466 3467 private void updateHtmlViews(int index) { 3468 final String title = tabPane.getTitleAt(index); 3469 final boolean isHTML = BasicHTML.isHTMLString(title); 3470 if (isHTML) { 3471 if (htmlViews == null) { // Initialize vector 3472 htmlViews = createHTMLVector(); 3473 } else { // Vector already exists 3474 final View v = BasicHTML.createHTMLView(tabPane, title); 3475 htmlViews.insertElementAt(v, index); 3476 } 3477 } else { // Not HTML 3478 if (htmlViews != null) { // Add placeholder 3479 htmlViews.insertElementAt(null, index); 3480 } // else nada! 3481 } 3482 updateMnemonics(); 3483 } 3484 3485 public void componentRemoved(final ContainerEvent e) { 3486 final JTabbedPane tp = (JTabbedPane)e.getContainer(); 3487 final Component child = e.getChild(); 3488 if (child instanceof UIResource) { 3489 return; 3490 } 3491 3492 // NOTE 4/15/2002 (joutwate): 3493 // This fix is implemented using client properties since there is 3494 // currently no IndexPropertyChangeEvent. Once 3495 // IndexPropertyChangeEvents have been added this code should be 3496 // modified to use it. 3497 final Integer indexObj = (Integer)tp.getClientProperty("__index_to_remove__"); 3498 if (indexObj != null) { 3499 final int index = indexObj.intValue(); 3500 if (htmlViews != null && htmlViews.size() > index) { 3501 htmlViews.removeElementAt(index); 3502 } 3503 tp.putClientProperty("__index_to_remove__", null); 3504 } 3505 isRunsDirty = true; 3506 updateMnemonics(); 3507 3508 validateFocusIndex(); 3509 } 3510 } 3511 3512 /** 3513 * This class should be treated as a "protected" inner class. 3514 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3515 */ 3516 public class PropertyChangeHandler implements PropertyChangeListener { 3517 // NOTE: This class exists only for backward compatibility. All 3518 // its functionality has been moved into Handler. If you need to add 3519 // new functionality add it to the Handler, but make sure this 3520 // class calls into the Handler. 3521 public void propertyChange(final PropertyChangeEvent e) { 3522 getHandler().propertyChange(e); 3523 } 3524 } 3525 3526 /** 3527 * This class should be treated as a "protected" inner class. 3528 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3529 */ 3530 public class TabSelectionHandler implements ChangeListener { 3531 // NOTE: This class exists only for backward compatibility. All 3532 // its functionality has been moved into Handler. If you need to add 3533 // new functionality add it to the Handler, but make sure this 3534 // class calls into the Handler. 3535 public void stateChanged(final ChangeEvent e) { 3536 getHandler().stateChanged(e); 3537 } 3538 } 3539 3540 /** 3541 * This class should be treated as a "protected" inner class. 3542 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3543 */ 3544 public class MouseHandler extends MouseAdapter { 3545 // NOTE: This class exists only for backward compatibility. All 3546 // its functionality has been moved into Handler. If you need to add 3547 // new functionality add it to the Handler, but make sure this 3548 // class calls into the Handler. 3549 public void mousePressed(final MouseEvent e) { 3550 getHandler().mousePressed(e); 3551 } 3552 } 3553 3554 /** 3555 * This class should be treated as a "protected" inner class. 3556 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3557 */ 3558 public class FocusHandler extends FocusAdapter { 3559 // NOTE: This class exists only for backward compatibility. All 3560 // its functionality has been moved into Handler. If you need to add 3561 // new functionality add it to the Handler, but make sure this 3562 // class calls into the Handler. 3563 public void focusGained(final FocusEvent e) { 3564 getHandler().focusGained(e); 3565 } 3566 3567 public void focusLost(final FocusEvent e) { 3568 getHandler().focusLost(e); 3569 } 3570 } 3571 3572 private Vector<View> createHTMLVector() { 3573 final Vector<View> htmlViews = new Vector<View>(); 3574 final int count = tabPane.getTabCount(); 3575 if (count > 0) { 3576 for (int i = 0; i < count; i++) { 3577 final String title = tabPane.getTitleAt(i); 3578 if (BasicHTML.isHTMLString(title)) { 3579 htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title)); 3580 } else { 3581 htmlViews.addElement(null); 3582 } 3583 } 3584 } 3585 return htmlViews; 3586 } 3587 3588 private class TabContainer extends JPanel implements UIResource { 3589 private boolean notifyTabbedPane = true; 3590 3591 public TabContainer() { 3592 super(null); 3593 setOpaque(false); 3594 } 3595 3596 public void remove(final Component comp) { 3597 final int index = tabPane.indexOfTabComponent(comp); 3598 super.remove(comp); 3599 if (notifyTabbedPane && index != -1) { 3600 tabPane.setTabComponentAt(index, null); 3601 } 3602 } 3603 3604 private void removeUnusedTabComponents() { 3605 for (final Component c : getComponents()) { 3606 if (!(c instanceof UIResource)) { 3607 final int index = tabPane.indexOfTabComponent(c); 3608 if (index == -1) { 3609 super.remove(c); 3610 } 3611 } 3612 } 3613 } 3614 3615 public boolean isOptimizedDrawingEnabled() { 3616 return tabScroller != null && !tabScroller.croppedEdge.isParamsSet(); 3617 } 3618 3619 public void doLayout() { 3620 // We layout tabComponents in JTabbedPane's layout manager 3621 // and use this method as a hook for repainting tabs 3622 // to update tabs area e.g. when the size of tabComponent was changed 3623 if (scrollableTabLayoutEnabled()) { 3624 tabScroller.tabPanel.repaint(); 3625 tabScroller.updateView(); 3626 } else { 3627 tabPane.repaint(getBounds()); 3628 } 3629 } 3630 } 3631 3632 private class CroppedEdge extends JPanel implements UIResource { 3633 private Shape shape; 3634 private int tabIndex; 3635 private int cropline; 3636 private int cropx, cropy; 3637 3638 public CroppedEdge() { 3639 setOpaque(false); 3640 } 3641 3642 public void setParams(final int tabIndex, final int cropline, final int cropx, final int cropy) { 3643 this.tabIndex = tabIndex; 3644 this.cropline = cropline; 3645 this.cropx = cropx; 3646 this.cropy = cropy; 3647 final Rectangle tabRect = rects[tabIndex]; 3648 setBounds(tabRect); 3649 shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline); 3650 if (getParent() == null && tabContainer != null) { 3651 tabContainer.add(this, 0); 3652 } 3653 } 3654 3655 public void resetParams() { 3656 shape = null; 3657 if (getParent() == tabContainer && tabContainer != null) { 3658 tabContainer.remove(this); 3659 } 3660 } 3661 3662 public boolean isParamsSet() { 3663 return shape != null; 3664 } 3665 3666 public int getTabIndex() { 3667 return tabIndex; 3668 } 3669 3670 public int getCropline() { 3671 return cropline; 3672 } 3673 3674 public int getCroppedSideWidth() { 3675 return 3; 3676 } 3677 3678 private Color getBgColor() { 3679 final Component parent = tabPane.getParent(); 3680 if (parent != null) { 3681 final Color bg = parent.getBackground(); 3682 if (bg != null) { 3683 return bg; 3684 } 3685 } 3686 return UIManager.getColor("control"); 3687 } 3688 3689 protected void paintComponent(final Graphics g) { 3690 super.paintComponent(g); 3691 if (isParamsSet() && g instanceof Graphics2D) { 3692 final Graphics2D g2 = (Graphics2D)g; 3693 g2.clipRect(0, 0, getWidth(), getHeight()); 3694 g2.setColor(getBgColor()); 3695 g2.translate(cropx, cropy); 3696 g2.fill(shape); 3697 paintCroppedTabEdge(g); 3698 g2.translate(-cropx, -cropy); 3699 } 3700 } 3701 } 3702 3703 /** 3704 * An ActionMap that populates its contents as necessary. The 3705 * contents are populated by invoking the <code>loadActionMap</code> 3706 * method on the passed in Object. 3707 * 3708 * @version 1.6, 11/17/05 3709 * @author Scott Violet 3710 */ 3711 static class LazyActionMap extends ActionMapUIResource { 3712 /** 3713 * Object to invoke <code>loadActionMap</code> on. This may be 3714 * a Class object. 3715 */ 3716 private transient Object _loader; 3717 3718 /** 3719 * Installs an ActionMap that will be populated by invoking the 3720 * <code>loadActionMap</code> method on the specified Class 3721 * when necessary. 3722 * <p> 3723 * This should be used if the ActionMap can be shared. 3724 * 3725 * @param c JComponent to install the ActionMap on. 3726 * @param loaderClass Class object that gets loadActionMap invoked 3727 * on. 3728 * @param defaultsKey Key to use to defaults table to check for 3729 * existing map and what resulting Map will be registered on. 3730 */ 3731 static void installLazyActionMap(final JComponent c, final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) { 3732 ActionMap map = (ActionMap)UIManager.get(defaultsKey); 3733 if (map == null) { 3734 map = new LazyActionMap(loaderClass); 3735 UIManager.getLookAndFeelDefaults().put(defaultsKey, map); 3736 } 3737 SwingUtilities.replaceUIActionMap(c, map); 3738 } 3739 3740 /** 3741 * Returns an ActionMap that will be populated by invoking the 3742 * <code>loadActionMap</code> method on the specified Class 3743 * when necessary. 3744 * <p> 3745 * This should be used if the ActionMap can be shared. 3746 * 3747 * @param c JComponent to install the ActionMap on. 3748 * @param loaderClass Class object that gets loadActionMap invoked 3749 * on. 3750 * @param defaultsKey Key to use to defaults table to check for 3751 * existing map and what resulting Map will be registered on. 3752 */ 3753 static ActionMap getActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) { 3754 ActionMap map = (ActionMap)UIManager.get(defaultsKey); 3755 if (map == null) { 3756 map = new LazyActionMap(loaderClass); 3757 UIManager.getLookAndFeelDefaults().put(defaultsKey, map); 3758 } 3759 return map; 3760 } 3761 3762 private LazyActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loader) { 3763 _loader = loader; 3764 } 3765 3766 public void put(final Action action) { 3767 put(action.getValue(Action.NAME), action); 3768 } 3769 3770 public void put(final Object key, final Action action) { 3771 loadIfNecessary(); 3772 super.put(key, action); 3773 } 3774 3775 public Action get(final Object key) { 3776 loadIfNecessary(); 3777 return super.get(key); 3778 } 3779 3780 public void remove(final Object key) { 3781 loadIfNecessary(); 3782 super.remove(key); 3783 } 3784 3785 public void clear() { 3786 loadIfNecessary(); 3787 super.clear(); 3788 } 3789 3790 public Object[] keys() { 3791 loadIfNecessary(); 3792 return super.keys(); 3793 } 3794 3795 public int size() { 3796 loadIfNecessary(); 3797 return super.size(); 3798 } 3799 3800 public Object[] allKeys() { 3801 loadIfNecessary(); 3802 return super.allKeys(); 3803 } 3804 3805 public void setParent(final ActionMap map) { 3806 loadIfNecessary(); 3807 super.setParent(map); 3808 } 3809 3810 private void loadIfNecessary() { 3811 if (_loader != null) { 3812 final Object loader = _loader; 3813 3814 _loader = null; 3815 final Class<?> klass = (Class<?>)loader; 3816 try { 3817 final java.lang.reflect.Method method = klass.getDeclaredMethod("loadActionMap", new Class[] { LazyActionMap.class }); 3818 method.invoke(klass, new Object[] { this }); 3819 } catch (final NoSuchMethodException nsme) { 3820 assert false : "LazyActionMap unable to load actions " + klass; 3821 } catch (final IllegalAccessException iae) { 3822 assert false : "LazyActionMap unable to load actions " + iae; 3823 } catch (final InvocationTargetException ite) { 3824 assert false : "LazyActionMap unable to load actions " + ite; 3825 } catch (final IllegalArgumentException iae) { 3826 assert false : "LazyActionMap unable to load actions " + iae; 3827 } 3828 } 3829 } 3830 } 3831 }