1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * Copy of javax.swing.plaf.basic.BasicTabbedPaneUI because the original 28 * does not have enough private methods marked as protected. 29 * 30 * This copy is from 1.6.0_04 as of 2008-02-02. 31 */ 32 33 package com.apple.laf; 34 35 import java.awt.*; 36 import java.awt.event.*; 37 import java.beans.*; 38 import java.lang.reflect.InvocationTargetException; 39 import java.util.*; 40 41 import javax.swing.*; 42 import javax.swing.event.*; 43 import javax.swing.plaf.*; 44 import javax.swing.plaf.basic.*; 45 import javax.swing.text.View; 46 47 import sun.swing.*; 48 49 public class AquaTabbedPaneCopyFromBasicUI extends TabbedPaneUI implements SwingConstants { 50 // Instance variables initialized at installation 51 52 protected JTabbedPane tabPane; 53 54 protected Color highlight; 55 protected Color lightHighlight; 56 protected Color shadow; 57 protected Color darkShadow; 58 protected Color focus; 59 private Color selectedColor; 60 61 protected int textIconGap; 62 63 protected int tabRunOverlay; 64 65 protected Insets tabInsets; 66 protected Insets selectedTabPadInsets; 67 protected Insets tabAreaInsets; 68 protected Insets contentBorderInsets; 69 private boolean tabsOverlapBorder; 70 private boolean tabsOpaque = true; 71 private boolean contentOpaque = true; 72 73 /** 74 * As of Java 2 platform v1.3 this previously undocumented field is no 75 * longer used. 76 * Key bindings are now defined by the LookAndFeel, please refer to 77 * the key bindings specification for further details. 78 * 79 * @deprecated As of Java 2 platform v1.3. 80 */ 81 @Deprecated 82 protected KeyStroke upKey; 83 /** 84 * As of Java 2 platform v1.3 this previously undocumented field is no 85 * longer used. 86 * Key bindings are now defined by the LookAndFeel, please refer to 87 * the key bindings specification for further details. 88 * 89 * @deprecated As of Java 2 platform v1.3. 90 */ 91 @Deprecated 92 protected KeyStroke downKey; 93 /** 94 * As of Java 2 platform v1.3 this previously undocumented field is no 95 * longer used. 96 * Key bindings are now defined by the LookAndFeel, please refer to 97 * the key bindings specification for further details. 98 * 99 * @deprecated As of Java 2 platform v1.3. 100 */ 101 @Deprecated 102 protected KeyStroke leftKey; 103 /** 104 * As of Java 2 platform v1.3 this previously undocumented field is no 105 * longer used. 106 * Key bindings are now defined by the LookAndFeel, please refer to 107 * the key bindings specification for further details. 108 * 109 * @deprecated As of Java 2 platform v1.3. 110 */ 111 @Deprecated 112 protected KeyStroke rightKey; 113 114 // Transient variables (recalculated each time TabbedPane is layed out) 115 116 protected int tabRuns[] = new int[10]; 117 protected int runCount = 0; 118 protected int selectedRun = -1; 119 protected Rectangle rects[] = new Rectangle[0]; 120 protected int maxTabHeight; 121 protected int maxTabWidth; 122 123 // Listeners 124 125 protected ChangeListener tabChangeListener; 126 protected PropertyChangeListener propertyChangeListener; 127 protected MouseListener mouseListener; 128 protected FocusListener focusListener; 129 130 // Private instance data 131 132 private final Insets currentPadInsets = new Insets(0, 0, 0, 0); 133 private final Insets currentTabAreaInsets = new Insets(0, 0, 0, 0); 134 135 private Component visibleComponent; 136 // PENDING(api): See comment for ContainerHandler 137 private Vector<View> htmlViews; 138 139 private Hashtable<Integer, Integer> mnemonicToIndexMap; 140 141 /** 142 * InputMap used for mnemonics. Only non-null if the JTabbedPane has 143 * mnemonics associated with it. Lazily created in initMnemonics. 144 */ 145 private InputMap mnemonicInputMap; 146 147 // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT 148 private ScrollableTabSupport tabScroller; 149 150 private TabContainer tabContainer; 151 152 /** 153 * A rectangle used for general layout calculations in order 154 * to avoid constructing many new Rectangles on the fly. 155 */ 156 protected transient Rectangle calcRect = new Rectangle(0, 0, 0, 0); 157 158 /** 159 * Tab that has focus. 160 */ 161 private int focusIndex; 162 163 /** 164 * Combined listeners. 165 */ 166 private Handler handler; 167 168 /** 169 * Index of the tab the mouse is over. 170 */ 171 private int rolloverTabIndex; 172 173 /** 174 * This is set to true when a component is added/removed from the tab 175 * pane and set to false when layout happens. If true it indicates that 176 * tabRuns is not valid and shouldn't be used. 177 */ 178 private boolean isRunsDirty; 179 180 private boolean calculatedBaseline; 181 private int baseline; 182 183 // UI creation 184 185 public static ComponentUI createUI(final JComponent c) { 186 return new AquaTabbedPaneCopyFromBasicUI(); 187 } 188 189 // MACOSX adding accessor for superclass 190 protected Component getTabComponentAt(final int i) { 191 return tabPane.getTabComponentAt(i); 192 } 193 // END MACOSX 194 195 static void loadActionMap(final LazyActionMap map) { 196 map.put(new Actions(Actions.NEXT)); 197 map.put(new Actions(Actions.PREVIOUS)); 198 map.put(new Actions(Actions.RIGHT)); 199 map.put(new Actions(Actions.LEFT)); 200 map.put(new Actions(Actions.UP)); 201 map.put(new Actions(Actions.DOWN)); 202 map.put(new Actions(Actions.PAGE_UP)); 203 map.put(new Actions(Actions.PAGE_DOWN)); 204 map.put(new Actions(Actions.REQUEST_FOCUS)); 205 map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE)); 206 map.put(new Actions(Actions.SET_SELECTED)); 207 map.put(new Actions(Actions.SELECT_FOCUSED)); 208 map.put(new Actions(Actions.SCROLL_FORWARD)); 209 map.put(new Actions(Actions.SCROLL_BACKWARD)); 210 } 211 212 // UI Installation/De-installation 213 214 public void installUI(final JComponent c) { 215 this.tabPane = (JTabbedPane)c; 216 217 calculatedBaseline = false; 218 rolloverTabIndex = -1; 219 focusIndex = -1; 220 c.setLayout(createLayoutManager()); 221 installComponents(); 222 installDefaults(); 223 installListeners(); 224 installKeyboardActions(); 225 } 226 227 public void uninstallUI(final JComponent c) { 228 uninstallKeyboardActions(); 229 uninstallListeners(); 230 uninstallDefaults(); 231 uninstallComponents(); 232 c.setLayout(null); 233 234 this.tabPane = null; 235 } 236 237 /** 238 * Invoked by {@code installUI} to create 239 * a layout manager object to manage 240 * the {@code JTabbedPane}. 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(Integer.valueOf(mnemonic), Integer.valueOf(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))}. 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}. 551 * {@code index} 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}. 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} 769 * object. Tab placement may be either: 770 * {@code JTabbedPane.TOP}, {@code JTabbedPane.BOTTOM}, 771 * {@code JTabbedPane.LEFT}, or {@code JTabbedPane.RIGHT}. 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} 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 static final String NEXT = "navigateNext"; 2015 static final String PREVIOUS = "navigatePrevious"; 2016 static final String RIGHT = "navigateRight"; 2017 static final String LEFT = "navigateLeft"; 2018 static final String UP = "navigateUp"; 2019 static final String DOWN = "navigateDown"; 2020 static final String PAGE_UP = "navigatePageUp"; 2021 static final String PAGE_DOWN = "navigatePageDown"; 2022 static final String REQUEST_FOCUS = "requestFocus"; 2023 static final String REQUEST_FOCUS_FOR_VISIBLE = "requestFocusForVisibleComponent"; 2024 static final String SET_SELECTED = "setSelectedIndex"; 2025 static final String SELECT_FOCUSED = "selectTabWithFocus"; 2026 static final String SCROLL_FORWARD = "scrollTabsForwardAction"; 2027 static final 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(Integer.valueOf(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 int tabCount = tabPane.getTabCount(); 2187 int total = 0; 2188 if (tabCount > 0) { 2189 final int maxTabHeight = calculateMaxTabHeight(tabPlacement); 2190 total = calculateTabAreaHeight(tabPlacement, 1, maxTabHeight); 2191 } 2192 return total; 2193 } 2194 2195 protected int preferredTabAreaWidth(final int tabPlacement, final int height) { 2196 final int tabCount = tabPane.getTabCount(); 2197 int total = 0; 2198 if (tabCount > 0) { 2199 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2200 total = calculateTabAreaWidth(tabPlacement, 1, maxTabWidth); 2201 } 2202 return total; 2203 } 2204 2205 @SuppressWarnings("deprecation") 2206 public void layoutContainer(final Container parent) { 2207 /* Some of the code in this method deals with changing the 2208 * visibility of components to hide and show the contents for the 2209 * selected tab. This is older code that has since been duplicated 2210 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2211 * changes to happen sooner (see the note there). This code remains 2212 * for backward compatibility as there are some cases, such as 2213 * subclasses that don't fireStateChanged() where it may be used. 2214 * Any changes here need to be kept in synch with 2215 * JTabbedPane.fireStateChanged(). 2216 */ 2217 2218 setRolloverTab(-1); 2219 2220 final int tabPlacement = tabPane.getTabPlacement(); 2221 final Insets insets = tabPane.getInsets(); 2222 final int selectedIndex = tabPane.getSelectedIndex(); 2223 final Component visibleComponent = getVisibleComponent(); 2224 2225 calculateLayoutInfo(); 2226 2227 Component selectedComponent = null; 2228 if (selectedIndex < 0) { 2229 if (visibleComponent != null) { 2230 // The last tab was removed, so remove the component 2231 setVisibleComponent(null); 2232 } 2233 } else { 2234 selectedComponent = tabPane.getComponentAt(selectedIndex); 2235 } 2236 int cx, cy, cw, ch; 2237 int totalTabWidth = 0; 2238 int totalTabHeight = 0; 2239 final Insets contentInsets = getContentBorderInsets(tabPlacement); 2240 2241 boolean shouldChangeFocus = false; 2242 2243 // In order to allow programs to use a single component 2244 // as the display for multiple tabs, we will not change 2245 // the visible compnent if the currently selected tab 2246 // has a null component. This is a bit dicey, as we don't 2247 // explicitly state we support this in the spec, but since 2248 // programs are now depending on this, we're making it work. 2249 // 2250 if (selectedComponent != null) { 2251 if (selectedComponent != visibleComponent && visibleComponent != null) { 2252 if (SwingUtilities.findFocusOwner(visibleComponent) != null) { 2253 shouldChangeFocus = true; 2254 } 2255 } 2256 setVisibleComponent(selectedComponent); 2257 } 2258 2259 final Rectangle bounds = tabPane.getBounds(); 2260 final int numChildren = tabPane.getComponentCount(); 2261 2262 if (numChildren > 0) { 2263 2264 switch (tabPlacement) { 2265 case LEFT: 2266 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2267 cx = insets.left + totalTabWidth + contentInsets.left; 2268 cy = insets.top + contentInsets.top; 2269 break; 2270 case RIGHT: 2271 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2272 cx = insets.left + contentInsets.left; 2273 cy = insets.top + contentInsets.top; 2274 break; 2275 case BOTTOM: 2276 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2277 cx = insets.left + contentInsets.left; 2278 cy = insets.top + contentInsets.top; 2279 break; 2280 case TOP: 2281 default: 2282 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2283 cx = insets.left + contentInsets.left; 2284 cy = insets.top + totalTabHeight + contentInsets.top; 2285 } 2286 2287 cw = bounds.width - totalTabWidth - insets.left - insets.right - contentInsets.left - contentInsets.right; 2288 ch = bounds.height - totalTabHeight - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom; 2289 2290 for (int i = 0; i < numChildren; i++) { 2291 final Component child = tabPane.getComponent(i); 2292 if (child == tabContainer) { 2293 2294 final int tabContainerWidth = totalTabWidth == 0 ? bounds.width : totalTabWidth + insets.left + insets.right + contentInsets.left + contentInsets.right; 2295 final int tabContainerHeight = totalTabHeight == 0 ? bounds.height : totalTabHeight + insets.top + insets.bottom + contentInsets.top + contentInsets.bottom; 2296 2297 int tabContainerX = 0; 2298 int tabContainerY = 0; 2299 if (tabPlacement == BOTTOM) { 2300 tabContainerY = bounds.height - tabContainerHeight; 2301 } else if (tabPlacement == RIGHT) { 2302 tabContainerX = bounds.width - tabContainerWidth; 2303 } 2304 child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight); 2305 } else { 2306 child.setBounds(cx, cy, cw, ch); 2307 } 2308 } 2309 } 2310 layoutTabComponents(); 2311 if (shouldChangeFocus) { 2312 if (!requestFocusForVisibleComponent()) { 2313 tabPane.requestFocus(); 2314 } 2315 } 2316 } 2317 2318 public void calculateLayoutInfo() { 2319 final int tabCount = tabPane.getTabCount(); 2320 assureRectsCreated(tabCount); 2321 calculateTabRects(tabPane.getTabPlacement(), tabCount); 2322 isRunsDirty = false; 2323 } 2324 2325 protected void layoutTabComponents() { 2326 if (tabContainer == null) { 2327 return; 2328 } 2329 final Rectangle rect = new Rectangle(); 2330 final Point delta = new Point(-tabContainer.getX(), -tabContainer.getY()); 2331 if (scrollableTabLayoutEnabled()) { 2332 translatePointToTabPanel(0, 0, delta); 2333 } 2334 for (int i = 0; i < tabPane.getTabCount(); i++) { 2335 final Component c = tabPane.getTabComponentAt(i); 2336 if (c == null) { 2337 continue; 2338 } 2339 getTabBounds(i, rect); 2340 final Dimension preferredSize = c.getPreferredSize(); 2341 final Insets insets = getTabInsets(tabPane.getTabPlacement(), i); 2342 final int outerX = rect.x + insets.left + delta.x; 2343 final int outerY = rect.y + insets.top + delta.y; 2344 final int outerWidth = rect.width - insets.left - insets.right; 2345 final int outerHeight = rect.height - insets.top - insets.bottom; 2346 // centralize component 2347 final int x = outerX + (outerWidth - preferredSize.width) / 2; 2348 final int y = outerY + (outerHeight - preferredSize.height) / 2; 2349 final int tabPlacement = tabPane.getTabPlacement(); 2350 final boolean isSeleceted = i == tabPane.getSelectedIndex(); 2351 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted), y + getTabLabelShiftY(tabPlacement, i, isSeleceted), preferredSize.width, preferredSize.height); 2352 } 2353 } 2354 2355 protected void calculateTabRects(final int tabPlacement, final int tabCount) { 2356 final FontMetrics metrics = getFontMetrics(); 2357 final Dimension size = tabPane.getSize(); 2358 final Insets insets = tabPane.getInsets(); 2359 final Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 2360 final int fontHeight = metrics.getHeight(); 2361 final int selectedIndex = tabPane.getSelectedIndex(); 2362 int tabRunOverlay; 2363 int i, j; 2364 int x, y; 2365 int returnAt; 2366 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2367 boolean leftToRight = AquaUtils.isLeftToRight(tabPane); 2368 2369 // 2370 // Calculate bounds within which a tab run must fit 2371 // 2372 switch (tabPlacement) { 2373 case LEFT: 2374 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2375 x = insets.left + tabAreaInsets.left; 2376 y = insets.top + tabAreaInsets.top; 2377 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2378 break; 2379 case RIGHT: 2380 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2381 x = size.width - insets.right - tabAreaInsets.right - maxTabWidth; 2382 y = insets.top + tabAreaInsets.top; 2383 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom); 2384 break; 2385 case BOTTOM: 2386 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2387 x = insets.left + tabAreaInsets.left; 2388 y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight; 2389 returnAt = size.width - (insets.right + tabAreaInsets.right); 2390 break; 2391 case TOP: 2392 default: 2393 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2394 x = insets.left + tabAreaInsets.left; 2395 y = insets.top + tabAreaInsets.top; 2396 returnAt = size.width - (insets.right + tabAreaInsets.right); 2397 break; 2398 } 2399 2400 tabRunOverlay = getTabRunOverlay(tabPlacement); 2401 2402 runCount = 0; 2403 selectedRun = -1; 2404 2405 if (tabCount == 0) { 2406 return; 2407 } 2408 2409 // Run through tabs and partition them into runs 2410 Rectangle rect; 2411 for (i = 0; i < tabCount; i++) { 2412 rect = rects[i]; 2413 2414 if (!verticalTabRuns) { 2415 // Tabs on TOP or BOTTOM.... 2416 if (i > 0) { 2417 rect.x = rects[i - 1].x + rects[i - 1].width; 2418 } else { 2419 tabRuns[0] = 0; 2420 runCount = 1; 2421 maxTabWidth = 0; 2422 rect.x = x; 2423 } 2424 rect.width = calculateTabWidth(tabPlacement, i, metrics); 2425 maxTabWidth = Math.max(maxTabWidth, rect.width); 2426 2427 // Never move a TAB down a run if it is in the first column. 2428 // Even if there isn't enough room, moving it to a fresh 2429 // line won't help. 2430 if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) { 2431 if (runCount > tabRuns.length - 1) { 2432 expandTabRunsArray(); 2433 } 2434 tabRuns[runCount] = i; 2435 runCount++; 2436 rect.x = x; 2437 } 2438 // Initialize y position in case there's just one run 2439 rect.y = y; 2440 rect.height = maxTabHeight/* - 2*/; 2441 2442 } else { 2443 // Tabs on LEFT or RIGHT... 2444 if (i > 0) { 2445 rect.y = rects[i - 1].y + rects[i - 1].height; 2446 } else { 2447 tabRuns[0] = 0; 2448 runCount = 1; 2449 maxTabHeight = 0; 2450 rect.y = y; 2451 } 2452 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 2453 maxTabHeight = Math.max(maxTabHeight, rect.height); 2454 2455 // Never move a TAB over a run if it is in the first run. 2456 // Even if there isn't enough room, moving it to a fresh 2457 // column won't help. 2458 if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) { 2459 if (runCount > tabRuns.length - 1) { 2460 expandTabRunsArray(); 2461 } 2462 tabRuns[runCount] = i; 2463 runCount++; 2464 rect.y = y; 2465 } 2466 // Initialize x position in case there's just one column 2467 rect.x = x; 2468 rect.width = maxTabWidth/* - 2*/; 2469 2470 } 2471 if (i == selectedIndex) { 2472 selectedRun = runCount - 1; 2473 } 2474 } 2475 2476 if (runCount > 1) { 2477 // Re-distribute tabs in case last run has leftover space 2478 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns ? y : x, returnAt); 2479 2480 selectedRun = getRunForTab(tabCount, selectedIndex); 2481 2482 // Rotate run array so that selected run is first 2483 if (shouldRotateTabRuns(tabPlacement)) { 2484 rotateTabRuns(tabPlacement, selectedRun); 2485 } 2486 } 2487 2488 // Step through runs from back to front to calculate 2489 // tab y locations and to pad runs appropriately 2490 for (i = runCount - 1; i >= 0; i--) { 2491 final int start = tabRuns[i]; 2492 final int next = tabRuns[i == (runCount - 1) ? 0 : i + 1]; 2493 final int end = (next != 0 ? next - 1 : tabCount - 1); 2494 if (!verticalTabRuns) { 2495 for (j = start; j <= end; j++) { 2496 rect = rects[j]; 2497 rect.y = y; 2498 rect.x += getTabRunIndent(tabPlacement, i); 2499 } 2500 if (shouldPadTabRun(tabPlacement, i)) { 2501 padTabRun(tabPlacement, start, end, returnAt); 2502 } 2503 if (tabPlacement == BOTTOM) { 2504 y -= (maxTabHeight - tabRunOverlay); 2505 } else { 2506 y += (maxTabHeight - tabRunOverlay); 2507 } 2508 } else { 2509 for (j = start; j <= end; j++) { 2510 rect = rects[j]; 2511 rect.x = x; 2512 rect.y += getTabRunIndent(tabPlacement, i); 2513 } 2514 if (shouldPadTabRun(tabPlacement, i)) { 2515 padTabRun(tabPlacement, start, end, returnAt); 2516 } 2517 if (tabPlacement == RIGHT) { 2518 x -= (maxTabWidth - tabRunOverlay); 2519 } else { 2520 x += (maxTabWidth - tabRunOverlay); 2521 } 2522 } 2523 } 2524 2525 // Pad the selected tab so that it appears raised in front 2526 padSelectedTab(tabPlacement, selectedIndex); 2527 2528 // if right to left and tab placement on the top or 2529 // the bottom, flip x positions and adjust by widths 2530 if (!leftToRight && !verticalTabRuns) { 2531 final int rightMargin = size.width - (insets.right + tabAreaInsets.right); 2532 for (i = 0; i < tabCount; i++) { 2533 rects[i].x = rightMargin - rects[i].x - rects[i].width; 2534 } 2535 } 2536 } 2537 2538 /* 2539 * Rotates the run-index array so that the selected run is run[0] 2540 */ 2541 protected void rotateTabRuns(final int tabPlacement, final int selectedRun) { 2542 for (int i = 0; i < selectedRun; i++) { 2543 final int save = tabRuns[0]; 2544 for (int j = 1; j < runCount; j++) { 2545 tabRuns[j - 1] = tabRuns[j]; 2546 } 2547 tabRuns[runCount - 1] = save; 2548 } 2549 } 2550 2551 protected void normalizeTabRuns(final int tabPlacement, final int tabCount, final int start, final int max) { 2552 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2553 int run = runCount - 1; 2554 boolean keepAdjusting = true; 2555 double weight = 1.25; 2556 2557 // At this point the tab runs are packed to fit as many 2558 // tabs as possible, which can leave the last run with a lot 2559 // of extra space (resulting in very fat tabs on the last run). 2560 // So we'll attempt to distribute this extra space more evenly 2561 // across the runs in order to make the runs look more consistent. 2562 // 2563 // Starting with the last run, determine whether the last tab in 2564 // the previous run would fit (generously) in this run; if so, 2565 // move tab to current run and shift tabs accordingly. Cycle 2566 // through remaining runs using the same algorithm. 2567 // 2568 while (keepAdjusting) { 2569 final int last = lastTabInRun(tabCount, run); 2570 final int prevLast = lastTabInRun(tabCount, run - 1); 2571 int end; 2572 int prevLastLen; 2573 2574 if (!verticalTabRuns) { 2575 end = rects[last].x + rects[last].width; 2576 prevLastLen = (int)(maxTabWidth * weight); 2577 } else { 2578 end = rects[last].y + rects[last].height; 2579 prevLastLen = (int)(maxTabHeight * weight * 2); 2580 } 2581 2582 // Check if the run has enough extra space to fit the last tab 2583 // from the previous row... 2584 if (max - end > prevLastLen) { 2585 2586 // Insert tab from previous row and shift rest over 2587 tabRuns[run] = prevLast; 2588 if (!verticalTabRuns) { 2589 rects[prevLast].x = start; 2590 } else { 2591 rects[prevLast].y = start; 2592 } 2593 for (int i = prevLast + 1; i <= last; i++) { 2594 if (!verticalTabRuns) { 2595 rects[i].x = rects[i - 1].x + rects[i - 1].width; 2596 } else { 2597 rects[i].y = rects[i - 1].y + rects[i - 1].height; 2598 } 2599 } 2600 2601 } else if (run == runCount - 1) { 2602 // no more room left in last run, so we're done! 2603 keepAdjusting = false; 2604 } 2605 if (run - 1 > 0) { 2606 // check previous run next... 2607 run -= 1; 2608 } else { 2609 // check last run again...but require a higher ratio 2610 // of extraspace-to-tabsize because we don't want to 2611 // end up with too many tabs on the last run! 2612 run = runCount - 1; 2613 weight += .25; 2614 } 2615 } 2616 } 2617 2618 protected void padTabRun(final int tabPlacement, final int start, final int end, final int max) { 2619 final Rectangle lastRect = rects[end]; 2620 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 2621 final int runWidth = (lastRect.x + lastRect.width) - rects[start].x; 2622 final int deltaWidth = max - (lastRect.x + lastRect.width); 2623 final float factor = (float)deltaWidth / (float)runWidth; 2624 2625 for (int j = start; j <= end; j++) { 2626 final Rectangle pastRect = rects[j]; 2627 if (j > start) { 2628 pastRect.x = rects[j - 1].x + rects[j - 1].width; 2629 } 2630 pastRect.width += Math.round(pastRect.width * factor); 2631 } 2632 lastRect.width = max - lastRect.x; 2633 } else { 2634 final int runHeight = (lastRect.y + lastRect.height) - rects[start].y; 2635 final int deltaHeight = max - (lastRect.y + lastRect.height); 2636 final float factor = (float)deltaHeight / (float)runHeight; 2637 2638 for (int j = start; j <= end; j++) { 2639 final Rectangle pastRect = rects[j]; 2640 if (j > start) { 2641 pastRect.y = rects[j - 1].y + rects[j - 1].height; 2642 } 2643 pastRect.height += Math.round(pastRect.height * factor); 2644 } 2645 lastRect.height = max - lastRect.y; 2646 } 2647 } 2648 2649 protected void padSelectedTab(final int tabPlacement, final int selectedIndex) { 2650 2651 if (selectedIndex >= 0) { 2652 final Rectangle selRect = rects[selectedIndex]; 2653 final Insets padInsets = getSelectedTabPadInsets(tabPlacement); 2654 selRect.x -= padInsets.left; 2655 selRect.width += (padInsets.left + padInsets.right); 2656 selRect.y -= padInsets.top; 2657 selRect.height += (padInsets.top + padInsets.bottom); 2658 2659 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT 2660 // do not expand selected tab more then necessary 2661 final Dimension size = tabPane.getSize(); 2662 final Insets insets = tabPane.getInsets(); 2663 2664 if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) { 2665 final int top = insets.top - selRect.y; 2666 if (top > 0) { 2667 selRect.y += top; 2668 selRect.height -= top; 2669 } 2670 final int bottom = (selRect.y + selRect.height) + insets.bottom - size.height; 2671 if (bottom > 0) { 2672 selRect.height -= bottom; 2673 } 2674 } else { 2675 final int left = insets.left - selRect.x; 2676 if (left > 0) { 2677 selRect.x += left; 2678 selRect.width -= left; 2679 } 2680 final int right = (selRect.x + selRect.width) + insets.right - size.width; 2681 if (right > 0) { 2682 selRect.width -= right; 2683 } 2684 } 2685 } 2686 } 2687 } 2688 } 2689 2690 class TabbedPaneScrollLayout extends TabbedPaneLayout { 2691 2692 protected int preferredTabAreaHeight(final int tabPlacement, final int width) { 2693 return calculateMaxTabHeight(tabPlacement); 2694 } 2695 2696 protected int preferredTabAreaWidth(final int tabPlacement, final int height) { 2697 return calculateMaxTabWidth(tabPlacement); 2698 } 2699 2700 @SuppressWarnings("deprecation") 2701 public void layoutContainer(final Container parent) { 2702 /* Some of the code in this method deals with changing the 2703 * visibility of components to hide and show the contents for the 2704 * selected tab. This is older code that has since been duplicated 2705 * in JTabbedPane.fireStateChanged(), so as to allow visibility 2706 * changes to happen sooner (see the note there). This code remains 2707 * for backward compatibility as there are some cases, such as 2708 * subclasses that don't fireStateChanged() where it may be used. 2709 * Any changes here need to be kept in synch with 2710 * JTabbedPane.fireStateChanged(). 2711 */ 2712 2713 setRolloverTab(-1); 2714 2715 final int tabPlacement = tabPane.getTabPlacement(); 2716 final int tabCount = tabPane.getTabCount(); 2717 final Insets insets = tabPane.getInsets(); 2718 final int selectedIndex = tabPane.getSelectedIndex(); 2719 final Component visibleComponent = getVisibleComponent(); 2720 2721 calculateLayoutInfo(); 2722 2723 Component selectedComponent = null; 2724 if (selectedIndex < 0) { 2725 if (visibleComponent != null) { 2726 // The last tab was removed, so remove the component 2727 setVisibleComponent(null); 2728 } 2729 } else { 2730 selectedComponent = tabPane.getComponentAt(selectedIndex); 2731 } 2732 2733 if (tabPane.getTabCount() == 0) { 2734 tabScroller.croppedEdge.resetParams(); 2735 tabScroller.scrollForwardButton.setVisible(false); 2736 tabScroller.scrollBackwardButton.setVisible(false); 2737 return; 2738 } 2739 2740 boolean shouldChangeFocus = false; 2741 2742 // In order to allow programs to use a single component 2743 // as the display for multiple tabs, we will not change 2744 // the visible compnent if the currently selected tab 2745 // has a null component. This is a bit dicey, as we don't 2746 // explicitly state we support this in the spec, but since 2747 // programs are now depending on this, we're making it work. 2748 // 2749 if (selectedComponent != null) { 2750 if (selectedComponent != visibleComponent && visibleComponent != null) { 2751 if (SwingUtilities.findFocusOwner(visibleComponent) != null) { 2752 shouldChangeFocus = true; 2753 } 2754 } 2755 setVisibleComponent(selectedComponent); 2756 } 2757 int tx, ty, tw, th; // tab area bounds 2758 int cx, cy, cw, ch; // content area bounds 2759 final Insets contentInsets = getContentBorderInsets(tabPlacement); 2760 final Rectangle bounds = tabPane.getBounds(); 2761 final int numChildren = tabPane.getComponentCount(); 2762 2763 if (numChildren > 0) { 2764 switch (tabPlacement) { 2765 case LEFT: 2766 // calculate tab area bounds 2767 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2768 th = bounds.height - insets.top - insets.bottom; 2769 tx = insets.left; 2770 ty = insets.top; 2771 2772 // calculate content area bounds 2773 cx = tx + tw + contentInsets.left; 2774 cy = ty + contentInsets.top; 2775 cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right; 2776 ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom; 2777 break; 2778 case RIGHT: 2779 // calculate tab area bounds 2780 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); 2781 th = bounds.height - insets.top - insets.bottom; 2782 tx = bounds.width - insets.right - tw; 2783 ty = insets.top; 2784 2785 // calculate content area bounds 2786 cx = insets.left + contentInsets.left; 2787 cy = insets.top + contentInsets.top; 2788 cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right; 2789 ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom; 2790 break; 2791 case BOTTOM: 2792 // calculate tab area bounds 2793 tw = bounds.width - insets.left - insets.right; 2794 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2795 tx = insets.left; 2796 ty = bounds.height - insets.bottom - th; 2797 2798 // calculate content area bounds 2799 cx = insets.left + contentInsets.left; 2800 cy = insets.top + contentInsets.top; 2801 cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right; 2802 ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom; 2803 break; 2804 case TOP: 2805 default: 2806 // calculate tab area bounds 2807 tw = bounds.width - insets.left - insets.right; 2808 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); 2809 tx = insets.left; 2810 ty = insets.top; 2811 2812 // calculate content area bounds 2813 cx = tx + contentInsets.left; 2814 cy = ty + th + contentInsets.top; 2815 cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right; 2816 ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom; 2817 } 2818 2819 for (int i = 0; i < numChildren; i++) { 2820 final Component child = tabPane.getComponent(i); 2821 2822 if (tabScroller != null && child == tabScroller.viewport) { 2823 final JViewport viewport = (JViewport)child; 2824 final Rectangle viewRect = viewport.getViewRect(); 2825 int vw = tw; 2826 int vh = th; 2827 final Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize(); 2828 switch (tabPlacement) { 2829 case LEFT: 2830 case RIGHT: 2831 final int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 2832 if (totalTabHeight > th) { 2833 // Allow space for scrollbuttons 2834 vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0; 2835 if (totalTabHeight - viewRect.y <= vh) { 2836 // Scrolled to the end, so ensure the viewport size is 2837 // such that the scroll offset aligns with a tab 2838 vh = totalTabHeight - viewRect.y; 2839 } 2840 } 2841 break; 2842 case BOTTOM: 2843 case TOP: 2844 default: 2845 final int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 2846 if (totalTabWidth > tw) { 2847 // Need to allow space for scrollbuttons 2848 vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0; 2849 if (totalTabWidth - viewRect.x <= vw) { 2850 // Scrolled to the end, so ensure the viewport size is 2851 // such that the scroll offset aligns with a tab 2852 vw = totalTabWidth - viewRect.x; 2853 } 2854 } 2855 } 2856 child.setBounds(tx, ty, vw, vh); 2857 2858 } else if (tabScroller != null && (child == tabScroller.scrollForwardButton || child == tabScroller.scrollBackwardButton)) { 2859 final Component scrollbutton = child; 2860 final Dimension bsize = scrollbutton.getPreferredSize(); 2861 int bx = 0; 2862 int by = 0; 2863 final int bw = bsize.width; 2864 final int bh = bsize.height; 2865 boolean visible = false; 2866 2867 switch (tabPlacement) { 2868 case LEFT: 2869 case RIGHT: 2870 final int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height; 2871 if (totalTabHeight > th) { 2872 visible = true; 2873 bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx); 2874 by = (child == tabScroller.scrollForwardButton) ? bounds.height - insets.bottom - bsize.height : bounds.height - insets.bottom - 2 * bsize.height; 2875 } 2876 break; 2877 2878 case BOTTOM: 2879 case TOP: 2880 default: 2881 final int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width; 2882 2883 if (totalTabWidth > tw) { 2884 visible = true; 2885 bx = (child == tabScroller.scrollForwardButton) ? bounds.width - insets.left - bsize.width : bounds.width - insets.left - 2 * bsize.width; 2886 by = (tabPlacement == TOP ? ty + th - bsize.height : ty); 2887 } 2888 } 2889 child.setVisible(visible); 2890 if (visible) { 2891 child.setBounds(bx, by, bw, bh); 2892 } 2893 2894 } else { 2895 // All content children... 2896 child.setBounds(cx, cy, cw, ch); 2897 } 2898 } 2899 super.layoutTabComponents(); 2900 layoutCroppedEdge(); 2901 if (shouldChangeFocus) { 2902 if (!requestFocusForVisibleComponent()) { 2903 tabPane.requestFocus(); 2904 } 2905 } 2906 } 2907 } 2908 2909 private void layoutCroppedEdge() { 2910 tabScroller.croppedEdge.resetParams(); 2911 final Rectangle viewRect = tabScroller.viewport.getViewRect(); 2912 int cropline; 2913 for (int i = 0; i < rects.length; i++) { 2914 final Rectangle tabRect = rects[i]; 2915 switch (tabPane.getTabPlacement()) { 2916 case LEFT: 2917 case RIGHT: 2918 cropline = viewRect.y + viewRect.height; 2919 if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) { 2920 tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1, -currentTabAreaInsets.left, 0); 2921 } 2922 break; 2923 case TOP: 2924 case BOTTOM: 2925 default: 2926 cropline = viewRect.x + viewRect.width; 2927 if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) { 2928 tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1, 0, -currentTabAreaInsets.top); 2929 } 2930 } 2931 } 2932 } 2933 2934 protected void calculateTabRects(final int tabPlacement, final int tabCount) { 2935 final FontMetrics metrics = getFontMetrics(); 2936 final Dimension size = tabPane.getSize(); 2937 final Insets insets = tabPane.getInsets(); 2938 final Insets tabAreaInsets = getTabAreaInsets(tabPlacement); 2939 final int fontHeight = metrics.getHeight(); 2940 final int selectedIndex = tabPane.getSelectedIndex(); 2941 int i; 2942 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT); 2943 boolean leftToRight = AquaUtils.isLeftToRight(tabPane); 2944 final int x = tabAreaInsets.left; 2945 final int y = tabAreaInsets.top; 2946 int totalWidth = 0; 2947 int totalHeight = 0; 2948 2949 // 2950 // Calculate bounds within which a tab run must fit 2951 // 2952 switch (tabPlacement) { 2953 case LEFT: 2954 case RIGHT: 2955 maxTabWidth = calculateMaxTabWidth(tabPlacement); 2956 break; 2957 case BOTTOM: 2958 case TOP: 2959 default: 2960 maxTabHeight = calculateMaxTabHeight(tabPlacement); 2961 } 2962 2963 runCount = 0; 2964 selectedRun = -1; 2965 2966 if (tabCount == 0) { 2967 return; 2968 } 2969 2970 selectedRun = 0; 2971 runCount = 1; 2972 2973 // Run through tabs and lay them out in a single run 2974 Rectangle rect; 2975 for (i = 0; i < tabCount; i++) { 2976 rect = rects[i]; 2977 2978 if (!verticalTabRuns) { 2979 // Tabs on TOP or BOTTOM.... 2980 if (i > 0) { 2981 rect.x = rects[i - 1].x + rects[i - 1].width; 2982 } else { 2983 tabRuns[0] = 0; 2984 maxTabWidth = 0; 2985 totalHeight += maxTabHeight; 2986 rect.x = x; 2987 } 2988 rect.width = calculateTabWidth(tabPlacement, i, metrics); 2989 totalWidth = rect.x + rect.width; 2990 maxTabWidth = Math.max(maxTabWidth, rect.width); 2991 2992 rect.y = y; 2993 rect.height = maxTabHeight/* - 2*/; 2994 2995 } else { 2996 // Tabs on LEFT or RIGHT... 2997 if (i > 0) { 2998 rect.y = rects[i - 1].y + rects[i - 1].height; 2999 } else { 3000 tabRuns[0] = 0; 3001 maxTabHeight = 0; 3002 totalWidth = maxTabWidth; 3003 rect.y = y; 3004 } 3005 rect.height = calculateTabHeight(tabPlacement, i, fontHeight); 3006 totalHeight = rect.y + rect.height; 3007 maxTabHeight = Math.max(maxTabHeight, rect.height); 3008 3009 rect.x = x; 3010 rect.width = maxTabWidth/* - 2*/; 3011 3012 } 3013 } 3014 3015 if (tabsOverlapBorder) { 3016 // Pad the selected tab so that it appears raised in front 3017 padSelectedTab(tabPlacement, selectedIndex); 3018 } 3019 3020 // if right to left and tab placement on the top or 3021 // the bottom, flip x positions and adjust by widths 3022 if (!leftToRight && !verticalTabRuns) { 3023 final int rightMargin = size.width - (insets.right + tabAreaInsets.right); 3024 for (i = 0; i < tabCount; i++) { 3025 rects[i].x = rightMargin - rects[i].x - rects[i].width; 3026 } 3027 } 3028 tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight)); 3029 } 3030 } 3031 3032 private class ScrollableTabSupport implements ActionListener, ChangeListener { 3033 public ScrollableTabViewport viewport; 3034 public ScrollableTabPanel tabPanel; 3035 public JButton scrollForwardButton; 3036 public JButton scrollBackwardButton; 3037 public CroppedEdge croppedEdge; 3038 public int leadingTabIndex; 3039 3040 private final Point tabViewPosition = new Point(0, 0); 3041 3042 ScrollableTabSupport(final int tabPlacement) { 3043 viewport = new ScrollableTabViewport(); 3044 tabPanel = new ScrollableTabPanel(); 3045 viewport.setView(tabPanel); 3046 viewport.addChangeListener(this); 3047 croppedEdge = new CroppedEdge(); 3048 createButtons(); 3049 } 3050 3051 /** 3052 * Recreates the scroll buttons and adds them to the TabbedPane. 3053 */ 3054 void createButtons() { 3055 if (scrollForwardButton != null) { 3056 tabPane.remove(scrollForwardButton); 3057 scrollForwardButton.removeActionListener(this); 3058 tabPane.remove(scrollBackwardButton); 3059 scrollBackwardButton.removeActionListener(this); 3060 } 3061 final int tabPlacement = tabPane.getTabPlacement(); 3062 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3063 scrollForwardButton = createScrollButton(EAST); 3064 scrollBackwardButton = createScrollButton(WEST); 3065 3066 } else { // tabPlacement = LEFT || RIGHT 3067 scrollForwardButton = createScrollButton(SOUTH); 3068 scrollBackwardButton = createScrollButton(NORTH); 3069 } 3070 scrollForwardButton.addActionListener(this); 3071 scrollBackwardButton.addActionListener(this); 3072 tabPane.add(scrollForwardButton); 3073 tabPane.add(scrollBackwardButton); 3074 } 3075 3076 public void scrollForward(final int tabPlacement) { 3077 final Dimension viewSize = viewport.getViewSize(); 3078 final Rectangle viewRect = viewport.getViewRect(); 3079 3080 if (tabPlacement == TOP || tabPlacement == BOTTOM) { 3081 if (viewRect.width >= viewSize.width - viewRect.x) { 3082 return; // no room left to scroll 3083 } 3084 } else { // tabPlacement == LEFT || tabPlacement == RIGHT 3085 if (viewRect.height >= viewSize.height - viewRect.y) { 3086 return; 3087 } 3088 } 3089 setLeadingTabIndex(tabPlacement, leadingTabIndex + 1); 3090 } 3091 3092 public void scrollBackward(final int tabPlacement) { 3093 if (leadingTabIndex == 0) { 3094 return; // no room left to scroll 3095 } 3096 setLeadingTabIndex(tabPlacement, leadingTabIndex - 1); 3097 } 3098 3099 public void setLeadingTabIndex(final int tabPlacement, final int index) { 3100 leadingTabIndex = index; 3101 final Dimension viewSize = viewport.getViewSize(); 3102 final Rectangle viewRect = viewport.getViewRect(); 3103 3104 switch (tabPlacement) { 3105 case TOP: 3106 case BOTTOM: 3107 tabViewPosition.x = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].x; 3108 3109 if ((viewSize.width - tabViewPosition.x) < viewRect.width) { 3110 // We've scrolled to the end, so adjust the viewport size 3111 // to ensure the view position remains aligned on a tab boundary 3112 final Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x, viewRect.height); 3113 viewport.setExtentSize(extentSize); 3114 } 3115 break; 3116 case LEFT: 3117 case RIGHT: 3118 tabViewPosition.y = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].y; 3119 3120 if ((viewSize.height - tabViewPosition.y) < viewRect.height) { 3121 // We've scrolled to the end, so adjust the viewport size 3122 // to ensure the view position remains aligned on a tab boundary 3123 final Dimension extentSize = new Dimension(viewRect.width, viewSize.height - tabViewPosition.y); 3124 viewport.setExtentSize(extentSize); 3125 } 3126 } 3127 viewport.setViewPosition(tabViewPosition); 3128 } 3129 3130 public void stateChanged(final ChangeEvent e) { 3131 updateView(); 3132 } 3133 3134 private void updateView() { 3135 final int tabPlacement = tabPane.getTabPlacement(); 3136 final int tabCount = tabPane.getTabCount(); 3137 final Rectangle vpRect = viewport.getBounds(); 3138 final Dimension viewSize = viewport.getViewSize(); 3139 final Rectangle viewRect = viewport.getViewRect(); 3140 3141 leadingTabIndex = getClosestTab(viewRect.x, viewRect.y); 3142 3143 // If the tab isn't right aligned, adjust it. 3144 if (leadingTabIndex + 1 < tabCount) { 3145 switch (tabPlacement) { 3146 case TOP: 3147 case BOTTOM: 3148 if (rects[leadingTabIndex].x < viewRect.x) { 3149 leadingTabIndex++; 3150 } 3151 break; 3152 case LEFT: 3153 case RIGHT: 3154 if (rects[leadingTabIndex].y < viewRect.y) { 3155 leadingTabIndex++; 3156 } 3157 break; 3158 } 3159 } 3160 final Insets contentInsets = getContentBorderInsets(tabPlacement); 3161 switch (tabPlacement) { 3162 case LEFT: 3163 tabPane.repaint(vpRect.x + vpRect.width, vpRect.y, contentInsets.left, vpRect.height); 3164 scrollBackwardButton.setEnabled(viewRect.y > 0 && leadingTabIndex > 0); 3165 scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.height - viewRect.y > viewRect.height); 3166 break; 3167 case RIGHT: 3168 tabPane.repaint(vpRect.x - contentInsets.right, vpRect.y, contentInsets.right, vpRect.height); 3169 scrollBackwardButton.setEnabled(viewRect.y > 0 && leadingTabIndex > 0); 3170 scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.height - viewRect.y > viewRect.height); 3171 break; 3172 case BOTTOM: 3173 tabPane.repaint(vpRect.x, vpRect.y - contentInsets.bottom, vpRect.width, contentInsets.bottom); 3174 scrollBackwardButton.setEnabled(viewRect.x > 0 && leadingTabIndex > 0); 3175 scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.width - viewRect.x > viewRect.width); 3176 break; 3177 case TOP: 3178 default: 3179 tabPane.repaint(vpRect.x, vpRect.y + vpRect.height, vpRect.width, contentInsets.top); 3180 scrollBackwardButton.setEnabled(viewRect.x > 0 && leadingTabIndex > 0); 3181 scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.width - viewRect.x > viewRect.width); 3182 } 3183 } 3184 3185 /** 3186 * ActionListener for the scroll buttons. 3187 */ 3188 public void actionPerformed(final ActionEvent e) { 3189 final ActionMap map = tabPane.getActionMap(); 3190 3191 if (map != null) { 3192 String actionKey; 3193 3194 if (e.getSource() == scrollForwardButton) { 3195 actionKey = "scrollTabsForwardAction"; 3196 } else { 3197 actionKey = "scrollTabsBackwardAction"; 3198 } 3199 final Action action = map.get(actionKey); 3200 3201 if (action != null && action.isEnabled()) { 3202 action.actionPerformed(new ActionEvent(tabPane, ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers())); 3203 } 3204 } 3205 } 3206 3207 public String toString() { 3208 return new String("viewport.viewSize=" + viewport.getViewSize() + "\n" + "viewport.viewRectangle=" + viewport.getViewRect() + "\n" + "leadingTabIndex=" + leadingTabIndex + "\n" + "tabViewPosition=" + tabViewPosition); 3209 } 3210 3211 } 3212 3213 @SuppressWarnings("serial") // Superclass is not serializable across versions 3214 private class ScrollableTabViewport extends JViewport implements UIResource { 3215 public ScrollableTabViewport() { 3216 super(); 3217 setName("TabbedPane.scrollableViewport"); 3218 setScrollMode(SIMPLE_SCROLL_MODE); 3219 setOpaque(tabPane.isOpaque()); 3220 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3221 if (bgColor == null) { 3222 bgColor = tabPane.getBackground(); 3223 } 3224 setBackground(bgColor); 3225 } 3226 } 3227 3228 @SuppressWarnings("serial") // Superclass is not serializable across versions 3229 private class ScrollableTabPanel extends JPanel implements UIResource { 3230 public ScrollableTabPanel() { 3231 super(null); 3232 setOpaque(tabPane.isOpaque()); 3233 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground"); 3234 if (bgColor == null) { 3235 bgColor = tabPane.getBackground(); 3236 } 3237 setBackground(bgColor); 3238 } 3239 3240 public void paintComponent(final Graphics g) { 3241 super.paintComponent(g); 3242 AquaTabbedPaneCopyFromBasicUI.this.paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex()); 3243 if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) { 3244 final Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()]; 3245 g.translate(croppedRect.x, croppedRect.y); 3246 tabScroller.croppedEdge.paintComponent(g); 3247 g.translate(-croppedRect.x, -croppedRect.y); 3248 } 3249 } 3250 3251 public void doLayout() { 3252 if (getComponentCount() > 0) { 3253 final Component child = getComponent(0); 3254 child.setBounds(0, 0, getWidth(), getHeight()); 3255 } 3256 } 3257 } 3258 3259 @SuppressWarnings("serial") // Superclass is not serializable across versions 3260 private class ScrollableTabButton extends javax.swing.plaf.basic.BasicArrowButton implements UIResource, SwingConstants { 3261 public ScrollableTabButton(final int direction) { 3262 super(direction, UIManager.getColor("TabbedPane.selected"), UIManager.getColor("TabbedPane.shadow"), UIManager.getColor("TabbedPane.darkShadow"), UIManager.getColor("TabbedPane.highlight")); 3263 } 3264 } 3265 3266 // Controller: event listeners 3267 3268 private class Handler implements ChangeListener, ContainerListener, FocusListener, MouseListener, MouseMotionListener, PropertyChangeListener { 3269 // 3270 // PropertyChangeListener 3271 // 3272 public void propertyChange(final PropertyChangeEvent e) { 3273 final JTabbedPane pane = (JTabbedPane)e.getSource(); 3274 final String name = e.getPropertyName(); 3275 final boolean isScrollLayout = scrollableTabLayoutEnabled(); 3276 if (name == "mnemonicAt") { 3277 updateMnemonics(); 3278 pane.repaint(); 3279 } else if (name == "displayedMnemonicIndexAt") { 3280 pane.repaint(); 3281 } else if (name == "indexForTitle") { 3282 calculatedBaseline = false; 3283 updateHtmlViews((Integer) e.getNewValue(), false); 3284 } else if (name == "tabLayoutPolicy") { 3285 AquaTabbedPaneCopyFromBasicUI.this.uninstallUI(pane); 3286 AquaTabbedPaneCopyFromBasicUI.this.installUI(pane); 3287 calculatedBaseline = false; 3288 } else if (name == "tabPlacement") { 3289 if (scrollableTabLayoutEnabled()) { 3290 tabScroller.createButtons(); 3291 } 3292 calculatedBaseline = false; 3293 } else if (name == "opaque" && isScrollLayout) { 3294 final boolean newVal = ((Boolean)e.getNewValue()).booleanValue(); 3295 tabScroller.tabPanel.setOpaque(newVal); 3296 tabScroller.viewport.setOpaque(newVal); 3297 } else if (name == "background" && isScrollLayout) { 3298 final Color newVal = (Color)e.getNewValue(); 3299 tabScroller.tabPanel.setBackground(newVal); 3300 tabScroller.viewport.setBackground(newVal); 3301 final Color newColor = selectedColor == null ? newVal : selectedColor; 3302 tabScroller.scrollForwardButton.setBackground(newColor); 3303 tabScroller.scrollBackwardButton.setBackground(newColor); 3304 } else if (name == "indexForTabComponent") { 3305 if (tabContainer != null) { 3306 tabContainer.removeUnusedTabComponents(); 3307 } 3308 final Component c = tabPane.getTabComponentAt((Integer)e.getNewValue()); 3309 if (c != null) { 3310 if (tabContainer == null) { 3311 installTabContainer(); 3312 } else { 3313 tabContainer.add(c); 3314 } 3315 } 3316 tabPane.revalidate(); 3317 tabPane.repaint(); 3318 calculatedBaseline = false; 3319 } else if (name == "indexForNullComponent") { 3320 isRunsDirty = true; 3321 updateHtmlViews((Integer) e.getNewValue(), true); 3322 } else if (name == "font") { 3323 calculatedBaseline = false; 3324 } 3325 } 3326 3327 // 3328 // ChangeListener 3329 // 3330 public void stateChanged(final ChangeEvent e) { 3331 final JTabbedPane tabPane = (JTabbedPane)e.getSource(); 3332 tabPane.revalidate(); 3333 tabPane.repaint(); 3334 3335 setFocusIndex(tabPane.getSelectedIndex(), false); 3336 3337 if (scrollableTabLayoutEnabled()) { 3338 final int index = tabPane.getSelectedIndex(); 3339 if (index < rects.length && index != -1) { 3340 tabScroller.tabPanel.scrollRectToVisible((Rectangle)rects[index].clone()); 3341 } 3342 } 3343 } 3344 3345 // 3346 // MouseListener 3347 // 3348 public void mouseClicked(final MouseEvent e) {} 3349 3350 public void mouseReleased(final MouseEvent e) {} 3351 3352 public void mouseEntered(final MouseEvent e) { 3353 setRolloverTab(e.getX(), e.getY()); 3354 } 3355 3356 public void mouseExited(final MouseEvent e) { 3357 setRolloverTab(-1); 3358 } 3359 3360 public void mousePressed(final MouseEvent e) { 3361 if (!tabPane.isEnabled()) { 3362 return; 3363 } 3364 final int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY()); 3365 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) { 3366 if (tabIndex != tabPane.getSelectedIndex()) { 3367 // Clicking on unselected tab, change selection, do NOT 3368 // request focus. 3369 // This will trigger the focusIndex to change by way 3370 // of stateChanged. 3371 tabPane.setSelectedIndex(tabIndex); 3372 } else if (tabPane.isRequestFocusEnabled()) { 3373 // Clicking on selected tab, try and give the tabbedpane 3374 // focus. Repaint will occur in focusGained. 3375 tabPane.requestFocus(); 3376 } 3377 } 3378 } 3379 3380 // 3381 // MouseMotionListener 3382 // 3383 public void mouseDragged(final MouseEvent e) {} 3384 3385 public void mouseMoved(final MouseEvent e) { 3386 setRolloverTab(e.getX(), e.getY()); 3387 } 3388 3389 // 3390 // FocusListener 3391 // 3392 public void focusGained(final FocusEvent e) { 3393 setFocusIndex(tabPane.getSelectedIndex(), true); 3394 } 3395 3396 public void focusLost(final FocusEvent e) { 3397 repaintTab(focusIndex); 3398 } 3399 3400 // 3401 // ContainerListener 3402 // 3403 /* GES 2/3/99: 3404 The container listener code was added to support HTML 3405 rendering of tab titles. 3406 3407 Ideally, we would be able to listen for property changes 3408 when a tab is added or its text modified. At the moment 3409 there are no such events because the Beans spec doesn't 3410 allow 'indexed' property changes (i.e. tab 2's text changed 3411 from A to B). 3412 3413 In order to get around this, we listen for tabs to be added 3414 or removed by listening for the container events. we then 3415 queue up a runnable (so the component has a chance to complete 3416 the add) which checks the tab title of the new component to see 3417 if it requires HTML rendering. 3418 3419 The Views (one per tab title requiring HTML rendering) are 3420 stored in the htmlViews Vector, which is only allocated after 3421 the first time we run into an HTML tab. Note that this vector 3422 is kept in step with the number of pages, and nulls are added 3423 for those pages whose tab title do not require HTML rendering. 3424 3425 This makes it easy for the paint and layout code to tell 3426 whether to invoke the HTML engine without having to check 3427 the string during time-sensitive operations. 3428 3429 When we have added a way to listen for tab additions and 3430 changes to tab text, this code should be removed and 3431 replaced by something which uses that. */ 3432 3433 public void componentAdded(final ContainerEvent e) { 3434 final JTabbedPane tp = (JTabbedPane)e.getContainer(); 3435 final Component child = e.getChild(); 3436 if (child instanceof UIResource) { 3437 return; 3438 } 3439 isRunsDirty = true; 3440 updateHtmlViews(tp.indexOfComponent(child), true); 3441 } 3442 3443 private void updateHtmlViews(int index, boolean inserted) { 3444 final String title = tabPane.getTitleAt(index); 3445 final boolean isHTML = BasicHTML.isHTMLString(title); 3446 if (isHTML) { 3447 if (htmlViews == null) { // Initialize vector 3448 htmlViews = createHTMLVector(); 3449 } else { // Vector already exists 3450 final View v = BasicHTML.createHTMLView(tabPane, title); 3451 setHtmlView(v, inserted, index); 3452 } 3453 } else { // Not HTML 3454 if (htmlViews != null) { // Add placeholder 3455 setHtmlView(null, inserted, index); 3456 } // else nada! 3457 } 3458 updateMnemonics(); 3459 } 3460 3461 private void setHtmlView(View v, boolean inserted, int index) { 3462 if (inserted || index >= htmlViews.size()) { 3463 htmlViews.insertElementAt(v, index); 3464 } else { 3465 htmlViews.setElementAt(v, index); 3466 } 3467 } 3468 3469 public void componentRemoved(final ContainerEvent e) { 3470 final JTabbedPane tp = (JTabbedPane)e.getContainer(); 3471 final Component child = e.getChild(); 3472 if (child instanceof UIResource) { 3473 return; 3474 } 3475 3476 // NOTE 4/15/2002 (joutwate): 3477 // This fix is implemented using client properties since there is 3478 // currently no IndexPropertyChangeEvent. Once 3479 // IndexPropertyChangeEvents have been added this code should be 3480 // modified to use it. 3481 final Integer indexObj = (Integer)tp.getClientProperty("__index_to_remove__"); 3482 if (indexObj != null) { 3483 final int index = indexObj.intValue(); 3484 if (htmlViews != null && htmlViews.size() > index) { 3485 htmlViews.removeElementAt(index); 3486 } 3487 tp.putClientProperty("__index_to_remove__", null); 3488 } 3489 isRunsDirty = true; 3490 updateMnemonics(); 3491 3492 validateFocusIndex(); 3493 } 3494 } 3495 3496 /** 3497 * This class should be treated as a "protected" inner class. 3498 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3499 */ 3500 public class PropertyChangeHandler implements PropertyChangeListener { 3501 // NOTE: This class exists only for backward compatibility. All 3502 // its functionality has been moved into Handler. If you need to add 3503 // new functionality add it to the Handler, but make sure this 3504 // class calls into the Handler. 3505 public void propertyChange(final PropertyChangeEvent e) { 3506 getHandler().propertyChange(e); 3507 } 3508 } 3509 3510 /** 3511 * This class should be treated as a "protected" inner class. 3512 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3513 */ 3514 public class TabSelectionHandler implements ChangeListener { 3515 // NOTE: This class exists only for backward compatibility. All 3516 // its functionality has been moved into Handler. If you need to add 3517 // new functionality add it to the Handler, but make sure this 3518 // class calls into the Handler. 3519 public void stateChanged(final ChangeEvent e) { 3520 getHandler().stateChanged(e); 3521 } 3522 } 3523 3524 /** 3525 * This class should be treated as a "protected" inner class. 3526 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3527 */ 3528 public class MouseHandler extends MouseAdapter { 3529 // NOTE: This class exists only for backward compatibility. All 3530 // its functionality has been moved into Handler. If you need to add 3531 // new functionality add it to the Handler, but make sure this 3532 // class calls into the Handler. 3533 public void mousePressed(final MouseEvent e) { 3534 getHandler().mousePressed(e); 3535 } 3536 } 3537 3538 /** 3539 * This class should be treated as a "protected" inner class. 3540 * Instantiate it only within subclasses of BasicTabbedPaneUI. 3541 */ 3542 public class FocusHandler extends FocusAdapter { 3543 // NOTE: This class exists only for backward compatibility. All 3544 // its functionality has been moved into Handler. If you need to add 3545 // new functionality add it to the Handler, but make sure this 3546 // class calls into the Handler. 3547 public void focusGained(final FocusEvent e) { 3548 getHandler().focusGained(e); 3549 } 3550 3551 public void focusLost(final FocusEvent e) { 3552 getHandler().focusLost(e); 3553 } 3554 } 3555 3556 private Vector<View> createHTMLVector() { 3557 final Vector<View> htmlViews = new Vector<View>(); 3558 final int count = tabPane.getTabCount(); 3559 if (count > 0) { 3560 for (int i = 0; i < count; i++) { 3561 final String title = tabPane.getTitleAt(i); 3562 if (BasicHTML.isHTMLString(title)) { 3563 htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title)); 3564 } else { 3565 htmlViews.addElement(null); 3566 } 3567 } 3568 } 3569 return htmlViews; 3570 } 3571 3572 @SuppressWarnings("serial") // Superclass is not serializable across versions 3573 private class TabContainer extends JPanel implements UIResource { 3574 private boolean notifyTabbedPane = true; 3575 3576 public TabContainer() { 3577 super(null); 3578 setOpaque(false); 3579 } 3580 3581 public void remove(final Component comp) { 3582 final int index = tabPane.indexOfTabComponent(comp); 3583 super.remove(comp); 3584 if (notifyTabbedPane && index != -1) { 3585 tabPane.setTabComponentAt(index, null); 3586 } 3587 } 3588 3589 private void removeUnusedTabComponents() { 3590 for (final Component c : getComponents()) { 3591 if (!(c instanceof UIResource)) { 3592 final int index = tabPane.indexOfTabComponent(c); 3593 if (index == -1) { 3594 super.remove(c); 3595 } 3596 } 3597 } 3598 } 3599 3600 public boolean isOptimizedDrawingEnabled() { 3601 return tabScroller != null && !tabScroller.croppedEdge.isParamsSet(); 3602 } 3603 3604 public void doLayout() { 3605 // We layout tabComponents in JTabbedPane's layout manager 3606 // and use this method as a hook for repainting tabs 3607 // to update tabs area e.g. when the size of tabComponent was changed 3608 if (scrollableTabLayoutEnabled()) { 3609 tabScroller.tabPanel.repaint(); 3610 tabScroller.updateView(); 3611 } else { 3612 tabPane.repaint(getBounds()); 3613 } 3614 } 3615 } 3616 3617 @SuppressWarnings("serial") // Superclass is not serializable across versions 3618 private class CroppedEdge extends JPanel implements UIResource { 3619 private Shape shape; 3620 private int tabIndex; 3621 private int cropline; 3622 private int cropx, cropy; 3623 3624 public CroppedEdge() { 3625 setOpaque(false); 3626 } 3627 3628 public void setParams(final int tabIndex, final int cropline, final int cropx, final int cropy) { 3629 this.tabIndex = tabIndex; 3630 this.cropline = cropline; 3631 this.cropx = cropx; 3632 this.cropy = cropy; 3633 final Rectangle tabRect = rects[tabIndex]; 3634 setBounds(tabRect); 3635 shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline); 3636 if (getParent() == null && tabContainer != null) { 3637 tabContainer.add(this, 0); 3638 } 3639 } 3640 3641 public void resetParams() { 3642 shape = null; 3643 if (getParent() == tabContainer && tabContainer != null) { 3644 tabContainer.remove(this); 3645 } 3646 } 3647 3648 public boolean isParamsSet() { 3649 return shape != null; 3650 } 3651 3652 public int getTabIndex() { 3653 return tabIndex; 3654 } 3655 3656 public int getCropline() { 3657 return cropline; 3658 } 3659 3660 public int getCroppedSideWidth() { 3661 return 3; 3662 } 3663 3664 private Color getBgColor() { 3665 final Component parent = tabPane.getParent(); 3666 if (parent != null) { 3667 final Color bg = parent.getBackground(); 3668 if (bg != null) { 3669 return bg; 3670 } 3671 } 3672 return UIManager.getColor("control"); 3673 } 3674 3675 protected void paintComponent(final Graphics g) { 3676 super.paintComponent(g); 3677 if (isParamsSet() && g instanceof Graphics2D) { 3678 final Graphics2D g2 = (Graphics2D)g; 3679 g2.clipRect(0, 0, getWidth(), getHeight()); 3680 g2.setColor(getBgColor()); 3681 g2.translate(cropx, cropy); 3682 g2.fill(shape); 3683 paintCroppedTabEdge(g); 3684 g2.translate(-cropx, -cropy); 3685 } 3686 } 3687 } 3688 3689 /** 3690 * An ActionMap that populates its contents as necessary. The 3691 * contents are populated by invoking the {@code loadActionMap} 3692 * method on the passed in Object. 3693 * 3694 * @version 1.6, 11/17/05 3695 * @author Scott Violet 3696 */ 3697 @SuppressWarnings("serial") // Superclass is not serializable across versions 3698 static class LazyActionMap extends ActionMapUIResource { 3699 /** 3700 * Object to invoke {@code loadActionMap} on. This may be 3701 * a Class object. 3702 */ 3703 private transient Object _loader; 3704 3705 /** 3706 * Installs an ActionMap that will be populated by invoking the 3707 * {@code loadActionMap} method on the specified Class 3708 * when necessary. 3709 * <p> 3710 * This should be used if the ActionMap can be shared. 3711 * 3712 * @param c JComponent to install the ActionMap on. 3713 * @param loaderClass Class object that gets loadActionMap invoked 3714 * on. 3715 * @param defaultsKey Key to use to defaults table to check for 3716 * existing map and what resulting Map will be registered on. 3717 */ 3718 static void installLazyActionMap(final JComponent c, final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) { 3719 ActionMap map = (ActionMap)UIManager.get(defaultsKey); 3720 if (map == null) { 3721 map = new LazyActionMap(loaderClass); 3722 UIManager.getLookAndFeelDefaults().put(defaultsKey, map); 3723 } 3724 SwingUtilities.replaceUIActionMap(c, map); 3725 } 3726 3727 /** 3728 * Returns an ActionMap that will be populated by invoking the 3729 * {@code loadActionMap} method on the specified Class 3730 * when necessary. 3731 * <p> 3732 * This should be used if the ActionMap can be shared. 3733 * 3734 * @param c JComponent to install the ActionMap on. 3735 * @param loaderClass Class object that gets loadActionMap invoked 3736 * on. 3737 * @param defaultsKey Key to use to defaults table to check for 3738 * existing map and what resulting Map will be registered on. 3739 */ 3740 static ActionMap getActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) { 3741 ActionMap map = (ActionMap)UIManager.get(defaultsKey); 3742 if (map == null) { 3743 map = new LazyActionMap(loaderClass); 3744 UIManager.getLookAndFeelDefaults().put(defaultsKey, map); 3745 } 3746 return map; 3747 } 3748 3749 private LazyActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loader) { 3750 _loader = loader; 3751 } 3752 3753 public void put(final Action action) { 3754 put(action.getValue(Action.NAME), action); 3755 } 3756 3757 public void put(final Object key, final Action action) { 3758 loadIfNecessary(); 3759 super.put(key, action); 3760 } 3761 3762 public Action get(final Object key) { 3763 loadIfNecessary(); 3764 return super.get(key); 3765 } 3766 3767 public void remove(final Object key) { 3768 loadIfNecessary(); 3769 super.remove(key); 3770 } 3771 3772 public void clear() { 3773 loadIfNecessary(); 3774 super.clear(); 3775 } 3776 3777 public Object[] keys() { 3778 loadIfNecessary(); 3779 return super.keys(); 3780 } 3781 3782 public int size() { 3783 loadIfNecessary(); 3784 return super.size(); 3785 } 3786 3787 public Object[] allKeys() { 3788 loadIfNecessary(); 3789 return super.allKeys(); 3790 } 3791 3792 public void setParent(final ActionMap map) { 3793 loadIfNecessary(); 3794 super.setParent(map); 3795 } 3796 3797 private void loadIfNecessary() { 3798 if (_loader != null) { 3799 final Object loader = _loader; 3800 3801 _loader = null; 3802 final Class<?> klass = (Class<?>)loader; 3803 try { 3804 final java.lang.reflect.Method method = klass.getDeclaredMethod("loadActionMap", new Class<?>[] { LazyActionMap.class }); 3805 method.invoke(klass, new Object[] { this }); 3806 } catch (final NoSuchMethodException nsme) { 3807 assert false : "LazyActionMap unable to load actions " + klass; 3808 } catch (final IllegalAccessException iae) { 3809 assert false : "LazyActionMap unable to load actions " + iae; 3810 } catch (final InvocationTargetException ite) { 3811 assert false : "LazyActionMap unable to load actions " + ite; 3812 } catch (final IllegalArgumentException iae) { 3813 assert false : "LazyActionMap unable to load actions " + iae; 3814 } 3815 } 3816 } 3817 } 3818 }