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