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