1 /*
   2  * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * Copy of javax.swing.plaf.basic.BasicTabbedPaneUI because the original
  28  * does not have enough private methods marked as protected.
  29  *
  30  * This copy is from 1.6.0_04 as of 2008-02-02.
  31  */
  32 
  33 package com.apple.laf;
  34 
  35 import java.awt.*;
  36 import java.awt.event.*;
  37 import java.beans.*;
  38 import java.lang.reflect.InvocationTargetException;
  39 import java.util.*;
  40 
  41 import javax.swing.*;
  42 import javax.swing.event.*;
  43 import javax.swing.plaf.*;
  44 import javax.swing.plaf.basic.*;
  45 import javax.swing.text.View;
  46 
  47 import sun.swing.*;
  48 
  49 public class AquaTabbedPaneCopyFromBasicUI extends TabbedPaneUI implements SwingConstants {
  50 // Instance variables initialized at installation
  51 
  52     protected JTabbedPane tabPane;
  53 
  54     protected Color highlight;
  55     protected Color lightHighlight;
  56     protected Color shadow;
  57     protected Color darkShadow;
  58     protected Color focus;
  59     private Color selectedColor;
  60 
  61     protected int textIconGap;
  62 
  63     protected int tabRunOverlay;
  64 
  65     protected Insets tabInsets;
  66     protected Insets selectedTabPadInsets;
  67     protected Insets tabAreaInsets;
  68     protected Insets contentBorderInsets;
  69     private boolean tabsOverlapBorder;
  70     private boolean tabsOpaque = true;
  71     private boolean contentOpaque = true;
  72 
  73     /**
  74      * As of Java 2 platform v1.3 this previously undocumented field is no
  75      * longer used.
  76      * Key bindings are now defined by the LookAndFeel, please refer to
  77      * the key bindings specification for further details.
  78      *
  79      * @deprecated As of Java 2 platform v1.3.
  80      */
  81     @Deprecated
  82     protected KeyStroke upKey;
  83     /**
  84      * As of Java 2 platform v1.3 this previously undocumented field is no
  85      * longer used.
  86      * Key bindings are now defined by the LookAndFeel, please refer to
  87      * the key bindings specification for further details.
  88      *
  89      * @deprecated As of Java 2 platform v1.3.
  90      */
  91     @Deprecated
  92     protected KeyStroke downKey;
  93     /**
  94      * As of Java 2 platform v1.3 this previously undocumented field is no
  95      * longer used.
  96      * Key bindings are now defined by the LookAndFeel, please refer to
  97      * the key bindings specification for further details.
  98      *
  99      * @deprecated As of Java 2 platform v1.3.
 100      */
 101     @Deprecated
 102     protected KeyStroke leftKey;
 103     /**
 104      * As of Java 2 platform v1.3 this previously undocumented field is no
 105      * longer used.
 106      * Key bindings are now defined by the LookAndFeel, please refer to
 107      * the key bindings specification for further details.
 108      *
 109      * @deprecated As of Java 2 platform v1.3.
 110      */
 111     @Deprecated
 112     protected KeyStroke rightKey;
 113 
 114 // Transient variables (recalculated each time TabbedPane is layed out)
 115 
 116     protected int tabRuns[] = new int[10];
 117     protected int runCount = 0;
 118     protected int selectedRun = -1;
 119     protected Rectangle rects[] = new Rectangle[0];
 120     protected int maxTabHeight;
 121     protected int maxTabWidth;
 122 
 123 // Listeners
 124 
 125     protected ChangeListener tabChangeListener;
 126     protected PropertyChangeListener propertyChangeListener;
 127     protected MouseListener mouseListener;
 128     protected FocusListener focusListener;
 129 
 130 // Private instance data
 131 
 132     private final Insets currentPadInsets = new Insets(0, 0, 0, 0);
 133     private final Insets currentTabAreaInsets = new Insets(0, 0, 0, 0);
 134 
 135     private Component visibleComponent;
 136     // PENDING(api): See comment for ContainerHandler
 137     private Vector<View> htmlViews;
 138 
 139     private Hashtable<Integer, Integer> mnemonicToIndexMap;
 140 
 141     /**
 142      * InputMap used for mnemonics. Only non-null if the JTabbedPane has
 143      * mnemonics associated with it. Lazily created in initMnemonics.
 144      */
 145     private InputMap mnemonicInputMap;
 146 
 147     // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
 148     private ScrollableTabSupport tabScroller;
 149 
 150     private TabContainer tabContainer;
 151 
 152     /**
 153      * A rectangle used for general layout calculations in order
 154      * to avoid constructing many new Rectangles on the fly.
 155      */
 156     protected transient Rectangle calcRect = new Rectangle(0, 0, 0, 0);
 157 
 158     /**
 159      * Tab that has focus.
 160      */
 161     private int focusIndex;
 162 
 163     /**
 164      * Combined listeners.
 165      */
 166     private Handler handler;
 167 
 168     /**
 169      * Index of the tab the mouse is over.
 170      */
 171     private int rolloverTabIndex;
 172 
 173     /**
 174      * This is set to true when a component is added/removed from the tab
 175      * pane and set to false when layout happens.  If true it indicates that
 176      * tabRuns is not valid and shouldn't be used.
 177      */
 178     private boolean isRunsDirty;
 179 
 180     private boolean calculatedBaseline;
 181     private int baseline;
 182 
 183 // UI creation
 184 
 185     public static ComponentUI createUI(final JComponent c) {
 186         return new AquaTabbedPaneCopyFromBasicUI();
 187     }
 188 
 189     // MACOSX adding accessor for superclass
 190     protected Component getTabComponentAt(final int i) {
 191         return tabPane.getTabComponentAt(i);
 192     }
 193     // END MACOSX
 194 
 195     static void loadActionMap(final LazyActionMap map) {
 196         map.put(new Actions(Actions.NEXT));
 197         map.put(new Actions(Actions.PREVIOUS));
 198         map.put(new Actions(Actions.RIGHT));
 199         map.put(new Actions(Actions.LEFT));
 200         map.put(new Actions(Actions.UP));
 201         map.put(new Actions(Actions.DOWN));
 202         map.put(new Actions(Actions.PAGE_UP));
 203         map.put(new Actions(Actions.PAGE_DOWN));
 204         map.put(new Actions(Actions.REQUEST_FOCUS));
 205         map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE));
 206         map.put(new Actions(Actions.SET_SELECTED));
 207         map.put(new Actions(Actions.SELECT_FOCUSED));
 208         map.put(new Actions(Actions.SCROLL_FORWARD));
 209         map.put(new Actions(Actions.SCROLL_BACKWARD));
 210     }
 211 
 212 // UI Installation/De-installation
 213 
 214     public void installUI(final JComponent c) {
 215         this.tabPane = (JTabbedPane)c;
 216 
 217         calculatedBaseline = false;
 218         rolloverTabIndex = -1;
 219         focusIndex = -1;
 220         c.setLayout(createLayoutManager());
 221         installComponents();
 222         installDefaults();
 223         installListeners();
 224         installKeyboardActions();
 225     }
 226 
 227     public void uninstallUI(final JComponent c) {
 228         uninstallKeyboardActions();
 229         uninstallListeners();
 230         uninstallDefaults();
 231         uninstallComponents();
 232         c.setLayout(null);
 233 
 234         this.tabPane = null;
 235     }
 236 
 237     /**
 238      * Invoked by {@code installUI} to create
 239      * a layout manager object to manage
 240      * the {@code JTabbedPane}.
 241      *
 242      * @return a layout manager object
 243      *
 244      * @see TabbedPaneLayout
 245      * @see javax.swing.JTabbedPane#getTabLayoutPolicy
 246      */
 247     protected LayoutManager createLayoutManager() {
 248         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
 249             return new TabbedPaneScrollLayout();
 250         } else { /* WRAP_TAB_LAYOUT */
 251             return new TabbedPaneLayout();
 252         }
 253     }
 254 
 255     /* In an attempt to preserve backward compatibility for programs
 256      * which have extended BasicTabbedPaneUI to do their own layout, the
 257      * UI uses the installed layoutManager (and not tabLayoutPolicy) to
 258      * determine if scrollTabLayout is enabled.
 259      */
 260     boolean scrollableTabLayoutEnabled() {
 261         return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
 262     }
 263 
 264     /**
 265      * Creates and installs any required subcomponents for the JTabbedPane.
 266      * Invoked by installUI.
 267      *
 268      * @since 1.4
 269      */
 270     protected void installComponents() {
 271         if (scrollableTabLayoutEnabled()) {
 272             if (tabScroller == null) {
 273                 tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
 274                 tabPane.add(tabScroller.viewport);
 275             }
 276         }
 277         installTabContainer();
 278     }
 279 
 280     private void installTabContainer() {
 281         for (int i = 0; i < tabPane.getTabCount(); i++) {
 282             final Component tabComponent = tabPane.getTabComponentAt(i);
 283             if (tabComponent != null) {
 284                 if (tabContainer == null) {
 285                     tabContainer = new TabContainer();
 286                 }
 287                 tabContainer.add(tabComponent);
 288             }
 289         }
 290         if (tabContainer == null) {
 291             return;
 292         }
 293         if (scrollableTabLayoutEnabled()) {
 294             tabScroller.tabPanel.add(tabContainer);
 295         } else {
 296             tabPane.add(tabContainer);
 297         }
 298     }
 299 
 300     /**
 301      * Creates and returns a JButton that will provide the user
 302      * with a way to scroll the tabs in a particular direction. The
 303      * returned JButton must be instance of UIResource.
 304      *
 305      * @param direction One of the SwingConstants constants:
 306      * SOUTH, NORTH, EAST or WEST
 307      * @return Widget for user to
 308      * @see javax.swing.JTabbedPane#setTabPlacement
 309      * @see javax.swing.SwingConstants
 310      * @throws IllegalArgumentException if direction is not one of
 311      *         NORTH, SOUTH, EAST or WEST
 312      * @since 1.5
 313      */
 314     protected JButton createScrollButton(final int direction) {
 315         if (direction != SOUTH && direction != NORTH && direction != EAST && direction != WEST) {
 316             throw new IllegalArgumentException("Direction must be one of: " + "SOUTH, NORTH, EAST or WEST");
 317         }
 318         return new ScrollableTabButton(direction);
 319     }
 320 
 321     /**
 322      * Removes any installed subcomponents from the JTabbedPane.
 323      * Invoked by uninstallUI.
 324      *
 325      * @since 1.4
 326      */
 327     protected void uninstallComponents() {
 328         uninstallTabContainer();
 329         if (scrollableTabLayoutEnabled()) {
 330             tabPane.remove(tabScroller.viewport);
 331             tabPane.remove(tabScroller.scrollForwardButton);
 332             tabPane.remove(tabScroller.scrollBackwardButton);
 333             tabScroller = null;
 334         }
 335     }
 336 
 337     private void uninstallTabContainer() {
 338         if (tabContainer == null) {
 339             return;
 340         }
 341         // Remove all the tabComponents, making sure not to notify
 342         // the tabbedpane.
 343         tabContainer.notifyTabbedPane = false;
 344         tabContainer.removeAll();
 345         if (scrollableTabLayoutEnabled()) {
 346             tabContainer.remove(tabScroller.croppedEdge);
 347             tabScroller.tabPanel.remove(tabContainer);
 348         } else {
 349             tabPane.remove(tabContainer);
 350         }
 351         tabContainer = null;
 352     }
 353 
 354     protected void installDefaults() {
 355         LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background", "TabbedPane.foreground", "TabbedPane.font");
 356         highlight = UIManager.getColor("TabbedPane.light");
 357         lightHighlight = UIManager.getColor("TabbedPane.highlight");
 358         shadow = UIManager.getColor("TabbedPane.shadow");
 359         darkShadow = UIManager.getColor("TabbedPane.darkShadow");
 360         focus = UIManager.getColor("TabbedPane.focus");
 361         selectedColor = UIManager.getColor("TabbedPane.selected");
 362 
 363         textIconGap = UIManager.getInt("TabbedPane.textIconGap");
 364         tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
 365         selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
 366         tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
 367         tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder");
 368         contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
 369         tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
 370         tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
 371         contentOpaque = UIManager.getBoolean("TabbedPane.contentOpaque");
 372         Object opaque = UIManager.get("TabbedPane.opaque");
 373         if (opaque == null) {
 374             opaque = Boolean.FALSE;
 375         }
 376         LookAndFeel.installProperty(tabPane, "opaque", opaque);
 377     }
 378 
 379     protected void uninstallDefaults() {
 380         highlight = null;
 381         lightHighlight = null;
 382         shadow = null;
 383         darkShadow = null;
 384         focus = null;
 385         tabInsets = null;
 386         selectedTabPadInsets = null;
 387         tabAreaInsets = null;
 388         contentBorderInsets = null;
 389     }
 390 
 391     protected void installListeners() {
 392         if ((propertyChangeListener = createPropertyChangeListener()) != null) {
 393             tabPane.addPropertyChangeListener(propertyChangeListener);
 394         }
 395         if ((tabChangeListener = createChangeListener()) != null) {
 396             tabPane.addChangeListener(tabChangeListener);
 397         }
 398         if ((mouseListener = createMouseListener()) != null) {
 399             tabPane.addMouseListener(mouseListener);
 400         }
 401         tabPane.addMouseMotionListener(getHandler());
 402         if ((focusListener = createFocusListener()) != null) {
 403             tabPane.addFocusListener(focusListener);
 404         }
 405         tabPane.addContainerListener(getHandler());
 406         if (tabPane.getTabCount() > 0) {
 407             htmlViews = createHTMLVector();
 408         }
 409     }
 410 
 411     protected void uninstallListeners() {
 412         if (mouseListener != null) {
 413             tabPane.removeMouseListener(mouseListener);
 414             mouseListener = null;
 415         }
 416         tabPane.removeMouseMotionListener(getHandler());
 417         if (focusListener != null) {
 418             tabPane.removeFocusListener(focusListener);
 419             focusListener = null;
 420         }
 421 
 422         tabPane.removeContainerListener(getHandler());
 423         if (htmlViews != null) {
 424             htmlViews.removeAllElements();
 425             htmlViews = null;
 426         }
 427         if (tabChangeListener != null) {
 428             tabPane.removeChangeListener(tabChangeListener);
 429             tabChangeListener = null;
 430         }
 431         if (propertyChangeListener != null) {
 432             tabPane.removePropertyChangeListener(propertyChangeListener);
 433             propertyChangeListener = null;
 434         }
 435         handler = null;
 436     }
 437 
 438     protected MouseListener createMouseListener() {
 439         return getHandler();
 440     }
 441 
 442     protected FocusListener createFocusListener() {
 443         return getHandler();
 444     }
 445 
 446     protected ChangeListener createChangeListener() {
 447         return getHandler();
 448     }
 449 
 450     protected PropertyChangeListener createPropertyChangeListener() {
 451         return getHandler();
 452     }
 453 
 454     private Handler getHandler() {
 455         if (handler == null) {
 456             handler = new Handler();
 457         }
 458         return handler;
 459     }
 460 
 461     protected void installKeyboardActions() {
 462         InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
 463 
 464         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
 465         km = getInputMap(JComponent.WHEN_FOCUSED);
 466         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
 467 
 468         LazyActionMap.installLazyActionMap(tabPane, AquaTabbedPaneCopyFromBasicUI.class, "TabbedPane.actionMap");
 469         updateMnemonics();
 470     }
 471 
 472     InputMap getInputMap(final int condition) {
 473         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
 474             return (InputMap)DefaultLookup.get(tabPane, this, "TabbedPane.ancestorInputMap");
 475         } else if (condition == JComponent.WHEN_FOCUSED) {
 476             return (InputMap)DefaultLookup.get(tabPane, this, "TabbedPane.focusInputMap");
 477         }
 478         return null;
 479     }
 480 
 481     protected void uninstallKeyboardActions() {
 482         SwingUtilities.replaceUIActionMap(tabPane, null);
 483         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
 484         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, null);
 485         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_IN_FOCUSED_WINDOW, null);
 486         mnemonicToIndexMap = null;
 487         mnemonicInputMap = null;
 488     }
 489 
 490     /**
 491      * Reloads the mnemonics. This should be invoked when a memonic changes,
 492      * when the title of a mnemonic changes, or when tabs are added/removed.
 493      */
 494     private void updateMnemonics() {
 495         resetMnemonics();
 496         for (int counter = tabPane.getTabCount() - 1; counter >= 0; counter--) {
 497             final int mnemonic = tabPane.getMnemonicAt(counter);
 498 
 499             if (mnemonic > 0) {
 500                 addMnemonic(counter, mnemonic);
 501             }
 502         }
 503     }
 504 
 505     /**
 506      * Resets the mnemonics bindings to an empty state.
 507      */
 508     private void resetMnemonics() {
 509         if (mnemonicToIndexMap != null) {
 510             mnemonicToIndexMap.clear();
 511             mnemonicInputMap.clear();
 512         }
 513     }
 514 
 515     /**
 516      * Adds the specified mnemonic at the specified index.
 517      */
 518     @SuppressWarnings("deprecation")
 519     private void addMnemonic(final int index, final int mnemonic) {
 520         if (mnemonicToIndexMap == null) {
 521             initMnemonics();
 522         }
 523         // [2165820] Mac OS X change: mnemonics need to be triggered with ctrl-option, not just option.
 524         mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, Event.ALT_MASK | Event.CTRL_MASK), "setSelectedIndex");
 525         mnemonicToIndexMap.put(Integer.valueOf(mnemonic), Integer.valueOf(index));
 526     }
 527 
 528     /**
 529      * Installs the state needed for mnemonics.
 530      */
 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 = SwingUtilities2.clipStringIfNecessary(null, metrics, title, availTextWidth);
 832             }
 833 
 834             paintText(g, tabPlacement, font, metrics, tabIndex, clippedTitle, textRect, isSelected);
 835 
 836             paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
 837         }
 838         paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect, textRect, isSelected);
 839     }
 840 
 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                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x, textRect.y + metrics.getAscent());
1011 
1012             } else { // tab disabled
1013                 g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
1014                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x, textRect.y + metrics.getAscent());
1015                 g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
1016                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g, title, mnemIndex, textRect.x - 1, textRect.y + metrics.getAscent() - 1);
1017 
1018             }
1019         }
1020     }
1021 
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 += SwingUtilities2.stringWidth(tabPane, metrics, title);
1644             }
1645         }
1646         return width;
1647     }
1648 
1649     protected int calculateMaxTabWidth(final int tabPlacement) {
1650         final FontMetrics metrics = getFontMetrics();
1651         final int tabCount = tabPane.getTabCount();
1652         int result = 0;
1653         for (int i = 0; i < tabCount; i++) {
1654             result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
1655         }
1656         return result;
1657     }
1658 
1659     protected int calculateTabAreaHeight(final int tabPlacement, final int horizRunCount, final int maxTabHeight) {
1660         final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1661         final int tabRunOverlay = getTabRunOverlay(tabPlacement);
1662         return (horizRunCount > 0 ? horizRunCount * (maxTabHeight - tabRunOverlay) + tabRunOverlay + tabAreaInsets.top + tabAreaInsets.bottom : 0);
1663     }
1664 
1665     protected int calculateTabAreaWidth(final int tabPlacement, final int vertRunCount, final int maxTabWidth) {
1666         final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1667         final int tabRunOverlay = getTabRunOverlay(tabPlacement);
1668         return (vertRunCount > 0 ? vertRunCount * (maxTabWidth - tabRunOverlay) + tabRunOverlay + tabAreaInsets.left + tabAreaInsets.right : 0);
1669     }
1670 
1671     protected Insets getTabInsets(final int tabPlacement, final int tabIndex) {
1672         return tabInsets;
1673     }
1674 
1675     protected Insets getSelectedTabPadInsets(final int tabPlacement) {
1676         rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
1677         return currentPadInsets;
1678     }
1679 
1680     protected Insets getTabAreaInsets(final int tabPlacement) {
1681         rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
1682         return currentTabAreaInsets;
1683     }
1684 
1685     protected Insets getContentBorderInsets(final int tabPlacement) {
1686         return contentBorderInsets;
1687     }
1688 
1689     protected FontMetrics getFontMetrics() {
1690         final Font font = tabPane.getFont();
1691         return tabPane.getFontMetrics(font);
1692     }
1693 
1694 // Tab Navigation methods
1695 
1696     protected void navigateSelectedTab(final int direction) {
1697         final int tabPlacement = tabPane.getTabPlacement();
1698         final int current = DefaultLookup.getBoolean(tabPane, this, "TabbedPane.selectionFollowsFocus", true) ? tabPane.getSelectedIndex() : getFocusIndex();
1699         final int tabCount = tabPane.getTabCount();
1700         final boolean leftToRight = AquaUtils.isLeftToRight(tabPane);
1701 
1702         // If we have no tabs then don't navigate.
1703         if (tabCount <= 0) {
1704             return;
1705         }
1706 
1707         int offset;
1708         switch (tabPlacement) {
1709             case LEFT:
1710             case RIGHT:
1711                 switch (direction) {
1712                     case NEXT:
1713                         selectNextTab(current);
1714                         break;
1715                     case PREVIOUS:
1716                         selectPreviousTab(current);
1717                         break;
1718                     case NORTH:
1719                         selectPreviousTabInRun(current);
1720                         break;
1721                     case SOUTH:
1722                         selectNextTabInRun(current);
1723                         break;
1724                     case WEST:
1725                         offset = getTabRunOffset(tabPlacement, tabCount, current, false);
1726                         selectAdjacentRunTab(tabPlacement, current, offset);
1727                         break;
1728                     case EAST:
1729                         offset = getTabRunOffset(tabPlacement, tabCount, current, true);
1730                         selectAdjacentRunTab(tabPlacement, current, offset);
1731                         break;
1732                     default:
1733                 }
1734                 break;
1735             case BOTTOM:
1736             case TOP:
1737             default:
1738                 switch (direction) {
1739                     case NEXT:
1740                         selectNextTab(current);
1741                         break;
1742                     case PREVIOUS:
1743                         selectPreviousTab(current);
1744                         break;
1745                     case NORTH:
1746                         offset = getTabRunOffset(tabPlacement, tabCount, current, false);
1747                         selectAdjacentRunTab(tabPlacement, current, offset);
1748                         break;
1749                     case SOUTH:
1750                         offset = getTabRunOffset(tabPlacement, tabCount, current, true);
1751                         selectAdjacentRunTab(tabPlacement, current, offset);
1752                         break;
1753                     case EAST:
1754                         if (leftToRight) {
1755                             selectNextTabInRun(current);
1756                         } else {
1757                             selectPreviousTabInRun(current);
1758                         }
1759                         break;
1760                     case WEST:
1761                         if (leftToRight) {
1762                             selectPreviousTabInRun(current);
1763                         } else {
1764                             selectNextTabInRun(current);
1765                         }
1766                         break;
1767                     default:
1768                 }
1769         }
1770     }
1771 
1772     protected void selectNextTabInRun(final int current) {
1773         final int tabCount = tabPane.getTabCount();
1774         int tabIndex = getNextTabIndexInRun(tabCount, current);
1775 
1776         while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1777             tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
1778         }
1779         navigateTo(tabIndex);
1780     }
1781 
1782     protected void selectPreviousTabInRun(final int current) {
1783         final int tabCount = tabPane.getTabCount();
1784         int tabIndex = getPreviousTabIndexInRun(tabCount, current);
1785 
1786         while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1787             tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
1788         }
1789         navigateTo(tabIndex);
1790     }
1791 
1792     protected void selectNextTab(final int current) {
1793         int tabIndex = getNextTabIndex(current);
1794 
1795         while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1796             tabIndex = getNextTabIndex(tabIndex);
1797         }
1798         navigateTo(tabIndex);
1799     }
1800 
1801     protected void selectPreviousTab(final int current) {
1802         int tabIndex = getPreviousTabIndex(current);
1803 
1804         while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1805             tabIndex = getPreviousTabIndex(tabIndex);
1806         }
1807         navigateTo(tabIndex);
1808     }
1809 
1810     protected void selectAdjacentRunTab(final int tabPlacement, final int tabIndex, final int offset) {
1811         if (runCount < 2) {
1812             return;
1813         }
1814         int newIndex;
1815         final Rectangle r = rects[tabIndex];
1816         switch (tabPlacement) {
1817             case LEFT:
1818             case RIGHT:
1819                 newIndex = tabForCoordinate(tabPane, r.x + r.width / 2 + offset, r.y + r.height / 2);
1820                 break;
1821             case BOTTOM:
1822             case TOP:
1823             default:
1824                 newIndex = tabForCoordinate(tabPane, r.x + r.width / 2, r.y + r.height / 2 + offset);
1825         }
1826         if (newIndex != -1) {
1827             while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
1828                 newIndex = getNextTabIndex(newIndex);
1829             }
1830             navigateTo(newIndex);
1831         }
1832     }
1833 
1834     private void navigateTo(final int index) {
1835         if (DefaultLookup.getBoolean(tabPane, this, "TabbedPane.selectionFollowsFocus", true)) {
1836             tabPane.setSelectedIndex(index);
1837         } else {
1838             // Just move focus (not selection)
1839             setFocusIndex(index, true);
1840         }
1841     }
1842 
1843     void setFocusIndex(final int index, final boolean repaint) {
1844         if (repaint && !isRunsDirty) {
1845             repaintTab(focusIndex);
1846             focusIndex = index;
1847             repaintTab(focusIndex);
1848         } else {
1849             focusIndex = index;
1850         }
1851     }
1852 
1853     /**
1854      * Repaints the specified tab.
1855      */
1856     private void repaintTab(final int index) {
1857         // If we're not valid that means we will shortly be validated and
1858         // painted, which means we don't have to do anything here.
1859         if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) {
1860             Rectangle rect = getTabBounds(tabPane, index);
1861             if (rect != null) {
1862                 tabPane.repaint(rect);
1863             }
1864         }
1865     }
1866 
1867     /**
1868      * Makes sure the focusIndex is valid.
1869      */
1870     private void validateFocusIndex() {
1871         if (focusIndex >= tabPane.getTabCount()) {
1872             setFocusIndex(tabPane.getSelectedIndex(), false);
1873         }
1874     }
1875 
1876     /**
1877      * Returns the index of the tab that has focus.
1878      *
1879      * @return index of tab that has focus
1880      * @since 1.5
1881      */
1882     protected int getFocusIndex() {
1883         return focusIndex;
1884     }
1885 
1886     protected int getTabRunOffset(final int tabPlacement, final int tabCount, final int tabIndex, final boolean forward) {
1887         final int run = getRunForTab(tabCount, tabIndex);
1888         int offset;
1889         switch (tabPlacement) {
1890             case LEFT: {
1891                 if (run == 0) {
1892                     offset = (forward ? -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth) : -maxTabWidth);
1893 
1894                 } else if (run == runCount - 1) {
1895                     offset = (forward ? maxTabWidth : calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth);
1896                 } else {
1897                     offset = (forward ? maxTabWidth : -maxTabWidth);
1898                 }
1899                 break;
1900             }
1901             case RIGHT: {
1902                 if (run == 0) {
1903                     offset = (forward ? maxTabWidth : calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth);
1904                 } else if (run == runCount - 1) {
1905                     offset = (forward ? -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth) - maxTabWidth) : -maxTabWidth);
1906                 } else {
1907                     offset = (forward ? maxTabWidth : -maxTabWidth);
1908                 }
1909                 break;
1910             }
1911             case BOTTOM: {
1912                 if (run == 0) {
1913                     offset = (forward ? maxTabHeight : calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight);
1914                 } else if (run == runCount - 1) {
1915                     offset = (forward ? -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight) : -maxTabHeight);
1916                 } else {
1917                     offset = (forward ? maxTabHeight : -maxTabHeight);
1918                 }
1919                 break;
1920             }
1921             case TOP:
1922             default: {
1923                 if (run == 0) {
1924                     offset = (forward ? -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight) : -maxTabHeight);
1925                 } else if (run == runCount - 1) {
1926                     offset = (forward ? maxTabHeight : calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) - maxTabHeight);
1927                 } else {
1928                     offset = (forward ? maxTabHeight : -maxTabHeight);
1929                 }
1930             }
1931         }
1932         return offset;
1933     }
1934 
1935     protected int getPreviousTabIndex(final int base) {
1936         final int tabIndex = (base - 1 >= 0 ? base - 1 : tabPane.getTabCount() - 1);
1937         return (tabIndex >= 0 ? tabIndex : 0);
1938     }
1939 
1940     protected int getNextTabIndex(final int base) {
1941         return (base + 1) % tabPane.getTabCount();
1942     }
1943 
1944     protected int getNextTabIndexInRun(final int tabCount, final int base) {
1945         if (runCount < 2) {
1946             return getNextTabIndex(base);
1947         }
1948         final int currentRun = getRunForTab(tabCount, base);
1949         final int next = getNextTabIndex(base);
1950         if (next == tabRuns[getNextTabRun(currentRun)]) {
1951             return tabRuns[currentRun];
1952         }
1953         return next;
1954     }
1955 
1956     protected int getPreviousTabIndexInRun(final int tabCount, final int base) {
1957         if (runCount < 2) {
1958             return getPreviousTabIndex(base);
1959         }
1960         final int currentRun = getRunForTab(tabCount, base);
1961         if (base == tabRuns[currentRun]) {
1962             final int previous = tabRuns[getNextTabRun(currentRun)] - 1;
1963             return (previous != -1 ? previous : tabCount - 1);
1964         }
1965         return getPreviousTabIndex(base);
1966     }
1967 
1968     protected int getPreviousTabRun(final int baseRun) {
1969         final int runIndex = (baseRun - 1 >= 0 ? baseRun - 1 : runCount - 1);
1970         return (runIndex >= 0 ? runIndex : 0);
1971     }
1972 
1973     protected int getNextTabRun(final int baseRun) {
1974         return (baseRun + 1) % runCount;
1975     }
1976 
1977     protected static void rotateInsets(final Insets topInsets, final Insets targetInsets, final int targetPlacement) {
1978 
1979         switch (targetPlacement) {
1980             case LEFT:
1981                 targetInsets.top = topInsets.left;
1982                 targetInsets.left = topInsets.top;
1983                 targetInsets.bottom = topInsets.right;
1984                 targetInsets.right = topInsets.bottom;
1985                 break;
1986             case BOTTOM:
1987                 targetInsets.top = topInsets.bottom;
1988                 targetInsets.left = topInsets.left;
1989                 targetInsets.bottom = topInsets.top;
1990                 targetInsets.right = topInsets.right;
1991                 break;
1992             case RIGHT:
1993                 targetInsets.top = topInsets.left;
1994                 targetInsets.left = topInsets.bottom;
1995                 targetInsets.bottom = topInsets.right;
1996                 targetInsets.right = topInsets.top;
1997                 break;
1998             case TOP:
1999             default:
2000                 targetInsets.top = topInsets.top;
2001                 targetInsets.left = topInsets.left;
2002                 targetInsets.bottom = topInsets.bottom;
2003                 targetInsets.right = topInsets.right;
2004         }
2005     }
2006 
2007     // REMIND(aim,7/29/98): This method should be made
2008     // protected in the next release where
2009     // API changes are allowed
2010     boolean requestFocusForVisibleComponent() {
2011         return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent());
2012     }
2013 
2014     private static class Actions extends UIAction {
2015         static final String NEXT = "navigateNext";
2016         static final String PREVIOUS = "navigatePrevious";
2017         static final String RIGHT = "navigateRight";
2018         static final String LEFT = "navigateLeft";
2019         static final String UP = "navigateUp";
2020         static final String DOWN = "navigateDown";
2021         static final String PAGE_UP = "navigatePageUp";
2022         static final String PAGE_DOWN = "navigatePageDown";
2023         static final String REQUEST_FOCUS = "requestFocus";
2024         static final String REQUEST_FOCUS_FOR_VISIBLE = "requestFocusForVisibleComponent";
2025         static final String SET_SELECTED = "setSelectedIndex";
2026         static final String SELECT_FOCUSED = "selectTabWithFocus";
2027         static final String SCROLL_FORWARD = "scrollTabsForwardAction";
2028         static final String SCROLL_BACKWARD = "scrollTabsBackwardAction";
2029 
2030         Actions(final String key) {
2031             super(key);
2032         }
2033 
2034         static Object getUIOfType(final ComponentUI ui, final Class<AquaTabbedPaneCopyFromBasicUI> klass) {
2035             if (klass.isInstance(ui)) {
2036                 return ui;
2037             }
2038             return null;
2039         }
2040 
2041         public void actionPerformed(final ActionEvent e) {
2042             final String key = getName();
2043             final JTabbedPane pane = (JTabbedPane)e.getSource();
2044             final AquaTabbedPaneCopyFromBasicUI ui = (AquaTabbedPaneCopyFromBasicUI)getUIOfType(pane.getUI(), AquaTabbedPaneCopyFromBasicUI.class);
2045 
2046             if (ui == null) {
2047                 return;
2048             }
2049 
2050             if (key == NEXT) {
2051                 ui.navigateSelectedTab(SwingConstants.NEXT);
2052             } else if (key == PREVIOUS) {
2053                 ui.navigateSelectedTab(SwingConstants.PREVIOUS);
2054             } else if (key == RIGHT) {
2055                 ui.navigateSelectedTab(SwingConstants.EAST);
2056             } else if (key == LEFT) {
2057                 ui.navigateSelectedTab(SwingConstants.WEST);
2058             } else if (key == UP) {
2059                 ui.navigateSelectedTab(SwingConstants.NORTH);
2060             } else if (key == DOWN) {
2061                 ui.navigateSelectedTab(SwingConstants.SOUTH);
2062             } else if (key == PAGE_UP) {
2063                 final int tabPlacement = pane.getTabPlacement();
2064                 if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2065                     ui.navigateSelectedTab(SwingConstants.WEST);
2066                 } else {
2067                     ui.navigateSelectedTab(SwingConstants.NORTH);
2068                 }
2069             } else if (key == PAGE_DOWN) {
2070                 final int tabPlacement = pane.getTabPlacement();
2071                 if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2072                     ui.navigateSelectedTab(SwingConstants.EAST);
2073                 } else {
2074                     ui.navigateSelectedTab(SwingConstants.SOUTH);
2075                 }
2076             } else if (key == REQUEST_FOCUS) {
2077                 pane.requestFocus();
2078             } else if (key == REQUEST_FOCUS_FOR_VISIBLE) {
2079                 ui.requestFocusForVisibleComponent();
2080             } else if (key == SET_SELECTED) {
2081                 final String command = e.getActionCommand();
2082 
2083                 if (command != null && command.length() > 0) {
2084                     int mnemonic = e.getActionCommand().charAt(0);
2085                     if (mnemonic >= 'a' && mnemonic <= 'z') {
2086                         mnemonic -= ('a' - 'A');
2087                     }
2088                     final Integer index = ui.mnemonicToIndexMap.get(Integer.valueOf(mnemonic));
2089                     if (index != null && pane.isEnabledAt(index.intValue())) {
2090                         pane.setSelectedIndex(index.intValue());
2091                     }
2092                 }
2093             } else if (key == SELECT_FOCUSED) {
2094                 final int focusIndex = ui.getFocusIndex();
2095                 if (focusIndex != -1) {
2096                     pane.setSelectedIndex(focusIndex);
2097                 }
2098             } else if (key == SCROLL_FORWARD) {
2099                 if (ui.scrollableTabLayoutEnabled()) {
2100                     ui.tabScroller.scrollForward(pane.getTabPlacement());
2101                 }
2102             } else if (key == SCROLL_BACKWARD) {
2103                 if (ui.scrollableTabLayoutEnabled()) {
2104                     ui.tabScroller.scrollBackward(pane.getTabPlacement());
2105                 }
2106             }
2107         }
2108     }
2109 
2110     /**
2111      * This class should be treated as a &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 int tabCount = tabPane.getTabCount();
2188             int total = 0;
2189             if (tabCount > 0) {
2190                 final int maxTabHeight = calculateMaxTabHeight(tabPlacement);
2191                 total = calculateTabAreaHeight(tabPlacement, 1, maxTabHeight);
2192             }
2193             return total;
2194         }
2195 
2196         protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
2197             final int tabCount = tabPane.getTabCount();
2198             int total = 0;
2199             if (tabCount > 0) {
2200                 maxTabWidth = calculateMaxTabWidth(tabPlacement);
2201                 total = calculateTabAreaWidth(tabPlacement, 1, maxTabWidth);
2202             }
2203             return total;
2204         }
2205 
2206         @SuppressWarnings("deprecation")
2207         public void layoutContainer(final Container parent) {
2208             /* Some of the code in this method deals with changing the
2209              * visibility of components to hide and show the contents for the
2210              * selected tab. This is older code that has since been duplicated
2211              * in JTabbedPane.fireStateChanged(), so as to allow visibility
2212              * changes to happen sooner (see the note there). This code remains
2213              * for backward compatibility as there are some cases, such as
2214              * subclasses that don't fireStateChanged() where it may be used.
2215              * Any changes here need to be kept in synch with
2216              * JTabbedPane.fireStateChanged().
2217              */
2218 
2219             setRolloverTab(-1);
2220 
2221             final int tabPlacement = tabPane.getTabPlacement();
2222             final Insets insets = tabPane.getInsets();
2223             final int selectedIndex = tabPane.getSelectedIndex();
2224             final Component visibleComponent = getVisibleComponent();
2225 
2226             calculateLayoutInfo();
2227 
2228             Component selectedComponent = null;
2229             if (selectedIndex < 0) {
2230                 if (visibleComponent != null) {
2231                     // The last tab was removed, so remove the component
2232                     setVisibleComponent(null);
2233                 }
2234             } else {
2235                 selectedComponent = tabPane.getComponentAt(selectedIndex);
2236             }
2237             int cx, cy, cw, ch;
2238             int totalTabWidth = 0;
2239             int totalTabHeight = 0;
2240             final Insets contentInsets = getContentBorderInsets(tabPlacement);
2241 
2242             boolean shouldChangeFocus = false;
2243 
2244             // In order to allow programs to use a single component
2245             // as the display for multiple tabs, we will not change
2246             // the visible compnent if the currently selected tab
2247             // has a null component.  This is a bit dicey, as we don't
2248             // explicitly state we support this in the spec, but since
2249             // programs are now depending on this, we're making it work.
2250             //
2251             if (selectedComponent != null) {
2252                 if (selectedComponent != visibleComponent && visibleComponent != null) {
2253                     if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
2254                         shouldChangeFocus = true;
2255                     }
2256                 }
2257                 setVisibleComponent(selectedComponent);
2258             }
2259 
2260             final Rectangle bounds = tabPane.getBounds();
2261             final int numChildren = tabPane.getComponentCount();
2262 
2263             if (numChildren > 0) {
2264 
2265                 switch (tabPlacement) {
2266                     case LEFT:
2267                         totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2268                         cx = insets.left + totalTabWidth + contentInsets.left;
2269                         cy = insets.top + contentInsets.top;
2270                         break;
2271                     case RIGHT:
2272                         totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2273                         cx = insets.left + contentInsets.left;
2274                         cy = insets.top + contentInsets.top;
2275                         break;
2276                     case BOTTOM:
2277                         totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2278                         cx = insets.left + contentInsets.left;
2279                         cy = insets.top + contentInsets.top;
2280                         break;
2281                     case TOP:
2282                     default:
2283                         totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2284                         cx = insets.left + contentInsets.left;
2285                         cy = insets.top + totalTabHeight + contentInsets.top;
2286                 }
2287 
2288                 cw = bounds.width - totalTabWidth - insets.left - insets.right - contentInsets.left - contentInsets.right;
2289                 ch = bounds.height - totalTabHeight - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
2290 
2291                 for (int i = 0; i < numChildren; i++) {
2292                     final Component child = tabPane.getComponent(i);
2293                     if (child == tabContainer) {
2294 
2295                         final int tabContainerWidth = totalTabWidth == 0 ? bounds.width : totalTabWidth + insets.left + insets.right + contentInsets.left + contentInsets.right;
2296                         final int tabContainerHeight = totalTabHeight == 0 ? bounds.height : totalTabHeight + insets.top + insets.bottom + contentInsets.top + contentInsets.bottom;
2297 
2298                         int tabContainerX = 0;
2299                         int tabContainerY = 0;
2300                         if (tabPlacement == BOTTOM) {
2301                             tabContainerY = bounds.height - tabContainerHeight;
2302                         } else if (tabPlacement == RIGHT) {
2303                             tabContainerX = bounds.width - tabContainerWidth;
2304                         }
2305                         child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
2306                     } else {
2307                         child.setBounds(cx, cy, cw, ch);
2308                     }
2309                 }
2310             }
2311             layoutTabComponents();
2312             if (shouldChangeFocus) {
2313                 if (!requestFocusForVisibleComponent()) {
2314                     tabPane.requestFocus();
2315                 }
2316             }
2317         }
2318 
2319         public void calculateLayoutInfo() {
2320             final int tabCount = tabPane.getTabCount();
2321             assureRectsCreated(tabCount);
2322             calculateTabRects(tabPane.getTabPlacement(), tabCount);
2323             isRunsDirty = false;
2324         }
2325 
2326         protected void layoutTabComponents() {
2327             if (tabContainer == null) {
2328                 return;
2329             }
2330             final Rectangle rect = new Rectangle();
2331             final Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
2332             if (scrollableTabLayoutEnabled()) {
2333                 translatePointToTabPanel(0, 0, delta);
2334             }
2335             for (int i = 0; i < tabPane.getTabCount(); i++) {
2336                 final Component c = tabPane.getTabComponentAt(i);
2337                 if (c == null) {
2338                     continue;
2339                 }
2340                 getTabBounds(i, rect);
2341                 final Dimension preferredSize = c.getPreferredSize();
2342                 final Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
2343                 final int outerX = rect.x + insets.left + delta.x;
2344                 final int outerY = rect.y + insets.top + delta.y;
2345                 final int outerWidth = rect.width - insets.left - insets.right;
2346                 final int outerHeight = rect.height - insets.top - insets.bottom;
2347                 // centralize component
2348                 final int x = outerX + (outerWidth - preferredSize.width) / 2;
2349                 final int y = outerY + (outerHeight - preferredSize.height) / 2;
2350                 final int tabPlacement = tabPane.getTabPlacement();
2351                 final boolean isSeleceted = i == tabPane.getSelectedIndex();
2352                 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted), y + getTabLabelShiftY(tabPlacement, i, isSeleceted), preferredSize.width, preferredSize.height);
2353             }
2354         }
2355 
2356         protected void calculateTabRects(final int tabPlacement, final int tabCount) {
2357             final FontMetrics metrics = getFontMetrics();
2358             final Dimension size = tabPane.getSize();
2359             final Insets insets = tabPane.getInsets();
2360             final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2361             final int fontHeight = metrics.getHeight();
2362             final int selectedIndex = tabPane.getSelectedIndex();
2363             int tabRunOverlay;
2364             int i, j;
2365             int x, y;
2366             int returnAt;
2367             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2368             boolean leftToRight = AquaUtils.isLeftToRight(tabPane);
2369 
2370             //
2371             // Calculate bounds within which a tab run must fit
2372             //
2373             switch (tabPlacement) {
2374                 case LEFT:
2375                     maxTabWidth = calculateMaxTabWidth(tabPlacement);
2376                     x = insets.left + tabAreaInsets.left;
2377                     y = insets.top + tabAreaInsets.top;
2378                     returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2379                     break;
2380                 case RIGHT:
2381                     maxTabWidth = calculateMaxTabWidth(tabPlacement);
2382                     x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
2383                     y = insets.top + tabAreaInsets.top;
2384                     returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2385                     break;
2386                 case BOTTOM:
2387                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
2388                     x = insets.left + tabAreaInsets.left;
2389                     y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
2390                     returnAt = size.width - (insets.right + tabAreaInsets.right);
2391                     break;
2392                 case TOP:
2393                 default:
2394                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
2395                     x = insets.left + tabAreaInsets.left;
2396                     y = insets.top + tabAreaInsets.top;
2397                     returnAt = size.width - (insets.right + tabAreaInsets.right);
2398                     break;
2399             }
2400 
2401             tabRunOverlay = getTabRunOverlay(tabPlacement);
2402 
2403             runCount = 0;
2404             selectedRun = -1;
2405 
2406             if (tabCount == 0) {
2407                 return;
2408             }
2409 
2410             // Run through tabs and partition them into runs
2411             Rectangle rect;
2412             for (i = 0; i < tabCount; i++) {
2413                 rect = rects[i];
2414 
2415                 if (!verticalTabRuns) {
2416                     // Tabs on TOP or BOTTOM....
2417                     if (i > 0) {
2418                         rect.x = rects[i - 1].x + rects[i - 1].width;
2419                     } else {
2420                         tabRuns[0] = 0;
2421                         runCount = 1;
2422                         maxTabWidth = 0;
2423                         rect.x = x;
2424                     }
2425                     rect.width = calculateTabWidth(tabPlacement, i, metrics);
2426                     maxTabWidth = Math.max(maxTabWidth, rect.width);
2427 
2428                     // Never move a TAB down a run if it is in the first column.
2429                     // Even if there isn't enough room, moving it to a fresh
2430                     // line won't help.
2431                     if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
2432                         if (runCount > tabRuns.length - 1) {
2433                             expandTabRunsArray();
2434                         }
2435                         tabRuns[runCount] = i;
2436                         runCount++;
2437                         rect.x = x;
2438                     }
2439                     // Initialize y position in case there's just one run
2440                     rect.y = y;
2441                     rect.height = maxTabHeight/* - 2*/;
2442 
2443                 } else {
2444                     // Tabs on LEFT or RIGHT...
2445                     if (i > 0) {
2446                         rect.y = rects[i - 1].y + rects[i - 1].height;
2447                     } else {
2448                         tabRuns[0] = 0;
2449                         runCount = 1;
2450                         maxTabHeight = 0;
2451                         rect.y = y;
2452                     }
2453                     rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
2454                     maxTabHeight = Math.max(maxTabHeight, rect.height);
2455 
2456                     // Never move a TAB over a run if it is in the first run.
2457                     // Even if there isn't enough room, moving it to a fresh
2458                     // column won't help.
2459                     if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
2460                         if (runCount > tabRuns.length - 1) {
2461                             expandTabRunsArray();
2462                         }
2463                         tabRuns[runCount] = i;
2464                         runCount++;
2465                         rect.y = y;
2466                     }
2467                     // Initialize x position in case there's just one column
2468                     rect.x = x;
2469                     rect.width = maxTabWidth/* - 2*/;
2470 
2471                 }
2472                 if (i == selectedIndex) {
2473                     selectedRun = runCount - 1;
2474                 }
2475             }
2476 
2477             if (runCount > 1) {
2478                 // Re-distribute tabs in case last run has leftover space
2479                 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns ? y : x, returnAt);
2480 
2481                 selectedRun = getRunForTab(tabCount, selectedIndex);
2482 
2483                 // Rotate run array so that selected run is first
2484                 if (shouldRotateTabRuns(tabPlacement)) {
2485                     rotateTabRuns(tabPlacement, selectedRun);
2486                 }
2487             }
2488 
2489             // Step through runs from back to front to calculate
2490             // tab y locations and to pad runs appropriately
2491             for (i = runCount - 1; i >= 0; i--) {
2492                 final int start = tabRuns[i];
2493                 final int next = tabRuns[i == (runCount - 1) ? 0 : i + 1];
2494                 final int end = (next != 0 ? next - 1 : tabCount - 1);
2495                 if (!verticalTabRuns) {
2496                     for (j = start; j <= end; j++) {
2497                         rect = rects[j];
2498                         rect.y = y;
2499                         rect.x += getTabRunIndent(tabPlacement, i);
2500                     }
2501                     if (shouldPadTabRun(tabPlacement, i)) {
2502                         padTabRun(tabPlacement, start, end, returnAt);
2503                     }
2504                     if (tabPlacement == BOTTOM) {
2505                         y -= (maxTabHeight - tabRunOverlay);
2506                     } else {
2507                         y += (maxTabHeight - tabRunOverlay);
2508                     }
2509                 } else {
2510                     for (j = start; j <= end; j++) {
2511                         rect = rects[j];
2512                         rect.x = x;
2513                         rect.y += getTabRunIndent(tabPlacement, i);
2514                     }
2515                     if (shouldPadTabRun(tabPlacement, i)) {
2516                         padTabRun(tabPlacement, start, end, returnAt);
2517                     }
2518                     if (tabPlacement == RIGHT) {
2519                         x -= (maxTabWidth - tabRunOverlay);
2520                     } else {
2521                         x += (maxTabWidth - tabRunOverlay);
2522                     }
2523                 }
2524             }
2525 
2526             // Pad the selected tab so that it appears raised in front
2527             padSelectedTab(tabPlacement, selectedIndex);
2528 
2529             // if right to left and tab placement on the top or
2530             // the bottom, flip x positions and adjust by widths
2531             if (!leftToRight && !verticalTabRuns) {
2532                 final int rightMargin = size.width - (insets.right + tabAreaInsets.right);
2533                 for (i = 0; i < tabCount; i++) {
2534                     rects[i].x = rightMargin - rects[i].x - rects[i].width;
2535                 }
2536             }
2537         }
2538 
2539         /*
2540          * Rotates the run-index array so that the selected run is run[0]
2541          */
2542         protected void rotateTabRuns(final int tabPlacement, final int selectedRun) {
2543             for (int i = 0; i < selectedRun; i++) {
2544                 final int save = tabRuns[0];
2545                 for (int j = 1; j < runCount; j++) {
2546                     tabRuns[j - 1] = tabRuns[j];
2547                 }
2548                 tabRuns[runCount - 1] = save;
2549             }
2550         }
2551 
2552         protected void normalizeTabRuns(final int tabPlacement, final int tabCount, final int start, final int max) {
2553             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2554             int run = runCount - 1;
2555             boolean keepAdjusting = true;
2556             double weight = 1.25;
2557 
2558             // At this point the tab runs are packed to fit as many
2559             // tabs as possible, which can leave the last run with a lot
2560             // of extra space (resulting in very fat tabs on the last run).
2561             // So we'll attempt to distribute this extra space more evenly
2562             // across the runs in order to make the runs look more consistent.
2563             //
2564             // Starting with the last run, determine whether the last tab in
2565             // the previous run would fit (generously) in this run; if so,
2566             // move tab to current run and shift tabs accordingly.  Cycle
2567             // through remaining runs using the same algorithm.
2568             //
2569             while (keepAdjusting) {
2570                 final int last = lastTabInRun(tabCount, run);
2571                 final int prevLast = lastTabInRun(tabCount, run - 1);
2572                 int end;
2573                 int prevLastLen;
2574 
2575                 if (!verticalTabRuns) {
2576                     end = rects[last].x + rects[last].width;
2577                     prevLastLen = (int)(maxTabWidth * weight);
2578                 } else {
2579                     end = rects[last].y + rects[last].height;
2580                     prevLastLen = (int)(maxTabHeight * weight * 2);
2581                 }
2582 
2583                 // Check if the run has enough extra space to fit the last tab
2584                 // from the previous row...
2585                 if (max - end > prevLastLen) {
2586 
2587                     // Insert tab from previous row and shift rest over
2588                     tabRuns[run] = prevLast;
2589                     if (!verticalTabRuns) {
2590                         rects[prevLast].x = start;
2591                     } else {
2592                         rects[prevLast].y = start;
2593                     }
2594                     for (int i = prevLast + 1; i <= last; i++) {
2595                         if (!verticalTabRuns) {
2596                             rects[i].x = rects[i - 1].x + rects[i - 1].width;
2597                         } else {
2598                             rects[i].y = rects[i - 1].y + rects[i - 1].height;
2599                         }
2600                     }
2601 
2602                 } else if (run == runCount - 1) {
2603                     // no more room left in last run, so we're done!
2604                     keepAdjusting = false;
2605                 }
2606                 if (run - 1 > 0) {
2607                     // check previous run next...
2608                     run -= 1;
2609                 } else {
2610                     // check last run again...but require a higher ratio
2611                     // of extraspace-to-tabsize because we don't want to
2612                     // end up with too many tabs on the last run!
2613                     run = runCount - 1;
2614                     weight += .25;
2615                 }
2616             }
2617         }
2618 
2619         protected void padTabRun(final int tabPlacement, final int start, final int end, final int max) {
2620             final Rectangle lastRect = rects[end];
2621             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2622                 final int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
2623                 final int deltaWidth = max - (lastRect.x + lastRect.width);
2624                 final float factor = (float)deltaWidth / (float)runWidth;
2625 
2626                 for (int j = start; j <= end; j++) {
2627                     final Rectangle pastRect = rects[j];
2628                     if (j > start) {
2629                         pastRect.x = rects[j - 1].x + rects[j - 1].width;
2630                     }
2631                     pastRect.width += Math.round(pastRect.width * factor);
2632                 }
2633                 lastRect.width = max - lastRect.x;
2634             } else {
2635                 final int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
2636                 final int deltaHeight = max - (lastRect.y + lastRect.height);
2637                 final float factor = (float)deltaHeight / (float)runHeight;
2638 
2639                 for (int j = start; j <= end; j++) {
2640                     final Rectangle pastRect = rects[j];
2641                     if (j > start) {
2642                         pastRect.y = rects[j - 1].y + rects[j - 1].height;
2643                     }
2644                     pastRect.height += Math.round(pastRect.height * factor);
2645                 }
2646                 lastRect.height = max - lastRect.y;
2647             }
2648         }
2649 
2650         protected void padSelectedTab(final int tabPlacement, final int selectedIndex) {
2651 
2652             if (selectedIndex >= 0) {
2653                 final Rectangle selRect = rects[selectedIndex];
2654                 final Insets padInsets = getSelectedTabPadInsets(tabPlacement);
2655                 selRect.x -= padInsets.left;
2656                 selRect.width += (padInsets.left + padInsets.right);
2657                 selRect.y -= padInsets.top;
2658                 selRect.height += (padInsets.top + padInsets.bottom);
2659 
2660                 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
2661                     // do not expand selected tab more then necessary
2662                     final Dimension size = tabPane.getSize();
2663                     final Insets insets = tabPane.getInsets();
2664 
2665                     if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) {
2666                         final int top = insets.top - selRect.y;
2667                         if (top > 0) {
2668                             selRect.y += top;
2669                             selRect.height -= top;
2670                         }
2671                         final int bottom = (selRect.y + selRect.height) + insets.bottom - size.height;
2672                         if (bottom > 0) {
2673                             selRect.height -= bottom;
2674                         }
2675                     } else {
2676                         final int left = insets.left - selRect.x;
2677                         if (left > 0) {
2678                             selRect.x += left;
2679                             selRect.width -= left;
2680                         }
2681                         final int right = (selRect.x + selRect.width) + insets.right - size.width;
2682                         if (right > 0) {
2683                             selRect.width -= right;
2684                         }
2685                     }
2686                 }
2687             }
2688         }
2689     }
2690 
2691     class TabbedPaneScrollLayout extends TabbedPaneLayout {
2692 
2693         protected int preferredTabAreaHeight(final int tabPlacement, final int width) {
2694             return calculateMaxTabHeight(tabPlacement);
2695         }
2696 
2697         protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
2698             return calculateMaxTabWidth(tabPlacement);
2699         }
2700 
2701         @SuppressWarnings("deprecation")
2702         public void layoutContainer(final Container parent) {
2703             /* Some of the code in this method deals with changing the
2704              * visibility of components to hide and show the contents for the
2705              * selected tab. This is older code that has since been duplicated
2706              * in JTabbedPane.fireStateChanged(), so as to allow visibility
2707              * changes to happen sooner (see the note there). This code remains
2708              * for backward compatibility as there are some cases, such as
2709              * subclasses that don't fireStateChanged() where it may be used.
2710              * Any changes here need to be kept in synch with
2711              * JTabbedPane.fireStateChanged().
2712              */
2713 
2714             setRolloverTab(-1);
2715 
2716             final int tabPlacement = tabPane.getTabPlacement();
2717             final int tabCount = tabPane.getTabCount();
2718             final Insets insets = tabPane.getInsets();
2719             final int selectedIndex = tabPane.getSelectedIndex();
2720             final Component visibleComponent = getVisibleComponent();
2721 
2722             calculateLayoutInfo();
2723 
2724             Component selectedComponent = null;
2725             if (selectedIndex < 0) {
2726                 if (visibleComponent != null) {
2727                     // The last tab was removed, so remove the component
2728                     setVisibleComponent(null);
2729                 }
2730             } else {
2731                 selectedComponent = tabPane.getComponentAt(selectedIndex);
2732             }
2733 
2734             if (tabPane.getTabCount() == 0) {
2735                 tabScroller.croppedEdge.resetParams();
2736                 tabScroller.scrollForwardButton.setVisible(false);
2737                 tabScroller.scrollBackwardButton.setVisible(false);
2738                 return;
2739             }
2740 
2741             boolean shouldChangeFocus = false;
2742 
2743             // In order to allow programs to use a single component
2744             // as the display for multiple tabs, we will not change
2745             // the visible compnent if the currently selected tab
2746             // has a null component.  This is a bit dicey, as we don't
2747             // explicitly state we support this in the spec, but since
2748             // programs are now depending on this, we're making it work.
2749             //
2750             if (selectedComponent != null) {
2751                 if (selectedComponent != visibleComponent && visibleComponent != null) {
2752                     if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
2753                         shouldChangeFocus = true;
2754                     }
2755                 }
2756                 setVisibleComponent(selectedComponent);
2757             }
2758             int tx, ty, tw, th; // tab area bounds
2759             int cx, cy, cw, ch; // content area bounds
2760             final Insets contentInsets = getContentBorderInsets(tabPlacement);
2761             final Rectangle bounds = tabPane.getBounds();
2762             final int numChildren = tabPane.getComponentCount();
2763 
2764             if (numChildren > 0) {
2765                 switch (tabPlacement) {
2766                     case LEFT:
2767                         // calculate tab area bounds
2768                         tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2769                         th = bounds.height - insets.top - insets.bottom;
2770                         tx = insets.left;
2771                         ty = insets.top;
2772 
2773                         // calculate content area bounds
2774                         cx = tx + tw + contentInsets.left;
2775                         cy = ty + contentInsets.top;
2776                         cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right;
2777                         ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
2778                         break;
2779                     case RIGHT:
2780                         // calculate tab area bounds
2781                         tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2782                         th = bounds.height - insets.top - insets.bottom;
2783                         tx = bounds.width - insets.right - tw;
2784                         ty = insets.top;
2785 
2786                         // calculate content area bounds
2787                         cx = insets.left + contentInsets.left;
2788                         cy = insets.top + contentInsets.top;
2789                         cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right;
2790                         ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
2791                         break;
2792                     case BOTTOM:
2793                         // calculate tab area bounds
2794                         tw = bounds.width - insets.left - insets.right;
2795                         th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2796                         tx = insets.left;
2797                         ty = bounds.height - insets.bottom - th;
2798 
2799                         // calculate content area bounds
2800                         cx = insets.left + contentInsets.left;
2801                         cy = insets.top + contentInsets.top;
2802                         cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
2803                         ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom;
2804                         break;
2805                     case TOP:
2806                     default:
2807                         // calculate tab area bounds
2808                         tw = bounds.width - insets.left - insets.right;
2809                         th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2810                         tx = insets.left;
2811                         ty = insets.top;
2812 
2813                         // calculate content area bounds
2814                         cx = tx + contentInsets.left;
2815                         cy = ty + th + contentInsets.top;
2816                         cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
2817                         ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom;
2818                 }
2819 
2820                 for (int i = 0; i < numChildren; i++) {
2821                     final Component child = tabPane.getComponent(i);
2822 
2823                     if (tabScroller != null && child == tabScroller.viewport) {
2824                         final JViewport viewport = (JViewport)child;
2825                         final Rectangle viewRect = viewport.getViewRect();
2826                         int vw = tw;
2827                         int vh = th;
2828                         final Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
2829                         switch (tabPlacement) {
2830                             case LEFT:
2831                             case RIGHT:
2832                                 final int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
2833                                 if (totalTabHeight > th) {
2834                                     // Allow space for scrollbuttons
2835                                     vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0;
2836                                     if (totalTabHeight - viewRect.y <= vh) {
2837                                         // Scrolled to the end, so ensure the viewport size is
2838                                         // such that the scroll offset aligns with a tab
2839                                         vh = totalTabHeight - viewRect.y;
2840                                     }
2841                                 }
2842                                 break;
2843                             case BOTTOM:
2844                             case TOP:
2845                             default:
2846                                 final int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
2847                                 if (totalTabWidth > tw) {
2848                                     // Need to allow space for scrollbuttons
2849                                     vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0;
2850                                     if (totalTabWidth - viewRect.x <= vw) {
2851                                         // Scrolled to the end, so ensure the viewport size is
2852                                         // such that the scroll offset aligns with a tab
2853                                         vw = totalTabWidth - viewRect.x;
2854                                     }
2855                                 }
2856                         }
2857                         child.setBounds(tx, ty, vw, vh);
2858 
2859                     } else if (tabScroller != null && (child == tabScroller.scrollForwardButton || child == tabScroller.scrollBackwardButton)) {
2860                         final Component scrollbutton = child;
2861                         final Dimension bsize = scrollbutton.getPreferredSize();
2862                         int bx = 0;
2863                         int by = 0;
2864                         final int bw = bsize.width;
2865                         final int bh = bsize.height;
2866                         boolean visible = false;
2867 
2868                         switch (tabPlacement) {
2869                             case LEFT:
2870                             case RIGHT:
2871                                 final int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
2872                                 if (totalTabHeight > th) {
2873                                     visible = true;
2874                                     bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx);
2875                                     by = (child == tabScroller.scrollForwardButton) ? bounds.height - insets.bottom - bsize.height : bounds.height - insets.bottom - 2 * bsize.height;
2876                                 }
2877                                 break;
2878 
2879                             case BOTTOM:
2880                             case TOP:
2881                             default:
2882                                 final int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
2883 
2884                                 if (totalTabWidth > tw) {
2885                                     visible = true;
2886                                     bx = (child == tabScroller.scrollForwardButton) ? bounds.width - insets.left - bsize.width : bounds.width - insets.left - 2 * bsize.width;
2887                                     by = (tabPlacement == TOP ? ty + th - bsize.height : ty);
2888                                 }
2889                         }
2890                         child.setVisible(visible);
2891                         if (visible) {
2892                             child.setBounds(bx, by, bw, bh);
2893                         }
2894 
2895                     } else {
2896                         // All content children...
2897                         child.setBounds(cx, cy, cw, ch);
2898                     }
2899                 }
2900                 super.layoutTabComponents();
2901                 layoutCroppedEdge();
2902                 if (shouldChangeFocus) {
2903                     if (!requestFocusForVisibleComponent()) {
2904                         tabPane.requestFocus();
2905                     }
2906                 }
2907             }
2908         }
2909 
2910         private void layoutCroppedEdge() {
2911             tabScroller.croppedEdge.resetParams();
2912             final Rectangle viewRect = tabScroller.viewport.getViewRect();
2913             int cropline;
2914             for (int i = 0; i < rects.length; i++) {
2915                 final Rectangle tabRect = rects[i];
2916                 switch (tabPane.getTabPlacement()) {
2917                     case LEFT:
2918                     case RIGHT:
2919                         cropline = viewRect.y + viewRect.height;
2920                         if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
2921                             tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1, -currentTabAreaInsets.left, 0);
2922                         }
2923                         break;
2924                     case TOP:
2925                     case BOTTOM:
2926                     default:
2927                         cropline = viewRect.x + viewRect.width;
2928                         if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) {
2929                             tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1, 0, -currentTabAreaInsets.top);
2930                         }
2931                 }
2932             }
2933         }
2934 
2935         protected void calculateTabRects(final int tabPlacement, final int tabCount) {
2936             final FontMetrics metrics = getFontMetrics();
2937             final Dimension size = tabPane.getSize();
2938             final Insets insets = tabPane.getInsets();
2939             final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2940             final int fontHeight = metrics.getHeight();
2941             final int selectedIndex = tabPane.getSelectedIndex();
2942             int i;
2943             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2944             boolean leftToRight = AquaUtils.isLeftToRight(tabPane);
2945             final int x = tabAreaInsets.left;
2946             final int y = tabAreaInsets.top;
2947             int totalWidth = 0;
2948             int totalHeight = 0;
2949 
2950             //
2951             // Calculate bounds within which a tab run must fit
2952             //
2953             switch (tabPlacement) {
2954                 case LEFT:
2955                 case RIGHT:
2956                     maxTabWidth = calculateMaxTabWidth(tabPlacement);
2957                     break;
2958                 case BOTTOM:
2959                 case TOP:
2960                 default:
2961                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
2962             }
2963 
2964             runCount = 0;
2965             selectedRun = -1;
2966 
2967             if (tabCount == 0) {
2968                 return;
2969             }
2970 
2971             selectedRun = 0;
2972             runCount = 1;
2973 
2974             // Run through tabs and lay them out in a single run
2975             Rectangle rect;
2976             for (i = 0; i < tabCount; i++) {
2977                 rect = rects[i];
2978 
2979                 if (!verticalTabRuns) {
2980                     // Tabs on TOP or BOTTOM....
2981                     if (i > 0) {
2982                         rect.x = rects[i - 1].x + rects[i - 1].width;
2983                     } else {
2984                         tabRuns[0] = 0;
2985                         maxTabWidth = 0;
2986                         totalHeight += maxTabHeight;
2987                         rect.x = x;
2988                     }
2989                     rect.width = calculateTabWidth(tabPlacement, i, metrics);
2990                     totalWidth = rect.x + rect.width;
2991                     maxTabWidth = Math.max(maxTabWidth, rect.width);
2992 
2993                     rect.y = y;
2994                     rect.height = maxTabHeight/* - 2*/;
2995 
2996                 } else {
2997                     // Tabs on LEFT or RIGHT...
2998                     if (i > 0) {
2999                         rect.y = rects[i - 1].y + rects[i - 1].height;
3000                     } else {
3001                         tabRuns[0] = 0;
3002                         maxTabHeight = 0;
3003                         totalWidth = maxTabWidth;
3004                         rect.y = y;
3005                     }
3006                     rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3007                     totalHeight = rect.y + rect.height;
3008                     maxTabHeight = Math.max(maxTabHeight, rect.height);
3009 
3010                     rect.x = x;
3011                     rect.width = maxTabWidth/* - 2*/;
3012 
3013                 }
3014             }
3015 
3016             if (tabsOverlapBorder) {
3017                 // Pad the selected tab so that it appears raised in front
3018                 padSelectedTab(tabPlacement, selectedIndex);
3019             }
3020 
3021             // if right to left and tab placement on the top or
3022             // the bottom, flip x positions and adjust by widths
3023             if (!leftToRight && !verticalTabRuns) {
3024                 final int rightMargin = size.width - (insets.right + tabAreaInsets.right);
3025                 for (i = 0; i < tabCount; i++) {
3026                     rects[i].x = rightMargin - rects[i].x - rects[i].width;
3027                 }
3028             }
3029             tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
3030         }
3031     }
3032 
3033     private class ScrollableTabSupport implements ActionListener, ChangeListener {
3034         public ScrollableTabViewport viewport;
3035         public ScrollableTabPanel tabPanel;
3036         public JButton scrollForwardButton;
3037         public JButton scrollBackwardButton;
3038         public CroppedEdge croppedEdge;
3039         public int leadingTabIndex;
3040 
3041         private final Point tabViewPosition = new Point(0, 0);
3042 
3043         ScrollableTabSupport(final int tabPlacement) {
3044             viewport = new ScrollableTabViewport();
3045             tabPanel = new ScrollableTabPanel();
3046             viewport.setView(tabPanel);
3047             viewport.addChangeListener(this);
3048             croppedEdge = new CroppedEdge();
3049             createButtons();
3050         }
3051 
3052         /**
3053          * Recreates the scroll buttons and adds them to the TabbedPane.
3054          */
3055         void createButtons() {
3056             if (scrollForwardButton != null) {
3057                 tabPane.remove(scrollForwardButton);
3058                 scrollForwardButton.removeActionListener(this);
3059                 tabPane.remove(scrollBackwardButton);
3060                 scrollBackwardButton.removeActionListener(this);
3061             }
3062             final int tabPlacement = tabPane.getTabPlacement();
3063             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3064                 scrollForwardButton = createScrollButton(EAST);
3065                 scrollBackwardButton = createScrollButton(WEST);
3066 
3067             } else { // tabPlacement = LEFT || RIGHT
3068                 scrollForwardButton = createScrollButton(SOUTH);
3069                 scrollBackwardButton = createScrollButton(NORTH);
3070             }
3071             scrollForwardButton.addActionListener(this);
3072             scrollBackwardButton.addActionListener(this);
3073             tabPane.add(scrollForwardButton);
3074             tabPane.add(scrollBackwardButton);
3075         }
3076 
3077         public void scrollForward(final int tabPlacement) {
3078             final Dimension viewSize = viewport.getViewSize();
3079             final Rectangle viewRect = viewport.getViewRect();
3080 
3081             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3082                 if (viewRect.width >= viewSize.width - viewRect.x) {
3083                     return; // no room left to scroll
3084                 }
3085             } else { // tabPlacement == LEFT || tabPlacement == RIGHT
3086                 if (viewRect.height >= viewSize.height - viewRect.y) {
3087                     return;
3088                 }
3089             }
3090             setLeadingTabIndex(tabPlacement, leadingTabIndex + 1);
3091         }
3092 
3093         public void scrollBackward(final int tabPlacement) {
3094             if (leadingTabIndex == 0) {
3095                 return; // no room left to scroll
3096             }
3097             setLeadingTabIndex(tabPlacement, leadingTabIndex - 1);
3098         }
3099 
3100         public void setLeadingTabIndex(final int tabPlacement, final int index) {
3101             leadingTabIndex = index;
3102             final Dimension viewSize = viewport.getViewSize();
3103             final Rectangle viewRect = viewport.getViewRect();
3104 
3105             switch (tabPlacement) {
3106                 case TOP:
3107                 case BOTTOM:
3108                     tabViewPosition.x = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].x;
3109 
3110                     if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
3111                         // We've scrolled to the end, so adjust the viewport size
3112                         // to ensure the view position remains aligned on a tab boundary
3113                         final Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x, viewRect.height);
3114                         viewport.setExtentSize(extentSize);
3115                     }
3116                     break;
3117                 case LEFT:
3118                 case RIGHT:
3119                     tabViewPosition.y = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].y;
3120 
3121                     if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
3122                         // We've scrolled to the end, so adjust the viewport size
3123                         // to ensure the view position remains aligned on a tab boundary
3124                         final Dimension extentSize = new Dimension(viewRect.width, viewSize.height - tabViewPosition.y);
3125                         viewport.setExtentSize(extentSize);
3126                     }
3127             }
3128             viewport.setViewPosition(tabViewPosition);
3129         }
3130 
3131         public void stateChanged(final ChangeEvent e) {
3132             updateView();
3133         }
3134 
3135         private void updateView() {
3136             final int tabPlacement = tabPane.getTabPlacement();
3137             final int tabCount = tabPane.getTabCount();
3138             final Rectangle vpRect = viewport.getBounds();
3139             final Dimension viewSize = viewport.getViewSize();
3140             final Rectangle viewRect = viewport.getViewRect();
3141 
3142             leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
3143 
3144             // If the tab isn't right aligned, adjust it.
3145             if (leadingTabIndex + 1 < tabCount) {
3146                 switch (tabPlacement) {
3147                     case TOP:
3148                     case BOTTOM:
3149                         if (rects[leadingTabIndex].x < viewRect.x) {
3150                             leadingTabIndex++;
3151                         }
3152                         break;
3153                     case LEFT:
3154                     case RIGHT:
3155                         if (rects[leadingTabIndex].y < viewRect.y) {
3156                             leadingTabIndex++;
3157                         }
3158                         break;
3159                 }
3160             }
3161             final Insets contentInsets = getContentBorderInsets(tabPlacement);
3162             switch (tabPlacement) {
3163                 case LEFT:
3164                     tabPane.repaint(vpRect.x + vpRect.width, vpRect.y, contentInsets.left, vpRect.height);
3165                     scrollBackwardButton.setEnabled(viewRect.y > 0 && leadingTabIndex > 0);
3166                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.height - viewRect.y > viewRect.height);
3167                     break;
3168                 case RIGHT:
3169                     tabPane.repaint(vpRect.x - contentInsets.right, vpRect.y, contentInsets.right, vpRect.height);
3170                     scrollBackwardButton.setEnabled(viewRect.y > 0 && leadingTabIndex > 0);
3171                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.height - viewRect.y > viewRect.height);
3172                     break;
3173                 case BOTTOM:
3174                     tabPane.repaint(vpRect.x, vpRect.y - contentInsets.bottom, vpRect.width, contentInsets.bottom);
3175                     scrollBackwardButton.setEnabled(viewRect.x > 0 && leadingTabIndex > 0);
3176                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.width - viewRect.x > viewRect.width);
3177                     break;
3178                 case TOP:
3179                 default:
3180                     tabPane.repaint(vpRect.x, vpRect.y + vpRect.height, vpRect.width, contentInsets.top);
3181                     scrollBackwardButton.setEnabled(viewRect.x > 0 && leadingTabIndex > 0);
3182                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.width - viewRect.x > viewRect.width);
3183             }
3184         }
3185 
3186         /**
3187          * ActionListener for the scroll buttons.
3188          */
3189         public void actionPerformed(final ActionEvent e) {
3190             final ActionMap map = tabPane.getActionMap();
3191 
3192             if (map != null) {
3193                 String actionKey;
3194 
3195                 if (e.getSource() == scrollForwardButton) {
3196                     actionKey = "scrollTabsForwardAction";
3197                 } else {
3198                     actionKey = "scrollTabsBackwardAction";
3199                 }
3200                 final Action action = map.get(actionKey);
3201 
3202                 if (action != null && action.isEnabled()) {
3203                     action.actionPerformed(new ActionEvent(tabPane, ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
3204                 }
3205             }
3206         }
3207 
3208         public String toString() {
3209             return new String("viewport.viewSize=" + viewport.getViewSize() + "\n" + "viewport.viewRectangle=" + viewport.getViewRect() + "\n" + "leadingTabIndex=" + leadingTabIndex + "\n" + "tabViewPosition=" + tabViewPosition);
3210         }
3211 
3212     }
3213 
3214     @SuppressWarnings("serial") // Superclass is not serializable across versions
3215     private class ScrollableTabViewport extends JViewport implements UIResource {
3216         public ScrollableTabViewport() {
3217             super();
3218             setName("TabbedPane.scrollableViewport");
3219             setScrollMode(SIMPLE_SCROLL_MODE);
3220             setOpaque(tabPane.isOpaque());
3221             Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3222             if (bgColor == null) {
3223                 bgColor = tabPane.getBackground();
3224             }
3225             setBackground(bgColor);
3226         }
3227     }
3228 
3229     @SuppressWarnings("serial") // Superclass is not serializable across versions
3230     private class ScrollableTabPanel extends JPanel implements UIResource {
3231         public ScrollableTabPanel() {
3232             super(null);
3233             setOpaque(tabPane.isOpaque());
3234             Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3235             if (bgColor == null) {
3236                 bgColor = tabPane.getBackground();
3237             }
3238             setBackground(bgColor);
3239         }
3240 
3241         public void paintComponent(final Graphics g) {
3242             super.paintComponent(g);
3243             AquaTabbedPaneCopyFromBasicUI.this.paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
3244             if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
3245                 final Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
3246                 g.translate(croppedRect.x, croppedRect.y);
3247                 tabScroller.croppedEdge.paintComponent(g);
3248                 g.translate(-croppedRect.x, -croppedRect.y);
3249             }
3250         }
3251 
3252         public void doLayout() {
3253             if (getComponentCount() > 0) {
3254                 final Component child = getComponent(0);
3255                 child.setBounds(0, 0, getWidth(), getHeight());
3256             }
3257         }
3258     }
3259 
3260     @SuppressWarnings("serial") // Superclass is not serializable across versions
3261     private class ScrollableTabButton extends javax.swing.plaf.basic.BasicArrowButton implements UIResource, SwingConstants {
3262         public ScrollableTabButton(final int direction) {
3263             super(direction, UIManager.getColor("TabbedPane.selected"), UIManager.getColor("TabbedPane.shadow"), UIManager.getColor("TabbedPane.darkShadow"), UIManager.getColor("TabbedPane.highlight"));
3264         }
3265     }
3266 
3267 // Controller: event listeners
3268 
3269     private class Handler implements ChangeListener, ContainerListener, FocusListener, MouseListener, MouseMotionListener, PropertyChangeListener {
3270         //
3271         // PropertyChangeListener
3272         //
3273         public void propertyChange(final PropertyChangeEvent e) {
3274             final JTabbedPane pane = (JTabbedPane)e.getSource();
3275             final String name = e.getPropertyName();
3276             final boolean isScrollLayout = scrollableTabLayoutEnabled();
3277             if (name == "mnemonicAt") {
3278                 updateMnemonics();
3279                 pane.repaint();
3280             } else if (name == "displayedMnemonicIndexAt") {
3281                 pane.repaint();
3282             } else if (name == "indexForTitle") {
3283                 calculatedBaseline = false;
3284                 updateHtmlViews((Integer) e.getNewValue(), false);
3285             } else if (name == "tabLayoutPolicy") {
3286                 AquaTabbedPaneCopyFromBasicUI.this.uninstallUI(pane);
3287                 AquaTabbedPaneCopyFromBasicUI.this.installUI(pane);
3288                 calculatedBaseline = false;
3289             } else if (name == "tabPlacement") {
3290                 if (scrollableTabLayoutEnabled()) {
3291                     tabScroller.createButtons();
3292                 }
3293                 calculatedBaseline = false;
3294             } else if (name == "opaque" && isScrollLayout) {
3295                 final boolean newVal = ((Boolean)e.getNewValue()).booleanValue();
3296                 tabScroller.tabPanel.setOpaque(newVal);
3297                 tabScroller.viewport.setOpaque(newVal);
3298             } else if (name == "background" && isScrollLayout) {
3299                 final Color newVal = (Color)e.getNewValue();
3300                 tabScroller.tabPanel.setBackground(newVal);
3301                 tabScroller.viewport.setBackground(newVal);
3302                 final Color newColor = selectedColor == null ? newVal : selectedColor;
3303                 tabScroller.scrollForwardButton.setBackground(newColor);
3304                 tabScroller.scrollBackwardButton.setBackground(newColor);
3305             } else if (name == "indexForTabComponent") {
3306                 if (tabContainer != null) {
3307                     tabContainer.removeUnusedTabComponents();
3308                 }
3309                 final Component c = tabPane.getTabComponentAt((Integer)e.getNewValue());
3310                 if (c != null) {
3311                     if (tabContainer == null) {
3312                         installTabContainer();
3313                     } else {
3314                         tabContainer.add(c);
3315                     }
3316                 }
3317                 tabPane.revalidate();
3318                 tabPane.repaint();
3319                 calculatedBaseline = false;
3320             } else if (name == "indexForNullComponent") {
3321                 isRunsDirty = true;
3322                 updateHtmlViews((Integer) e.getNewValue(), true);
3323             } else if (name == "font") {
3324                 calculatedBaseline = false;
3325             }
3326         }
3327 
3328         //
3329         // ChangeListener
3330         //
3331         public void stateChanged(final ChangeEvent e) {
3332             final JTabbedPane tabPane = (JTabbedPane)e.getSource();
3333             tabPane.revalidate();
3334             tabPane.repaint();
3335 
3336             setFocusIndex(tabPane.getSelectedIndex(), false);
3337 
3338             if (scrollableTabLayoutEnabled()) {
3339                 final int index = tabPane.getSelectedIndex();
3340                 if (index < rects.length && index != -1) {
3341                     tabScroller.tabPanel.scrollRectToVisible((Rectangle)rects[index].clone());
3342                 }
3343             }
3344         }
3345 
3346         //
3347         // MouseListener
3348         //
3349         public void mouseClicked(final MouseEvent e) {}
3350 
3351         public void mouseReleased(final MouseEvent e) {}
3352 
3353         public void mouseEntered(final MouseEvent e) {
3354             setRolloverTab(e.getX(), e.getY());
3355         }
3356 
3357         public void mouseExited(final MouseEvent e) {
3358             setRolloverTab(-1);
3359         }
3360 
3361         public void mousePressed(final MouseEvent e) {
3362             if (!tabPane.isEnabled()) {
3363                 return;
3364             }
3365             final int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
3366             if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
3367                 if (tabIndex != tabPane.getSelectedIndex()) {
3368                     // Clicking on unselected tab, change selection, do NOT
3369                     // request focus.
3370                     // This will trigger the focusIndex to change by way
3371                     // of stateChanged.
3372                     tabPane.setSelectedIndex(tabIndex);
3373                 } else if (tabPane.isRequestFocusEnabled()) {
3374                     // Clicking on selected tab, try and give the tabbedpane
3375                     // focus.  Repaint will occur in focusGained.
3376                     tabPane.requestFocus();
3377                 }
3378             }
3379         }
3380 
3381         //
3382         // MouseMotionListener
3383         //
3384         public void mouseDragged(final MouseEvent e) {}
3385 
3386         public void mouseMoved(final MouseEvent e) {
3387             setRolloverTab(e.getX(), e.getY());
3388         }
3389 
3390         //
3391         // FocusListener
3392         //
3393         public void focusGained(final FocusEvent e) {
3394             setFocusIndex(tabPane.getSelectedIndex(), true);
3395         }
3396 
3397         public void focusLost(final FocusEvent e) {
3398             repaintTab(focusIndex);
3399         }
3400 
3401         //
3402         // ContainerListener
3403         //
3404         /* GES 2/3/99:
3405            The container listener code was added to support HTML
3406            rendering of tab titles.
3407 
3408            Ideally, we would be able to listen for property changes
3409            when a tab is added or its text modified.  At the moment
3410            there are no such events because the Beans spec doesn't
3411            allow 'indexed' property changes (i.e. tab 2's text changed
3412            from A to B).
3413 
3414            In order to get around this, we listen for tabs to be added
3415            or removed by listening for the container events.  we then
3416            queue up a runnable (so the component has a chance to complete
3417            the add) which checks the tab title of the new component to see
3418            if it requires HTML rendering.
3419 
3420            The Views (one per tab title requiring HTML rendering) are
3421            stored in the htmlViews Vector, which is only allocated after
3422            the first time we run into an HTML tab.  Note that this vector
3423            is kept in step with the number of pages, and nulls are added
3424            for those pages whose tab title do not require HTML rendering.
3425 
3426            This makes it easy for the paint and layout code to tell
3427            whether to invoke the HTML engine without having to check
3428            the string during time-sensitive operations.
3429 
3430            When we have added a way to listen for tab additions and
3431            changes to tab text, this code should be removed and
3432            replaced by something which uses that.  */
3433 
3434         public void componentAdded(final ContainerEvent e) {
3435             final JTabbedPane tp = (JTabbedPane)e.getContainer();
3436             final Component child = e.getChild();
3437             if (child instanceof UIResource) {
3438                 return;
3439             }
3440             isRunsDirty = true;
3441             updateHtmlViews(tp.indexOfComponent(child), true);
3442         }
3443 
3444         private void updateHtmlViews(int index, boolean inserted) {
3445             final String title = tabPane.getTitleAt(index);
3446             final boolean isHTML = BasicHTML.isHTMLString(title);
3447             if (isHTML) {
3448                 if (htmlViews == null) { // Initialize vector
3449                     htmlViews = createHTMLVector();
3450                 } else { // Vector already exists
3451                     final View v = BasicHTML.createHTMLView(tabPane, title);
3452                     setHtmlView(v, inserted, index);
3453                 }
3454             } else { // Not HTML
3455                 if (htmlViews != null) { // Add placeholder
3456                     setHtmlView(null, inserted, index);
3457                 } // else nada!
3458             }
3459             updateMnemonics();
3460         }
3461 
3462         private void setHtmlView(View v, boolean inserted, int index) {
3463             if (inserted || index >= htmlViews.size()) {
3464                 htmlViews.insertElementAt(v, index);
3465             } else {
3466                 htmlViews.setElementAt(v, index);
3467             }
3468         }
3469 
3470         public void componentRemoved(final ContainerEvent e) {
3471             final JTabbedPane tp = (JTabbedPane)e.getContainer();
3472             final Component child = e.getChild();
3473             if (child instanceof UIResource) {
3474                 return;
3475             }
3476 
3477             // NOTE 4/15/2002 (joutwate):
3478             // This fix is implemented using client properties since there is
3479             // currently no IndexPropertyChangeEvent.  Once
3480             // IndexPropertyChangeEvents have been added this code should be
3481             // modified to use it.
3482             final Integer indexObj = (Integer)tp.getClientProperty("__index_to_remove__");
3483             if (indexObj != null) {
3484                 final int index = indexObj.intValue();
3485                 if (htmlViews != null && htmlViews.size() > index) {
3486                     htmlViews.removeElementAt(index);
3487                 }
3488                 tp.putClientProperty("__index_to_remove__", null);
3489             }
3490             isRunsDirty = true;
3491             updateMnemonics();
3492 
3493             validateFocusIndex();
3494         }
3495     }
3496 
3497     /**
3498      * This class should be treated as a &quot;protected&quot; inner class.
3499      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3500      */
3501     public class PropertyChangeHandler implements PropertyChangeListener {
3502         // NOTE: This class exists only for backward compatibility. All
3503         // its functionality has been moved into Handler. If you need to add
3504         // new functionality add it to the Handler, but make sure this
3505         // class calls into the Handler.
3506         public void propertyChange(final PropertyChangeEvent e) {
3507             getHandler().propertyChange(e);
3508         }
3509     }
3510 
3511     /**
3512      * This class should be treated as a &quot;protected&quot; inner class.
3513      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3514      */
3515     public class TabSelectionHandler implements ChangeListener {
3516         // NOTE: This class exists only for backward compatibility. All
3517         // its functionality has been moved into Handler. If you need to add
3518         // new functionality add it to the Handler, but make sure this
3519         // class calls into the Handler.
3520         public void stateChanged(final ChangeEvent e) {
3521             getHandler().stateChanged(e);
3522         }
3523     }
3524 
3525     /**
3526      * This class should be treated as a &quot;protected&quot; inner class.
3527      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3528      */
3529     public class MouseHandler extends MouseAdapter {
3530         // NOTE: This class exists only for backward compatibility. All
3531         // its functionality has been moved into Handler. If you need to add
3532         // new functionality add it to the Handler, but make sure this
3533         // class calls into the Handler.
3534         public void mousePressed(final MouseEvent e) {
3535             getHandler().mousePressed(e);
3536         }
3537     }
3538 
3539     /**
3540      * This class should be treated as a &quot;protected&quot; inner class.
3541      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3542      */
3543     public class FocusHandler extends FocusAdapter {
3544         // NOTE: This class exists only for backward compatibility. All
3545         // its functionality has been moved into Handler. If you need to add
3546         // new functionality add it to the Handler, but make sure this
3547         // class calls into the Handler.
3548         public void focusGained(final FocusEvent e) {
3549             getHandler().focusGained(e);
3550         }
3551 
3552         public void focusLost(final FocusEvent e) {
3553             getHandler().focusLost(e);
3554         }
3555     }
3556 
3557     private Vector<View> createHTMLVector() {
3558         final Vector<View> htmlViews = new Vector<View>();
3559         final int count = tabPane.getTabCount();
3560         if (count > 0) {
3561             for (int i = 0; i < count; i++) {
3562                 final String title = tabPane.getTitleAt(i);
3563                 if (BasicHTML.isHTMLString(title)) {
3564                     htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
3565                 } else {
3566                     htmlViews.addElement(null);
3567                 }
3568             }
3569         }
3570         return htmlViews;
3571     }
3572 
3573     @SuppressWarnings("serial") // Superclass is not serializable across versions
3574     private class TabContainer extends JPanel implements UIResource {
3575         private boolean notifyTabbedPane = true;
3576 
3577         public TabContainer() {
3578             super(null);
3579             setOpaque(false);
3580         }
3581 
3582         public void remove(final Component comp) {
3583             final int index = tabPane.indexOfTabComponent(comp);
3584             super.remove(comp);
3585             if (notifyTabbedPane && index != -1) {
3586                 tabPane.setTabComponentAt(index, null);
3587             }
3588         }
3589 
3590         private void removeUnusedTabComponents() {
3591             for (final Component c : getComponents()) {
3592                 if (!(c instanceof UIResource)) {
3593                     final int index = tabPane.indexOfTabComponent(c);
3594                     if (index == -1) {
3595                         super.remove(c);
3596                     }
3597                 }
3598             }
3599         }
3600 
3601         public boolean isOptimizedDrawingEnabled() {
3602             return tabScroller != null && !tabScroller.croppedEdge.isParamsSet();
3603         }
3604 
3605         public void doLayout() {
3606             // We layout tabComponents in JTabbedPane's layout manager
3607             // and use this method as a hook for repainting tabs
3608             // to update tabs area e.g. when the size of tabComponent was changed
3609             if (scrollableTabLayoutEnabled()) {
3610                 tabScroller.tabPanel.repaint();
3611                 tabScroller.updateView();
3612             } else {
3613                 tabPane.repaint(getBounds());
3614             }
3615         }
3616     }
3617 
3618     @SuppressWarnings("serial") // Superclass is not serializable across versions
3619     private class CroppedEdge extends JPanel implements UIResource {
3620         private Shape shape;
3621         private int tabIndex;
3622         private int cropline;
3623         private int cropx, cropy;
3624 
3625         public CroppedEdge() {
3626             setOpaque(false);
3627         }
3628 
3629         public void setParams(final int tabIndex, final int cropline, final int cropx, final int cropy) {
3630             this.tabIndex = tabIndex;
3631             this.cropline = cropline;
3632             this.cropx = cropx;
3633             this.cropy = cropy;
3634             final Rectangle tabRect = rects[tabIndex];
3635             setBounds(tabRect);
3636             shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline);
3637             if (getParent() == null && tabContainer != null) {
3638                 tabContainer.add(this, 0);
3639             }
3640         }
3641 
3642         public void resetParams() {
3643             shape = null;
3644             if (getParent() == tabContainer && tabContainer != null) {
3645                 tabContainer.remove(this);
3646             }
3647         }
3648 
3649         public boolean isParamsSet() {
3650             return shape != null;
3651         }
3652 
3653         public int getTabIndex() {
3654             return tabIndex;
3655         }
3656 
3657         public int getCropline() {
3658             return cropline;
3659         }
3660 
3661         public int getCroppedSideWidth() {
3662             return 3;
3663         }
3664 
3665         private Color getBgColor() {
3666             final Component parent = tabPane.getParent();
3667             if (parent != null) {
3668                 final Color bg = parent.getBackground();
3669                 if (bg != null) {
3670                     return bg;
3671                 }
3672             }
3673             return UIManager.getColor("control");
3674         }
3675 
3676         protected void paintComponent(final Graphics g) {
3677             super.paintComponent(g);
3678             if (isParamsSet() && g instanceof Graphics2D) {
3679                 final Graphics2D g2 = (Graphics2D)g;
3680                 g2.clipRect(0, 0, getWidth(), getHeight());
3681                 g2.setColor(getBgColor());
3682                 g2.translate(cropx, cropy);
3683                 g2.fill(shape);
3684                 paintCroppedTabEdge(g);
3685                 g2.translate(-cropx, -cropy);
3686             }
3687         }
3688     }
3689 
3690     /**
3691      * An ActionMap that populates its contents as necessary. The
3692      * contents are populated by invoking the {@code loadActionMap}
3693      * method on the passed in Object.
3694      *
3695      * @version 1.6, 11/17/05
3696      * @author Scott Violet
3697      */
3698     @SuppressWarnings("serial") // Superclass is not serializable across versions
3699     static class LazyActionMap extends ActionMapUIResource {
3700         /**
3701          * Object to invoke {@code loadActionMap} on. This may be
3702          * a Class object.
3703          */
3704         private transient Object _loader;
3705 
3706         /**
3707          * Installs an ActionMap that will be populated by invoking the
3708          * {@code loadActionMap} method on the specified Class
3709          * when necessary.
3710          * <p>
3711          * This should be used if the ActionMap can be shared.
3712          *
3713          * @param c JComponent to install the ActionMap on.
3714          * @param loaderClass Class object that gets loadActionMap invoked
3715          *                    on.
3716          * @param defaultsKey Key to use to defaults table to check for
3717          *        existing map and what resulting Map will be registered on.
3718          */
3719         static void installLazyActionMap(final JComponent c, final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) {
3720             ActionMap map = (ActionMap)UIManager.get(defaultsKey);
3721             if (map == null) {
3722                 map = new LazyActionMap(loaderClass);
3723                 UIManager.getLookAndFeelDefaults().put(defaultsKey, map);
3724             }
3725             SwingUtilities.replaceUIActionMap(c, map);
3726         }
3727 
3728         /**
3729          * Returns an ActionMap that will be populated by invoking the
3730          * {@code loadActionMap} method on the specified Class
3731          * when necessary.
3732          * <p>
3733          * This should be used if the ActionMap can be shared.
3734          *
3735          * @param loaderClass Class object that gets loadActionMap invoked
3736          *                    on.
3737          * @param defaultsKey Key to use to defaults table to check for
3738          *        existing map and what resulting Map will be registered on.
3739          */
3740         static ActionMap getActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) {
3741             ActionMap map = (ActionMap)UIManager.get(defaultsKey);
3742             if (map == null) {
3743                 map = new LazyActionMap(loaderClass);
3744                 UIManager.getLookAndFeelDefaults().put(defaultsKey, map);
3745             }
3746             return map;
3747         }
3748 
3749         private LazyActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loader) {
3750             _loader = loader;
3751         }
3752 
3753         public void put(final Action action) {
3754             put(action.getValue(Action.NAME), action);
3755         }
3756 
3757         public void put(final Object key, final Action action) {
3758             loadIfNecessary();
3759             super.put(key, action);
3760         }
3761 
3762         public Action get(final Object key) {
3763             loadIfNecessary();
3764             return super.get(key);
3765         }
3766 
3767         public void remove(final Object key) {
3768             loadIfNecessary();
3769             super.remove(key);
3770         }
3771 
3772         public void clear() {
3773             loadIfNecessary();
3774             super.clear();
3775         }
3776 
3777         public Object[] keys() {
3778             loadIfNecessary();
3779             return super.keys();
3780         }
3781 
3782         public int size() {
3783             loadIfNecessary();
3784             return super.size();
3785         }
3786 
3787         public Object[] allKeys() {
3788             loadIfNecessary();
3789             return super.allKeys();
3790         }
3791 
3792         public void setParent(final ActionMap map) {
3793             loadIfNecessary();
3794             super.setParent(map);
3795         }
3796 
3797         private void loadIfNecessary() {
3798             if (_loader != null) {
3799                 final Object loader = _loader;
3800 
3801                 _loader = null;
3802                 final Class<?> klass = (Class<?>)loader;
3803                 try {
3804                     final java.lang.reflect.Method method = klass.getDeclaredMethod("loadActionMap", new Class<?>[] { LazyActionMap.class });
3805                     method.invoke(klass, new Object[] { this });
3806                 } catch (final NoSuchMethodException nsme) {
3807                     assert false : "LazyActionMap unable to load actions " + klass;
3808                 } catch (final IllegalAccessException iae) {
3809                     assert false : "LazyActionMap unable to load actions " + iae;
3810                 } catch (final InvocationTargetException ite) {
3811                     assert false : "LazyActionMap unable to load actions " + ite;
3812                 } catch (final IllegalArgumentException iae) {
3813                     assert false : "LazyActionMap unable to load actions " + iae;
3814                 }
3815             }
3816         }
3817     }
3818 }