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