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