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