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