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