1 /*
   2  * Copyright (c) 2011, 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</code> to create
 239      * a layout manager object to manage
 240      * the <code>JTabbedPane</code>.
 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(new Integer(mnemonic), new Integer(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))</code>.
 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</code>.
 551      * <code>index</code> 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</code>.
 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</code>
 769      * object.  Tab placement may be either:
 770      * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
 771      * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
 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</code> 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         final static String NEXT = "navigateNext";
2015         final static String PREVIOUS = "navigatePrevious";
2016         final static String RIGHT = "navigateRight";
2017         final static String LEFT = "navigateLeft";
2018         final static String UP = "navigateUp";
2019         final static String DOWN = "navigateDown";
2020         final static String PAGE_UP = "navigatePageUp";
2021         final static String PAGE_DOWN = "navigatePageDown";
2022         final static String REQUEST_FOCUS = "requestFocus";
2023         final static String REQUEST_FOCUS_FOR_VISIBLE = "requestFocusForVisibleComponent";
2024         final static String SET_SELECTED = "setSelectedIndex";
2025         final static String SELECT_FOCUSED = "selectTabWithFocus";
2026         final static String SCROLL_FORWARD = "scrollTabsForwardAction";
2027         final static 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(new Integer(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 FontMetrics metrics = getFontMetrics();
2187             final int tabCount = tabPane.getTabCount();
2188             int total = 0;
2189             if (tabCount > 0) {
2190                 int rows = 1;
2191                 int x = 0;
2192 
2193                 final int maxTabHeight = calculateMaxTabHeight(tabPlacement);
2194 
2195                 for (int i = 0; i < tabCount; i++) {
2196                     final int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
2197 
2198                     if (x != 0 && x + tabWidth > width) {
2199                         rows++;
2200                         x = 0;
2201                     }
2202                     x += tabWidth;
2203                 }
2204                 total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
2205             }
2206             return total;
2207         }
2208 
2209         protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
2210             final FontMetrics metrics = getFontMetrics();
2211             final int tabCount = tabPane.getTabCount();
2212             int total = 0;
2213             if (tabCount > 0) {
2214                 int columns = 1;
2215                 int y = 0;
2216                 final int fontHeight = metrics.getHeight();
2217 
2218                 maxTabWidth = calculateMaxTabWidth(tabPlacement);
2219 
2220                 for (int i = 0; i < tabCount; i++) {
2221                     final int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
2222 
2223                     if (y != 0 && y + tabHeight > height) {
2224                         columns++;
2225                         y = 0;
2226                     }
2227                     y += tabHeight;
2228                 }
2229                 total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
2230             }
2231             return total;
2232         }
2233 
2234         public void layoutContainer(final Container parent) {
2235             /* Some of the code in this method deals with changing the
2236              * visibility of components to hide and show the contents for the
2237              * selected tab. This is older code that has since been duplicated
2238              * in JTabbedPane.fireStateChanged(), so as to allow visibility
2239              * changes to happen sooner (see the note there). This code remains
2240              * for backward compatibility as there are some cases, such as
2241              * subclasses that don't fireStateChanged() where it may be used.
2242              * Any changes here need to be kept in synch with
2243              * JTabbedPane.fireStateChanged().
2244              */
2245 
2246             setRolloverTab(-1);
2247 
2248             final int tabPlacement = tabPane.getTabPlacement();
2249             final Insets insets = tabPane.getInsets();
2250             final int selectedIndex = tabPane.getSelectedIndex();
2251             final Component visibleComponent = getVisibleComponent();
2252 
2253             calculateLayoutInfo();
2254 
2255             Component selectedComponent = null;
2256             if (selectedIndex < 0) {
2257                 if (visibleComponent != null) {
2258                     // The last tab was removed, so remove the component
2259                     setVisibleComponent(null);
2260                 }
2261             } else {
2262                 selectedComponent = tabPane.getComponentAt(selectedIndex);
2263             }
2264             int cx, cy, cw, ch;
2265             int totalTabWidth = 0;
2266             int totalTabHeight = 0;
2267             final Insets contentInsets = getContentBorderInsets(tabPlacement);
2268 
2269             boolean shouldChangeFocus = false;
2270 
2271             // In order to allow programs to use a single component
2272             // as the display for multiple tabs, we will not change
2273             // the visible compnent if the currently selected tab
2274             // has a null component.  This is a bit dicey, as we don't
2275             // explicitly state we support this in the spec, but since
2276             // programs are now depending on this, we're making it work.
2277             //
2278             if (selectedComponent != null) {
2279                 if (selectedComponent != visibleComponent && visibleComponent != null) {
2280                     if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
2281                         shouldChangeFocus = true;
2282                     }
2283                 }
2284                 setVisibleComponent(selectedComponent);
2285             }
2286 
2287             final Rectangle bounds = tabPane.getBounds();
2288             final int numChildren = tabPane.getComponentCount();
2289 
2290             if (numChildren > 0) {
2291 
2292                 switch (tabPlacement) {
2293                     case LEFT:
2294                         totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2295                         cx = insets.left + totalTabWidth + contentInsets.left;
2296                         cy = insets.top + contentInsets.top;
2297                         break;
2298                     case RIGHT:
2299                         totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2300                         cx = insets.left + contentInsets.left;
2301                         cy = insets.top + contentInsets.top;
2302                         break;
2303                     case BOTTOM:
2304                         totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2305                         cx = insets.left + contentInsets.left;
2306                         cy = insets.top + contentInsets.top;
2307                         break;
2308                     case TOP:
2309                     default:
2310                         totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2311                         cx = insets.left + contentInsets.left;
2312                         cy = insets.top + totalTabHeight + contentInsets.top;
2313                 }
2314 
2315                 cw = bounds.width - totalTabWidth - insets.left - insets.right - contentInsets.left - contentInsets.right;
2316                 ch = bounds.height - totalTabHeight - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
2317 
2318                 for (int i = 0; i < numChildren; i++) {
2319                     final Component child = tabPane.getComponent(i);
2320                     if (child == tabContainer) {
2321 
2322                         final int tabContainerWidth = totalTabWidth == 0 ? bounds.width : totalTabWidth + insets.left + insets.right + contentInsets.left + contentInsets.right;
2323                         final int tabContainerHeight = totalTabHeight == 0 ? bounds.height : totalTabHeight + insets.top + insets.bottom + contentInsets.top + contentInsets.bottom;
2324 
2325                         int tabContainerX = 0;
2326                         int tabContainerY = 0;
2327                         if (tabPlacement == BOTTOM) {
2328                             tabContainerY = bounds.height - tabContainerHeight;
2329                         } else if (tabPlacement == RIGHT) {
2330                             tabContainerX = bounds.width - tabContainerWidth;
2331                         }
2332                         child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
2333                     } else {
2334                         child.setBounds(cx, cy, cw, ch);
2335                     }
2336                 }
2337             }
2338             layoutTabComponents();
2339             if (shouldChangeFocus) {
2340                 if (!requestFocusForVisibleComponent()) {
2341                     tabPane.requestFocus();
2342                 }
2343             }
2344         }
2345 
2346         public void calculateLayoutInfo() {
2347             final int tabCount = tabPane.getTabCount();
2348             assureRectsCreated(tabCount);
2349             calculateTabRects(tabPane.getTabPlacement(), tabCount);
2350             isRunsDirty = false;
2351         }
2352 
2353         protected void layoutTabComponents() {
2354             if (tabContainer == null) {
2355                 return;
2356             }
2357             final Rectangle rect = new Rectangle();
2358             final Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
2359             if (scrollableTabLayoutEnabled()) {
2360                 translatePointToTabPanel(0, 0, delta);
2361             }
2362             for (int i = 0; i < tabPane.getTabCount(); i++) {
2363                 final Component c = tabPane.getTabComponentAt(i);
2364                 if (c == null) {
2365                     continue;
2366                 }
2367                 getTabBounds(i, rect);
2368                 final Dimension preferredSize = c.getPreferredSize();
2369                 final Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
2370                 final int outerX = rect.x + insets.left + delta.x;
2371                 final int outerY = rect.y + insets.top + delta.y;
2372                 final int outerWidth = rect.width - insets.left - insets.right;
2373                 final int outerHeight = rect.height - insets.top - insets.bottom;
2374                 // centralize component
2375                 final int x = outerX + (outerWidth - preferredSize.width) / 2;
2376                 final int y = outerY + (outerHeight - preferredSize.height) / 2;
2377                 final int tabPlacement = tabPane.getTabPlacement();
2378                 final boolean isSeleceted = i == tabPane.getSelectedIndex();
2379                 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted), y + getTabLabelShiftY(tabPlacement, i, isSeleceted), preferredSize.width, preferredSize.height);
2380             }
2381         }
2382 
2383         protected void calculateTabRects(final int tabPlacement, final int tabCount) {
2384             final FontMetrics metrics = getFontMetrics();
2385             final Dimension size = tabPane.getSize();
2386             final Insets insets = tabPane.getInsets();
2387             final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2388             final int fontHeight = metrics.getHeight();
2389             final int selectedIndex = tabPane.getSelectedIndex();
2390             int tabRunOverlay;
2391             int i, j;
2392             int x, y;
2393             int returnAt;
2394             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2395             boolean leftToRight = AquaUtils.isLeftToRight(tabPane);
2396 
2397             //
2398             // Calculate bounds within which a tab run must fit
2399             //
2400             switch (tabPlacement) {
2401                 case LEFT:
2402                     maxTabWidth = calculateMaxTabWidth(tabPlacement);
2403                     x = insets.left + tabAreaInsets.left;
2404                     y = insets.top + tabAreaInsets.top;
2405                     returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2406                     break;
2407                 case RIGHT:
2408                     maxTabWidth = calculateMaxTabWidth(tabPlacement);
2409                     x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
2410                     y = insets.top + tabAreaInsets.top;
2411                     returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2412                     break;
2413                 case BOTTOM:
2414                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
2415                     x = insets.left + tabAreaInsets.left;
2416                     y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
2417                     returnAt = size.width - (insets.right + tabAreaInsets.right);
2418                     break;
2419                 case TOP:
2420                 default:
2421                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
2422                     x = insets.left + tabAreaInsets.left;
2423                     y = insets.top + tabAreaInsets.top;
2424                     returnAt = size.width - (insets.right + tabAreaInsets.right);
2425                     break;
2426             }
2427 
2428             tabRunOverlay = getTabRunOverlay(tabPlacement);
2429 
2430             runCount = 0;
2431             selectedRun = -1;
2432 
2433             if (tabCount == 0) {
2434                 return;
2435             }
2436 
2437             // Run through tabs and partition them into runs
2438             Rectangle rect;
2439             for (i = 0; i < tabCount; i++) {
2440                 rect = rects[i];
2441 
2442                 if (!verticalTabRuns) {
2443                     // Tabs on TOP or BOTTOM....
2444                     if (i > 0) {
2445                         rect.x = rects[i - 1].x + rects[i - 1].width;
2446                     } else {
2447                         tabRuns[0] = 0;
2448                         runCount = 1;
2449                         maxTabWidth = 0;
2450                         rect.x = x;
2451                     }
2452                     rect.width = calculateTabWidth(tabPlacement, i, metrics);
2453                     maxTabWidth = Math.max(maxTabWidth, rect.width);
2454 
2455                     // Never move a TAB down a run if it is in the first column.
2456                     // Even if there isn't enough room, moving it to a fresh
2457                     // line won't help.
2458                     if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
2459                         if (runCount > tabRuns.length - 1) {
2460                             expandTabRunsArray();
2461                         }
2462                         tabRuns[runCount] = i;
2463                         runCount++;
2464                         rect.x = x;
2465                     }
2466                     // Initialize y position in case there's just one run
2467                     rect.y = y;
2468                     rect.height = maxTabHeight/* - 2*/;
2469 
2470                 } else {
2471                     // Tabs on LEFT or RIGHT...
2472                     if (i > 0) {
2473                         rect.y = rects[i - 1].y + rects[i - 1].height;
2474                     } else {
2475                         tabRuns[0] = 0;
2476                         runCount = 1;
2477                         maxTabHeight = 0;
2478                         rect.y = y;
2479                     }
2480                     rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
2481                     maxTabHeight = Math.max(maxTabHeight, rect.height);
2482 
2483                     // Never move a TAB over a run if it is in the first run.
2484                     // Even if there isn't enough room, moving it to a fresh
2485                     // column won't help.
2486                     if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
2487                         if (runCount > tabRuns.length - 1) {
2488                             expandTabRunsArray();
2489                         }
2490                         tabRuns[runCount] = i;
2491                         runCount++;
2492                         rect.y = y;
2493                     }
2494                     // Initialize x position in case there's just one column
2495                     rect.x = x;
2496                     rect.width = maxTabWidth/* - 2*/;
2497 
2498                 }
2499                 if (i == selectedIndex) {
2500                     selectedRun = runCount - 1;
2501                 }
2502             }
2503 
2504             if (runCount > 1) {
2505                 // Re-distribute tabs in case last run has leftover space
2506                 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns ? y : x, returnAt);
2507 
2508                 selectedRun = getRunForTab(tabCount, selectedIndex);
2509 
2510                 // Rotate run array so that selected run is first
2511                 if (shouldRotateTabRuns(tabPlacement)) {
2512                     rotateTabRuns(tabPlacement, selectedRun);
2513                 }
2514             }
2515 
2516             // Step through runs from back to front to calculate
2517             // tab y locations and to pad runs appropriately
2518             for (i = runCount - 1; i >= 0; i--) {
2519                 final int start = tabRuns[i];
2520                 final int next = tabRuns[i == (runCount - 1) ? 0 : i + 1];
2521                 final int end = (next != 0 ? next - 1 : tabCount - 1);
2522                 if (!verticalTabRuns) {
2523                     for (j = start; j <= end; j++) {
2524                         rect = rects[j];
2525                         rect.y = y;
2526                         rect.x += getTabRunIndent(tabPlacement, i);
2527                     }
2528                     if (shouldPadTabRun(tabPlacement, i)) {
2529                         padTabRun(tabPlacement, start, end, returnAt);
2530                     }
2531                     if (tabPlacement == BOTTOM) {
2532                         y -= (maxTabHeight - tabRunOverlay);
2533                     } else {
2534                         y += (maxTabHeight - tabRunOverlay);
2535                     }
2536                 } else {
2537                     for (j = start; j <= end; j++) {
2538                         rect = rects[j];
2539                         rect.x = x;
2540                         rect.y += getTabRunIndent(tabPlacement, i);
2541                     }
2542                     if (shouldPadTabRun(tabPlacement, i)) {
2543                         padTabRun(tabPlacement, start, end, returnAt);
2544                     }
2545                     if (tabPlacement == RIGHT) {
2546                         x -= (maxTabWidth - tabRunOverlay);
2547                     } else {
2548                         x += (maxTabWidth - tabRunOverlay);
2549                     }
2550                 }
2551             }
2552 
2553             // Pad the selected tab so that it appears raised in front
2554             padSelectedTab(tabPlacement, selectedIndex);
2555 
2556             // if right to left and tab placement on the top or
2557             // the bottom, flip x positions and adjust by widths
2558             if (!leftToRight && !verticalTabRuns) {
2559                 final int rightMargin = size.width - (insets.right + tabAreaInsets.right);
2560                 for (i = 0; i < tabCount; i++) {
2561                     rects[i].x = rightMargin - rects[i].x - rects[i].width;
2562                 }
2563             }
2564         }
2565 
2566         /*
2567          * Rotates the run-index array so that the selected run is run[0]
2568          */
2569         protected void rotateTabRuns(final int tabPlacement, final int selectedRun) {
2570             for (int i = 0; i < selectedRun; i++) {
2571                 final int save = tabRuns[0];
2572                 for (int j = 1; j < runCount; j++) {
2573                     tabRuns[j - 1] = tabRuns[j];
2574                 }
2575                 tabRuns[runCount - 1] = save;
2576             }
2577         }
2578 
2579         protected void normalizeTabRuns(final int tabPlacement, final int tabCount, final int start, final int max) {
2580             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2581             int run = runCount - 1;
2582             boolean keepAdjusting = true;
2583             double weight = 1.25;
2584 
2585             // At this point the tab runs are packed to fit as many
2586             // tabs as possible, which can leave the last run with a lot
2587             // of extra space (resulting in very fat tabs on the last run).
2588             // So we'll attempt to distribute this extra space more evenly
2589             // across the runs in order to make the runs look more consistent.
2590             //
2591             // Starting with the last run, determine whether the last tab in
2592             // the previous run would fit (generously) in this run; if so,
2593             // move tab to current run and shift tabs accordingly.  Cycle
2594             // through remaining runs using the same algorithm.
2595             //
2596             while (keepAdjusting) {
2597                 final int last = lastTabInRun(tabCount, run);
2598                 final int prevLast = lastTabInRun(tabCount, run - 1);
2599                 int end;
2600                 int prevLastLen;
2601 
2602                 if (!verticalTabRuns) {
2603                     end = rects[last].x + rects[last].width;
2604                     prevLastLen = (int)(maxTabWidth * weight);
2605                 } else {
2606                     end = rects[last].y + rects[last].height;
2607                     prevLastLen = (int)(maxTabHeight * weight * 2);
2608                 }
2609 
2610                 // Check if the run has enough extra space to fit the last tab
2611                 // from the previous row...
2612                 if (max - end > prevLastLen) {
2613 
2614                     // Insert tab from previous row and shift rest over
2615                     tabRuns[run] = prevLast;
2616                     if (!verticalTabRuns) {
2617                         rects[prevLast].x = start;
2618                     } else {
2619                         rects[prevLast].y = start;
2620                     }
2621                     for (int i = prevLast + 1; i <= last; i++) {
2622                         if (!verticalTabRuns) {
2623                             rects[i].x = rects[i - 1].x + rects[i - 1].width;
2624                         } else {
2625                             rects[i].y = rects[i - 1].y + rects[i - 1].height;
2626                         }
2627                     }
2628 
2629                 } else if (run == runCount - 1) {
2630                     // no more room left in last run, so we're done!
2631                     keepAdjusting = false;
2632                 }
2633                 if (run - 1 > 0) {
2634                     // check previous run next...
2635                     run -= 1;
2636                 } else {
2637                     // check last run again...but require a higher ratio
2638                     // of extraspace-to-tabsize because we don't want to
2639                     // end up with too many tabs on the last run!
2640                     run = runCount - 1;
2641                     weight += .25;
2642                 }
2643             }
2644         }
2645 
2646         protected void padTabRun(final int tabPlacement, final int start, final int end, final int max) {
2647             final Rectangle lastRect = rects[end];
2648             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2649                 final int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
2650                 final int deltaWidth = max - (lastRect.x + lastRect.width);
2651                 final float factor = (float)deltaWidth / (float)runWidth;
2652 
2653                 for (int j = start; j <= end; j++) {
2654                     final Rectangle pastRect = rects[j];
2655                     if (j > start) {
2656                         pastRect.x = rects[j - 1].x + rects[j - 1].width;
2657                     }
2658                     pastRect.width += Math.round(pastRect.width * factor);
2659                 }
2660                 lastRect.width = max - lastRect.x;
2661             } else {
2662                 final int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
2663                 final int deltaHeight = max - (lastRect.y + lastRect.height);
2664                 final float factor = (float)deltaHeight / (float)runHeight;
2665 
2666                 for (int j = start; j <= end; j++) {
2667                     final Rectangle pastRect = rects[j];
2668                     if (j > start) {
2669                         pastRect.y = rects[j - 1].y + rects[j - 1].height;
2670                     }
2671                     pastRect.height += Math.round(pastRect.height * factor);
2672                 }
2673                 lastRect.height = max - lastRect.y;
2674             }
2675         }
2676 
2677         protected void padSelectedTab(final int tabPlacement, final int selectedIndex) {
2678 
2679             if (selectedIndex >= 0) {
2680                 final Rectangle selRect = rects[selectedIndex];
2681                 final Insets padInsets = getSelectedTabPadInsets(tabPlacement);
2682                 selRect.x -= padInsets.left;
2683                 selRect.width += (padInsets.left + padInsets.right);
2684                 selRect.y -= padInsets.top;
2685                 selRect.height += (padInsets.top + padInsets.bottom);
2686 
2687                 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
2688                     // do not expand selected tab more then necessary
2689                     final Dimension size = tabPane.getSize();
2690                     final Insets insets = tabPane.getInsets();
2691 
2692                     if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) {
2693                         final int top = insets.top - selRect.y;
2694                         if (top > 0) {
2695                             selRect.y += top;
2696                             selRect.height -= top;
2697                         }
2698                         final int bottom = (selRect.y + selRect.height) + insets.bottom - size.height;
2699                         if (bottom > 0) {
2700                             selRect.height -= bottom;
2701                         }
2702                     } else {
2703                         final int left = insets.left - selRect.x;
2704                         if (left > 0) {
2705                             selRect.x += left;
2706                             selRect.width -= left;
2707                         }
2708                         final int right = (selRect.x + selRect.width) + insets.right - size.width;
2709                         if (right > 0) {
2710                             selRect.width -= right;
2711                         }
2712                     }
2713                 }
2714             }
2715         }
2716     }
2717 
2718     class TabbedPaneScrollLayout extends TabbedPaneLayout {
2719 
2720         protected int preferredTabAreaHeight(final int tabPlacement, final int width) {
2721             return calculateMaxTabHeight(tabPlacement);
2722         }
2723 
2724         protected int preferredTabAreaWidth(final int tabPlacement, final int height) {
2725             return calculateMaxTabWidth(tabPlacement);
2726         }
2727 
2728         public void layoutContainer(final Container parent) {
2729             /* Some of the code in this method deals with changing the
2730              * visibility of components to hide and show the contents for the
2731              * selected tab. This is older code that has since been duplicated
2732              * in JTabbedPane.fireStateChanged(), so as to allow visibility
2733              * changes to happen sooner (see the note there). This code remains
2734              * for backward compatibility as there are some cases, such as
2735              * subclasses that don't fireStateChanged() where it may be used.
2736              * Any changes here need to be kept in synch with
2737              * JTabbedPane.fireStateChanged().
2738              */
2739 
2740             setRolloverTab(-1);
2741 
2742             final int tabPlacement = tabPane.getTabPlacement();
2743             final int tabCount = tabPane.getTabCount();
2744             final Insets insets = tabPane.getInsets();
2745             final int selectedIndex = tabPane.getSelectedIndex();
2746             final Component visibleComponent = getVisibleComponent();
2747 
2748             calculateLayoutInfo();
2749 
2750             Component selectedComponent = null;
2751             if (selectedIndex < 0) {
2752                 if (visibleComponent != null) {
2753                     // The last tab was removed, so remove the component
2754                     setVisibleComponent(null);
2755                 }
2756             } else {
2757                 selectedComponent = tabPane.getComponentAt(selectedIndex);
2758             }
2759 
2760             if (tabPane.getTabCount() == 0) {
2761                 tabScroller.croppedEdge.resetParams();
2762                 tabScroller.scrollForwardButton.setVisible(false);
2763                 tabScroller.scrollBackwardButton.setVisible(false);
2764                 return;
2765             }
2766 
2767             boolean shouldChangeFocus = false;
2768 
2769             // In order to allow programs to use a single component
2770             // as the display for multiple tabs, we will not change
2771             // the visible compnent if the currently selected tab
2772             // has a null component.  This is a bit dicey, as we don't
2773             // explicitly state we support this in the spec, but since
2774             // programs are now depending on this, we're making it work.
2775             //
2776             if (selectedComponent != null) {
2777                 if (selectedComponent != visibleComponent && visibleComponent != null) {
2778                     if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
2779                         shouldChangeFocus = true;
2780                     }
2781                 }
2782                 setVisibleComponent(selectedComponent);
2783             }
2784             int tx, ty, tw, th; // tab area bounds
2785             int cx, cy, cw, ch; // content area bounds
2786             final Insets contentInsets = getContentBorderInsets(tabPlacement);
2787             final Rectangle bounds = tabPane.getBounds();
2788             final int numChildren = tabPane.getComponentCount();
2789 
2790             if (numChildren > 0) {
2791                 switch (tabPlacement) {
2792                     case LEFT:
2793                         // calculate tab area bounds
2794                         tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2795                         th = bounds.height - insets.top - insets.bottom;
2796                         tx = insets.left;
2797                         ty = insets.top;
2798 
2799                         // calculate content area bounds
2800                         cx = tx + tw + contentInsets.left;
2801                         cy = ty + contentInsets.top;
2802                         cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right;
2803                         ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
2804                         break;
2805                     case RIGHT:
2806                         // calculate tab area bounds
2807                         tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2808                         th = bounds.height - insets.top - insets.bottom;
2809                         tx = bounds.width - insets.right - tw;
2810                         ty = insets.top;
2811 
2812                         // calculate content area bounds
2813                         cx = insets.left + contentInsets.left;
2814                         cy = insets.top + contentInsets.top;
2815                         cw = bounds.width - insets.left - insets.right - tw - contentInsets.left - contentInsets.right;
2816                         ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
2817                         break;
2818                     case BOTTOM:
2819                         // calculate tab area bounds
2820                         tw = bounds.width - insets.left - insets.right;
2821                         th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2822                         tx = insets.left;
2823                         ty = bounds.height - insets.bottom - th;
2824 
2825                         // calculate content area bounds
2826                         cx = insets.left + contentInsets.left;
2827                         cy = insets.top + contentInsets.top;
2828                         cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
2829                         ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom;
2830                         break;
2831                     case TOP:
2832                     default:
2833                         // calculate tab area bounds
2834                         tw = bounds.width - insets.left - insets.right;
2835                         th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2836                         tx = insets.left;
2837                         ty = insets.top;
2838 
2839                         // calculate content area bounds
2840                         cx = tx + contentInsets.left;
2841                         cy = ty + th + contentInsets.top;
2842                         cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
2843                         ch = bounds.height - insets.top - insets.bottom - th - contentInsets.top - contentInsets.bottom;
2844                 }
2845 
2846                 for (int i = 0; i < numChildren; i++) {
2847                     final Component child = tabPane.getComponent(i);
2848 
2849                     if (tabScroller != null && child == tabScroller.viewport) {
2850                         final JViewport viewport = (JViewport)child;
2851                         final Rectangle viewRect = viewport.getViewRect();
2852                         int vw = tw;
2853                         int vh = th;
2854                         final Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
2855                         switch (tabPlacement) {
2856                             case LEFT:
2857                             case RIGHT:
2858                                 final int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
2859                                 if (totalTabHeight > th) {
2860                                     // Allow space for scrollbuttons
2861                                     vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0;
2862                                     if (totalTabHeight - viewRect.y <= vh) {
2863                                         // Scrolled to the end, so ensure the viewport size is
2864                                         // such that the scroll offset aligns with a tab
2865                                         vh = totalTabHeight - viewRect.y;
2866                                     }
2867                                 }
2868                                 break;
2869                             case BOTTOM:
2870                             case TOP:
2871                             default:
2872                                 final int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
2873                                 if (totalTabWidth > tw) {
2874                                     // Need to allow space for scrollbuttons
2875                                     vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0;
2876                                     if (totalTabWidth - viewRect.x <= vw) {
2877                                         // Scrolled to the end, so ensure the viewport size is
2878                                         // such that the scroll offset aligns with a tab
2879                                         vw = totalTabWidth - viewRect.x;
2880                                     }
2881                                 }
2882                         }
2883                         child.setBounds(tx, ty, vw, vh);
2884 
2885                     } else if (tabScroller != null && (child == tabScroller.scrollForwardButton || child == tabScroller.scrollBackwardButton)) {
2886                         final Component scrollbutton = child;
2887                         final Dimension bsize = scrollbutton.getPreferredSize();
2888                         int bx = 0;
2889                         int by = 0;
2890                         final int bw = bsize.width;
2891                         final int bh = bsize.height;
2892                         boolean visible = false;
2893 
2894                         switch (tabPlacement) {
2895                             case LEFT:
2896                             case RIGHT:
2897                                 final int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
2898                                 if (totalTabHeight > th) {
2899                                     visible = true;
2900                                     bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx);
2901                                     by = (child == tabScroller.scrollForwardButton) ? bounds.height - insets.bottom - bsize.height : bounds.height - insets.bottom - 2 * bsize.height;
2902                                 }
2903                                 break;
2904 
2905                             case BOTTOM:
2906                             case TOP:
2907                             default:
2908                                 final int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
2909 
2910                                 if (totalTabWidth > tw) {
2911                                     visible = true;
2912                                     bx = (child == tabScroller.scrollForwardButton) ? bounds.width - insets.left - bsize.width : bounds.width - insets.left - 2 * bsize.width;
2913                                     by = (tabPlacement == TOP ? ty + th - bsize.height : ty);
2914                                 }
2915                         }
2916                         child.setVisible(visible);
2917                         if (visible) {
2918                             child.setBounds(bx, by, bw, bh);
2919                         }
2920 
2921                     } else {
2922                         // All content children...
2923                         child.setBounds(cx, cy, cw, ch);
2924                     }
2925                 }
2926                 super.layoutTabComponents();
2927                 layoutCroppedEdge();
2928                 if (shouldChangeFocus) {
2929                     if (!requestFocusForVisibleComponent()) {
2930                         tabPane.requestFocus();
2931                     }
2932                 }
2933             }
2934         }
2935 
2936         private void layoutCroppedEdge() {
2937             tabScroller.croppedEdge.resetParams();
2938             final Rectangle viewRect = tabScroller.viewport.getViewRect();
2939             int cropline;
2940             for (int i = 0; i < rects.length; i++) {
2941                 final Rectangle tabRect = rects[i];
2942                 switch (tabPane.getTabPlacement()) {
2943                     case LEFT:
2944                     case RIGHT:
2945                         cropline = viewRect.y + viewRect.height;
2946                         if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
2947                             tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1, -currentTabAreaInsets.left, 0);
2948                         }
2949                         break;
2950                     case TOP:
2951                     case BOTTOM:
2952                     default:
2953                         cropline = viewRect.x + viewRect.width;
2954                         if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) {
2955                             tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1, 0, -currentTabAreaInsets.top);
2956                         }
2957                 }
2958             }
2959         }
2960 
2961         protected void calculateTabRects(final int tabPlacement, final int tabCount) {
2962             final FontMetrics metrics = getFontMetrics();
2963             final Dimension size = tabPane.getSize();
2964             final Insets insets = tabPane.getInsets();
2965             final Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2966             final int fontHeight = metrics.getHeight();
2967             final int selectedIndex = tabPane.getSelectedIndex();
2968             int i;
2969             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2970             boolean leftToRight = AquaUtils.isLeftToRight(tabPane);
2971             final int x = tabAreaInsets.left;
2972             final int y = tabAreaInsets.top;
2973             int totalWidth = 0;
2974             int totalHeight = 0;
2975 
2976             //
2977             // Calculate bounds within which a tab run must fit
2978             //
2979             switch (tabPlacement) {
2980                 case LEFT:
2981                 case RIGHT:
2982                     maxTabWidth = calculateMaxTabWidth(tabPlacement);
2983                     break;
2984                 case BOTTOM:
2985                 case TOP:
2986                 default:
2987                     maxTabHeight = calculateMaxTabHeight(tabPlacement);
2988             }
2989 
2990             runCount = 0;
2991             selectedRun = -1;
2992 
2993             if (tabCount == 0) {
2994                 return;
2995             }
2996 
2997             selectedRun = 0;
2998             runCount = 1;
2999 
3000             // Run through tabs and lay them out in a single run
3001             Rectangle rect;
3002             for (i = 0; i < tabCount; i++) {
3003                 rect = rects[i];
3004 
3005                 if (!verticalTabRuns) {
3006                     // Tabs on TOP or BOTTOM....
3007                     if (i > 0) {
3008                         rect.x = rects[i - 1].x + rects[i - 1].width;
3009                     } else {
3010                         tabRuns[0] = 0;
3011                         maxTabWidth = 0;
3012                         totalHeight += maxTabHeight;
3013                         rect.x = x;
3014                     }
3015                     rect.width = calculateTabWidth(tabPlacement, i, metrics);
3016                     totalWidth = rect.x + rect.width;
3017                     maxTabWidth = Math.max(maxTabWidth, rect.width);
3018 
3019                     rect.y = y;
3020                     rect.height = maxTabHeight/* - 2*/;
3021 
3022                 } else {
3023                     // Tabs on LEFT or RIGHT...
3024                     if (i > 0) {
3025                         rect.y = rects[i - 1].y + rects[i - 1].height;
3026                     } else {
3027                         tabRuns[0] = 0;
3028                         maxTabHeight = 0;
3029                         totalWidth = maxTabWidth;
3030                         rect.y = y;
3031                     }
3032                     rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3033                     totalHeight = rect.y + rect.height;
3034                     maxTabHeight = Math.max(maxTabHeight, rect.height);
3035 
3036                     rect.x = x;
3037                     rect.width = maxTabWidth/* - 2*/;
3038 
3039                 }
3040             }
3041 
3042             if (tabsOverlapBorder) {
3043                 // Pad the selected tab so that it appears raised in front
3044                 padSelectedTab(tabPlacement, selectedIndex);
3045             }
3046 
3047             // if right to left and tab placement on the top or
3048             // the bottom, flip x positions and adjust by widths
3049             if (!leftToRight && !verticalTabRuns) {
3050                 final int rightMargin = size.width - (insets.right + tabAreaInsets.right);
3051                 for (i = 0; i < tabCount; i++) {
3052                     rects[i].x = rightMargin - rects[i].x - rects[i].width;
3053                 }
3054             }
3055             tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
3056         }
3057     }
3058 
3059     private class ScrollableTabSupport implements ActionListener, ChangeListener {
3060         public ScrollableTabViewport viewport;
3061         public ScrollableTabPanel tabPanel;
3062         public JButton scrollForwardButton;
3063         public JButton scrollBackwardButton;
3064         public CroppedEdge croppedEdge;
3065         public int leadingTabIndex;
3066 
3067         private final Point tabViewPosition = new Point(0, 0);
3068 
3069         ScrollableTabSupport(final int tabPlacement) {
3070             viewport = new ScrollableTabViewport();
3071             tabPanel = new ScrollableTabPanel();
3072             viewport.setView(tabPanel);
3073             viewport.addChangeListener(this);
3074             croppedEdge = new CroppedEdge();
3075             createButtons();
3076         }
3077 
3078         /**
3079          * Recreates the scroll buttons and adds them to the TabbedPane.
3080          */
3081         void createButtons() {
3082             if (scrollForwardButton != null) {
3083                 tabPane.remove(scrollForwardButton);
3084                 scrollForwardButton.removeActionListener(this);
3085                 tabPane.remove(scrollBackwardButton);
3086                 scrollBackwardButton.removeActionListener(this);
3087             }
3088             final int tabPlacement = tabPane.getTabPlacement();
3089             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3090                 scrollForwardButton = createScrollButton(EAST);
3091                 scrollBackwardButton = createScrollButton(WEST);
3092 
3093             } else { // tabPlacement = LEFT || RIGHT
3094                 scrollForwardButton = createScrollButton(SOUTH);
3095                 scrollBackwardButton = createScrollButton(NORTH);
3096             }
3097             scrollForwardButton.addActionListener(this);
3098             scrollBackwardButton.addActionListener(this);
3099             tabPane.add(scrollForwardButton);
3100             tabPane.add(scrollBackwardButton);
3101         }
3102 
3103         public void scrollForward(final int tabPlacement) {
3104             final Dimension viewSize = viewport.getViewSize();
3105             final Rectangle viewRect = viewport.getViewRect();
3106 
3107             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3108                 if (viewRect.width >= viewSize.width - viewRect.x) {
3109                     return; // no room left to scroll
3110                 }
3111             } else { // tabPlacement == LEFT || tabPlacement == RIGHT
3112                 if (viewRect.height >= viewSize.height - viewRect.y) {
3113                     return;
3114                 }
3115             }
3116             setLeadingTabIndex(tabPlacement, leadingTabIndex + 1);
3117         }
3118 
3119         public void scrollBackward(final int tabPlacement) {
3120             if (leadingTabIndex == 0) {
3121                 return; // no room left to scroll
3122             }
3123             setLeadingTabIndex(tabPlacement, leadingTabIndex - 1);
3124         }
3125 
3126         public void setLeadingTabIndex(final int tabPlacement, final int index) {
3127             leadingTabIndex = index;
3128             final Dimension viewSize = viewport.getViewSize();
3129             final Rectangle viewRect = viewport.getViewRect();
3130 
3131             switch (tabPlacement) {
3132                 case TOP:
3133                 case BOTTOM:
3134                     tabViewPosition.x = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].x;
3135 
3136                     if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
3137                         // We've scrolled to the end, so adjust the viewport size
3138                         // to ensure the view position remains aligned on a tab boundary
3139                         final Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x, viewRect.height);
3140                         viewport.setExtentSize(extentSize);
3141                     }
3142                     break;
3143                 case LEFT:
3144                 case RIGHT:
3145                     tabViewPosition.y = leadingTabIndex == 0 ? 0 : rects[leadingTabIndex].y;
3146 
3147                     if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
3148                         // We've scrolled to the end, so adjust the viewport size
3149                         // to ensure the view position remains aligned on a tab boundary
3150                         final Dimension extentSize = new Dimension(viewRect.width, viewSize.height - tabViewPosition.y);
3151                         viewport.setExtentSize(extentSize);
3152                     }
3153             }
3154             viewport.setViewPosition(tabViewPosition);
3155         }
3156 
3157         public void stateChanged(final ChangeEvent e) {
3158             updateView();
3159         }
3160 
3161         private void updateView() {
3162             final int tabPlacement = tabPane.getTabPlacement();
3163             final int tabCount = tabPane.getTabCount();
3164             final Rectangle vpRect = viewport.getBounds();
3165             final Dimension viewSize = viewport.getViewSize();
3166             final Rectangle viewRect = viewport.getViewRect();
3167 
3168             leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
3169 
3170             // If the tab isn't right aligned, adjust it.
3171             if (leadingTabIndex + 1 < tabCount) {
3172                 switch (tabPlacement) {
3173                     case TOP:
3174                     case BOTTOM:
3175                         if (rects[leadingTabIndex].x < viewRect.x) {
3176                             leadingTabIndex++;
3177                         }
3178                         break;
3179                     case LEFT:
3180                     case RIGHT:
3181                         if (rects[leadingTabIndex].y < viewRect.y) {
3182                             leadingTabIndex++;
3183                         }
3184                         break;
3185                 }
3186             }
3187             final Insets contentInsets = getContentBorderInsets(tabPlacement);
3188             switch (tabPlacement) {
3189                 case LEFT:
3190                     tabPane.repaint(vpRect.x + vpRect.width, vpRect.y, contentInsets.left, vpRect.height);
3191                     scrollBackwardButton.setEnabled(viewRect.y > 0 && leadingTabIndex > 0);
3192                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.height - viewRect.y > viewRect.height);
3193                     break;
3194                 case RIGHT:
3195                     tabPane.repaint(vpRect.x - contentInsets.right, vpRect.y, contentInsets.right, vpRect.height);
3196                     scrollBackwardButton.setEnabled(viewRect.y > 0 && leadingTabIndex > 0);
3197                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.height - viewRect.y > viewRect.height);
3198                     break;
3199                 case BOTTOM:
3200                     tabPane.repaint(vpRect.x, vpRect.y - contentInsets.bottom, vpRect.width, contentInsets.bottom);
3201                     scrollBackwardButton.setEnabled(viewRect.x > 0 && leadingTabIndex > 0);
3202                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.width - viewRect.x > viewRect.width);
3203                     break;
3204                 case TOP:
3205                 default:
3206                     tabPane.repaint(vpRect.x, vpRect.y + vpRect.height, vpRect.width, contentInsets.top);
3207                     scrollBackwardButton.setEnabled(viewRect.x > 0 && leadingTabIndex > 0);
3208                     scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1 && viewSize.width - viewRect.x > viewRect.width);
3209             }
3210         }
3211 
3212         /**
3213          * ActionListener for the scroll buttons.
3214          */
3215         public void actionPerformed(final ActionEvent e) {
3216             final ActionMap map = tabPane.getActionMap();
3217 
3218             if (map != null) {
3219                 String actionKey;
3220 
3221                 if (e.getSource() == scrollForwardButton) {
3222                     actionKey = "scrollTabsForwardAction";
3223                 } else {
3224                     actionKey = "scrollTabsBackwardAction";
3225                 }
3226                 final Action action = map.get(actionKey);
3227 
3228                 if (action != null && action.isEnabled()) {
3229                     action.actionPerformed(new ActionEvent(tabPane, ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
3230                 }
3231             }
3232         }
3233 
3234         public String toString() {
3235             return new String("viewport.viewSize=" + viewport.getViewSize() + "\n" + "viewport.viewRectangle=" + viewport.getViewRect() + "\n" + "leadingTabIndex=" + leadingTabIndex + "\n" + "tabViewPosition=" + tabViewPosition);
3236         }
3237 
3238     }
3239 
3240     private class ScrollableTabViewport extends JViewport implements UIResource {
3241         public ScrollableTabViewport() {
3242             super();
3243             setName("TabbedPane.scrollableViewport");
3244             setScrollMode(SIMPLE_SCROLL_MODE);
3245             setOpaque(tabPane.isOpaque());
3246             Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3247             if (bgColor == null) {
3248                 bgColor = tabPane.getBackground();
3249             }
3250             setBackground(bgColor);
3251         }
3252     }
3253 
3254     private class ScrollableTabPanel extends JPanel implements UIResource {
3255         public ScrollableTabPanel() {
3256             super(null);
3257             setOpaque(tabPane.isOpaque());
3258             Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3259             if (bgColor == null) {
3260                 bgColor = tabPane.getBackground();
3261             }
3262             setBackground(bgColor);
3263         }
3264 
3265         public void paintComponent(final Graphics g) {
3266             super.paintComponent(g);
3267             AquaTabbedPaneCopyFromBasicUI.this.paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
3268             if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
3269                 final Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
3270                 g.translate(croppedRect.x, croppedRect.y);
3271                 tabScroller.croppedEdge.paintComponent(g);
3272                 g.translate(-croppedRect.x, -croppedRect.y);
3273             }
3274         }
3275 
3276         public void doLayout() {
3277             if (getComponentCount() > 0) {
3278                 final Component child = getComponent(0);
3279                 child.setBounds(0, 0, getWidth(), getHeight());
3280             }
3281         }
3282     }
3283 
3284     private class ScrollableTabButton extends javax.swing.plaf.basic.BasicArrowButton implements UIResource, SwingConstants {
3285         public ScrollableTabButton(final int direction) {
3286             super(direction, UIManager.getColor("TabbedPane.selected"), UIManager.getColor("TabbedPane.shadow"), UIManager.getColor("TabbedPane.darkShadow"), UIManager.getColor("TabbedPane.highlight"));
3287         }
3288     }
3289 
3290 // Controller: event listeners
3291 
3292     private class Handler implements ChangeListener, ContainerListener, FocusListener, MouseListener, MouseMotionListener, PropertyChangeListener {
3293         //
3294         // PropertyChangeListener
3295         //
3296         public void propertyChange(final PropertyChangeEvent e) {
3297             final JTabbedPane pane = (JTabbedPane)e.getSource();
3298             final String name = e.getPropertyName();
3299             final boolean isScrollLayout = scrollableTabLayoutEnabled();
3300             if (name == "mnemonicAt") {
3301                 updateMnemonics();
3302                 pane.repaint();
3303             } else if (name == "displayedMnemonicIndexAt") {
3304                 pane.repaint();
3305             } else if (name == "indexForTitle") {
3306                 calculatedBaseline = false;
3307                 updateHtmlViews((Integer) e.getNewValue());
3308             } else if (name == "tabLayoutPolicy") {
3309                 AquaTabbedPaneCopyFromBasicUI.this.uninstallUI(pane);
3310                 AquaTabbedPaneCopyFromBasicUI.this.installUI(pane);
3311                 calculatedBaseline = false;
3312             } else if (name == "tabPlacement") {
3313                 if (scrollableTabLayoutEnabled()) {
3314                     tabScroller.createButtons();
3315                 }
3316                 calculatedBaseline = false;
3317             } else if (name == "opaque" && isScrollLayout) {
3318                 final boolean newVal = ((Boolean)e.getNewValue()).booleanValue();
3319                 tabScroller.tabPanel.setOpaque(newVal);
3320                 tabScroller.viewport.setOpaque(newVal);
3321             } else if (name == "background" && isScrollLayout) {
3322                 final Color newVal = (Color)e.getNewValue();
3323                 tabScroller.tabPanel.setBackground(newVal);
3324                 tabScroller.viewport.setBackground(newVal);
3325                 final Color newColor = selectedColor == null ? newVal : selectedColor;
3326                 tabScroller.scrollForwardButton.setBackground(newColor);
3327                 tabScroller.scrollBackwardButton.setBackground(newColor);
3328             } else if (name == "indexForTabComponent") {
3329                 if (tabContainer != null) {
3330                     tabContainer.removeUnusedTabComponents();
3331                 }
3332                 final Component c = tabPane.getTabComponentAt((Integer)e.getNewValue());
3333                 if (c != null) {
3334                     if (tabContainer == null) {
3335                         installTabContainer();
3336                     } else {
3337                         tabContainer.add(c);
3338                     }
3339                 }
3340                 tabPane.revalidate();
3341                 tabPane.repaint();
3342                 calculatedBaseline = false;
3343             } else if (name == "indexForNullComponent") {
3344                 isRunsDirty = true;
3345                 updateHtmlViews((Integer) e.getNewValue());
3346             } else if (name == "font") {
3347                 calculatedBaseline = false;
3348             }
3349         }
3350 
3351         //
3352         // ChangeListener
3353         //
3354         public void stateChanged(final ChangeEvent e) {
3355             final JTabbedPane tabPane = (JTabbedPane)e.getSource();
3356             tabPane.revalidate();
3357             tabPane.repaint();
3358 
3359             setFocusIndex(tabPane.getSelectedIndex(), false);
3360 
3361             if (scrollableTabLayoutEnabled()) {
3362                 final int index = tabPane.getSelectedIndex();
3363                 if (index < rects.length && index != -1) {
3364                     tabScroller.tabPanel.scrollRectToVisible((Rectangle)rects[index].clone());
3365                 }
3366             }
3367         }
3368 
3369         //
3370         // MouseListener
3371         //
3372         public void mouseClicked(final MouseEvent e) {}
3373 
3374         public void mouseReleased(final MouseEvent e) {}
3375 
3376         public void mouseEntered(final MouseEvent e) {
3377             setRolloverTab(e.getX(), e.getY());
3378         }
3379 
3380         public void mouseExited(final MouseEvent e) {
3381             setRolloverTab(-1);
3382         }
3383 
3384         public void mousePressed(final MouseEvent e) {
3385             if (!tabPane.isEnabled()) {
3386                 return;
3387             }
3388             final int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
3389             if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
3390                 if (tabIndex != tabPane.getSelectedIndex()) {
3391                     // Clicking on unselected tab, change selection, do NOT
3392                     // request focus.
3393                     // This will trigger the focusIndex to change by way
3394                     // of stateChanged.
3395                     tabPane.setSelectedIndex(tabIndex);
3396                 } else if (tabPane.isRequestFocusEnabled()) {
3397                     // Clicking on selected tab, try and give the tabbedpane
3398                     // focus.  Repaint will occur in focusGained.
3399                     tabPane.requestFocus();
3400                 }
3401             }
3402         }
3403 
3404         //
3405         // MouseMotionListener
3406         //
3407         public void mouseDragged(final MouseEvent e) {}
3408 
3409         public void mouseMoved(final MouseEvent e) {
3410             setRolloverTab(e.getX(), e.getY());
3411         }
3412 
3413         //
3414         // FocusListener
3415         //
3416         public void focusGained(final FocusEvent e) {
3417             setFocusIndex(tabPane.getSelectedIndex(), true);
3418         }
3419 
3420         public void focusLost(final FocusEvent e) {
3421             repaintTab(focusIndex);
3422         }
3423 
3424         //
3425         // ContainerListener
3426         //
3427         /* GES 2/3/99:
3428            The container listener code was added to support HTML
3429            rendering of tab titles.
3430 
3431            Ideally, we would be able to listen for property changes
3432            when a tab is added or its text modified.  At the moment
3433            there are no such events because the Beans spec doesn't
3434            allow 'indexed' property changes (i.e. tab 2's text changed
3435            from A to B).
3436 
3437            In order to get around this, we listen for tabs to be added
3438            or removed by listening for the container events.  we then
3439            queue up a runnable (so the component has a chance to complete
3440            the add) which checks the tab title of the new component to see
3441            if it requires HTML rendering.
3442 
3443            The Views (one per tab title requiring HTML rendering) are
3444            stored in the htmlViews Vector, which is only allocated after
3445            the first time we run into an HTML tab.  Note that this vector
3446            is kept in step with the number of pages, and nulls are added
3447            for those pages whose tab title do not require HTML rendering.
3448 
3449            This makes it easy for the paint and layout code to tell
3450            whether to invoke the HTML engine without having to check
3451            the string during time-sensitive operations.
3452 
3453            When we have added a way to listen for tab additions and
3454            changes to tab text, this code should be removed and
3455            replaced by something which uses that.  */
3456 
3457         public void componentAdded(final ContainerEvent e) {
3458             final JTabbedPane tp = (JTabbedPane)e.getContainer();
3459             final Component child = e.getChild();
3460             if (child instanceof UIResource) {
3461                 return;
3462             }
3463             isRunsDirty = true;
3464             updateHtmlViews(tp.indexOfComponent(child));
3465         }
3466 
3467         private void updateHtmlViews(int index) {
3468             final String title = tabPane.getTitleAt(index);
3469             final boolean isHTML = BasicHTML.isHTMLString(title);
3470             if (isHTML) {
3471                 if (htmlViews == null) { // Initialize vector
3472                     htmlViews = createHTMLVector();
3473                 } else { // Vector already exists
3474                     final View v = BasicHTML.createHTMLView(tabPane, title);
3475                     htmlViews.insertElementAt(v, index);
3476                 }
3477             } else { // Not HTML
3478                 if (htmlViews != null) { // Add placeholder
3479                     htmlViews.insertElementAt(null, index);
3480                 } // else nada!
3481             }
3482             updateMnemonics();
3483         }
3484 
3485         public void componentRemoved(final ContainerEvent e) {
3486             final JTabbedPane tp = (JTabbedPane)e.getContainer();
3487             final Component child = e.getChild();
3488             if (child instanceof UIResource) {
3489                 return;
3490             }
3491 
3492             // NOTE 4/15/2002 (joutwate):
3493             // This fix is implemented using client properties since there is
3494             // currently no IndexPropertyChangeEvent.  Once
3495             // IndexPropertyChangeEvents have been added this code should be
3496             // modified to use it.
3497             final Integer indexObj = (Integer)tp.getClientProperty("__index_to_remove__");
3498             if (indexObj != null) {
3499                 final int index = indexObj.intValue();
3500                 if (htmlViews != null && htmlViews.size() > index) {
3501                     htmlViews.removeElementAt(index);
3502                 }
3503                 tp.putClientProperty("__index_to_remove__", null);
3504             }
3505             isRunsDirty = true;
3506             updateMnemonics();
3507 
3508             validateFocusIndex();
3509         }
3510     }
3511 
3512     /**
3513      * This class should be treated as a &quot;protected&quot; inner class.
3514      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3515      */
3516     public class PropertyChangeHandler implements PropertyChangeListener {
3517         // NOTE: This class exists only for backward compatibility. All
3518         // its functionality has been moved into Handler. If you need to add
3519         // new functionality add it to the Handler, but make sure this
3520         // class calls into the Handler.
3521         public void propertyChange(final PropertyChangeEvent e) {
3522             getHandler().propertyChange(e);
3523         }
3524     }
3525 
3526     /**
3527      * This class should be treated as a &quot;protected&quot; inner class.
3528      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3529      */
3530     public class TabSelectionHandler implements ChangeListener {
3531         // NOTE: This class exists only for backward compatibility. All
3532         // its functionality has been moved into Handler. If you need to add
3533         // new functionality add it to the Handler, but make sure this
3534         // class calls into the Handler.
3535         public void stateChanged(final ChangeEvent e) {
3536             getHandler().stateChanged(e);
3537         }
3538     }
3539 
3540     /**
3541      * This class should be treated as a &quot;protected&quot; inner class.
3542      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3543      */
3544     public class MouseHandler extends MouseAdapter {
3545         // NOTE: This class exists only for backward compatibility. All
3546         // its functionality has been moved into Handler. If you need to add
3547         // new functionality add it to the Handler, but make sure this
3548         // class calls into the Handler.
3549         public void mousePressed(final MouseEvent e) {
3550             getHandler().mousePressed(e);
3551         }
3552     }
3553 
3554     /**
3555      * This class should be treated as a &quot;protected&quot; inner class.
3556      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3557      */
3558     public class FocusHandler extends FocusAdapter {
3559         // NOTE: This class exists only for backward compatibility. All
3560         // its functionality has been moved into Handler. If you need to add
3561         // new functionality add it to the Handler, but make sure this
3562         // class calls into the Handler.
3563         public void focusGained(final FocusEvent e) {
3564             getHandler().focusGained(e);
3565         }
3566 
3567         public void focusLost(final FocusEvent e) {
3568             getHandler().focusLost(e);
3569         }
3570     }
3571 
3572     private Vector<View> createHTMLVector() {
3573         final Vector<View> htmlViews = new Vector<View>();
3574         final int count = tabPane.getTabCount();
3575         if (count > 0) {
3576             for (int i = 0; i < count; i++) {
3577                 final String title = tabPane.getTitleAt(i);
3578                 if (BasicHTML.isHTMLString(title)) {
3579                     htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
3580                 } else {
3581                     htmlViews.addElement(null);
3582                 }
3583             }
3584         }
3585         return htmlViews;
3586     }
3587 
3588     private class TabContainer extends JPanel implements UIResource {
3589         private boolean notifyTabbedPane = true;
3590 
3591         public TabContainer() {
3592             super(null);
3593             setOpaque(false);
3594         }
3595 
3596         public void remove(final Component comp) {
3597             final int index = tabPane.indexOfTabComponent(comp);
3598             super.remove(comp);
3599             if (notifyTabbedPane && index != -1) {
3600                 tabPane.setTabComponentAt(index, null);
3601             }
3602         }
3603 
3604         private void removeUnusedTabComponents() {
3605             for (final Component c : getComponents()) {
3606                 if (!(c instanceof UIResource)) {
3607                     final int index = tabPane.indexOfTabComponent(c);
3608                     if (index == -1) {
3609                         super.remove(c);
3610                     }
3611                 }
3612             }
3613         }
3614 
3615         public boolean isOptimizedDrawingEnabled() {
3616             return tabScroller != null && !tabScroller.croppedEdge.isParamsSet();
3617         }
3618 
3619         public void doLayout() {
3620             // We layout tabComponents in JTabbedPane's layout manager
3621             // and use this method as a hook for repainting tabs
3622             // to update tabs area e.g. when the size of tabComponent was changed
3623             if (scrollableTabLayoutEnabled()) {
3624                 tabScroller.tabPanel.repaint();
3625                 tabScroller.updateView();
3626             } else {
3627                 tabPane.repaint(getBounds());
3628             }
3629         }
3630     }
3631 
3632     private class CroppedEdge extends JPanel implements UIResource {
3633         private Shape shape;
3634         private int tabIndex;
3635         private int cropline;
3636         private int cropx, cropy;
3637 
3638         public CroppedEdge() {
3639             setOpaque(false);
3640         }
3641 
3642         public void setParams(final int tabIndex, final int cropline, final int cropx, final int cropy) {
3643             this.tabIndex = tabIndex;
3644             this.cropline = cropline;
3645             this.cropx = cropx;
3646             this.cropy = cropy;
3647             final Rectangle tabRect = rects[tabIndex];
3648             setBounds(tabRect);
3649             shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline);
3650             if (getParent() == null && tabContainer != null) {
3651                 tabContainer.add(this, 0);
3652             }
3653         }
3654 
3655         public void resetParams() {
3656             shape = null;
3657             if (getParent() == tabContainer && tabContainer != null) {
3658                 tabContainer.remove(this);
3659             }
3660         }
3661 
3662         public boolean isParamsSet() {
3663             return shape != null;
3664         }
3665 
3666         public int getTabIndex() {
3667             return tabIndex;
3668         }
3669 
3670         public int getCropline() {
3671             return cropline;
3672         }
3673 
3674         public int getCroppedSideWidth() {
3675             return 3;
3676         }
3677 
3678         private Color getBgColor() {
3679             final Component parent = tabPane.getParent();
3680             if (parent != null) {
3681                 final Color bg = parent.getBackground();
3682                 if (bg != null) {
3683                     return bg;
3684                 }
3685             }
3686             return UIManager.getColor("control");
3687         }
3688 
3689         protected void paintComponent(final Graphics g) {
3690             super.paintComponent(g);
3691             if (isParamsSet() && g instanceof Graphics2D) {
3692                 final Graphics2D g2 = (Graphics2D)g;
3693                 g2.clipRect(0, 0, getWidth(), getHeight());
3694                 g2.setColor(getBgColor());
3695                 g2.translate(cropx, cropy);
3696                 g2.fill(shape);
3697                 paintCroppedTabEdge(g);
3698                 g2.translate(-cropx, -cropy);
3699             }
3700         }
3701     }
3702 
3703     /**
3704      * An ActionMap that populates its contents as necessary. The
3705      * contents are populated by invoking the <code>loadActionMap</code>
3706      * method on the passed in Object.
3707      *
3708      * @version 1.6, 11/17/05
3709      * @author Scott Violet
3710      */
3711     static class LazyActionMap extends ActionMapUIResource {
3712         /**
3713          * Object to invoke <code>loadActionMap</code> on. This may be
3714          * a Class object.
3715          */
3716         private transient Object _loader;
3717 
3718         /**
3719          * Installs an ActionMap that will be populated by invoking the
3720          * <code>loadActionMap</code> method on the specified Class
3721          * when necessary.
3722          * <p>
3723          * This should be used if the ActionMap can be shared.
3724          *
3725          * @param c JComponent to install the ActionMap on.
3726          * @param loaderClass Class object that gets loadActionMap invoked
3727          *                    on.
3728          * @param defaultsKey Key to use to defaults table to check for
3729          *        existing map and what resulting Map will be registered on.
3730          */
3731         static void installLazyActionMap(final JComponent c, final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) {
3732             ActionMap map = (ActionMap)UIManager.get(defaultsKey);
3733             if (map == null) {
3734                 map = new LazyActionMap(loaderClass);
3735                 UIManager.getLookAndFeelDefaults().put(defaultsKey, map);
3736             }
3737             SwingUtilities.replaceUIActionMap(c, map);
3738         }
3739 
3740         /**
3741          * Returns an ActionMap that will be populated by invoking the
3742          * <code>loadActionMap</code> method on the specified Class
3743          * when necessary.
3744          * <p>
3745          * This should be used if the ActionMap can be shared.
3746          *
3747          * @param c JComponent to install the ActionMap on.
3748          * @param loaderClass Class object that gets loadActionMap invoked
3749          *                    on.
3750          * @param defaultsKey Key to use to defaults table to check for
3751          *        existing map and what resulting Map will be registered on.
3752          */
3753         static ActionMap getActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loaderClass, final String defaultsKey) {
3754             ActionMap map = (ActionMap)UIManager.get(defaultsKey);
3755             if (map == null) {
3756                 map = new LazyActionMap(loaderClass);
3757                 UIManager.getLookAndFeelDefaults().put(defaultsKey, map);
3758             }
3759             return map;
3760         }
3761 
3762         private LazyActionMap(final Class<AquaTabbedPaneCopyFromBasicUI> loader) {
3763             _loader = loader;
3764         }
3765 
3766         public void put(final Action action) {
3767             put(action.getValue(Action.NAME), action);
3768         }
3769 
3770         public void put(final Object key, final Action action) {
3771             loadIfNecessary();
3772             super.put(key, action);
3773         }
3774 
3775         public Action get(final Object key) {
3776             loadIfNecessary();
3777             return super.get(key);
3778         }
3779 
3780         public void remove(final Object key) {
3781             loadIfNecessary();
3782             super.remove(key);
3783         }
3784 
3785         public void clear() {
3786             loadIfNecessary();
3787             super.clear();
3788         }
3789 
3790         public Object[] keys() {
3791             loadIfNecessary();
3792             return super.keys();
3793         }
3794 
3795         public int size() {
3796             loadIfNecessary();
3797             return super.size();
3798         }
3799 
3800         public Object[] allKeys() {
3801             loadIfNecessary();
3802             return super.allKeys();
3803         }
3804 
3805         public void setParent(final ActionMap map) {
3806             loadIfNecessary();
3807             super.setParent(map);
3808         }
3809 
3810         private void loadIfNecessary() {
3811             if (_loader != null) {
3812                 final Object loader = _loader;
3813 
3814                 _loader = null;
3815                 final Class<?> klass = (Class<?>)loader;
3816                 try {
3817                     final java.lang.reflect.Method method = klass.getDeclaredMethod("loadActionMap", new Class[] { LazyActionMap.class });
3818                     method.invoke(klass, new Object[] { this });
3819                 } catch (final NoSuchMethodException nsme) {
3820                     assert false : "LazyActionMap unable to load actions " + klass;
3821                 } catch (final IllegalAccessException iae) {
3822                     assert false : "LazyActionMap unable to load actions " + iae;
3823                 } catch (final InvocationTargetException ite) {
3824                     assert false : "LazyActionMap unable to load actions " + ite;
3825                 } catch (final IllegalArgumentException iae) {
3826                     assert false : "LazyActionMap unable to load actions " + iae;
3827                 }
3828             }
3829         }
3830     }
3831 }