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