1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 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, BasicLookAndFeel.getFocusAcceleratorKeyMask()),
 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 &lt; 0 || index &gt;= 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             } else if (!scrollableTabLayoutEnabled() && isHorizontalTabPlacement()) {
 879                 clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, textRect.width);
 880             }
 881 
 882             paintText(g, tabPlacement, font, metrics,
 883                     tabIndex, clippedTitle, textRect, isSelected);
 884 
 885             paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
 886         }
 887         paintFocusIndicator(g, tabPlacement, rects, tabIndex,
 888                   iconRect, textRect, isSelected);
 889     }
 890 
 891     private boolean isHorizontalTabPlacement() {
 892         return tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM;
 893     }
 894 
 895     /* This method will create and return a polygon shape for the given tab rectangle
 896      * which has been cropped at the specified cropline with a torn edge visual.
 897      * e.g. A "File" tab which has cropped been cropped just after the "i":
 898      *             -------------
 899      *             |  .....     |
 900      *             |  .          |
 901      *             |  ...  .    |
 902      *             |  .    .   |
 903      *             |  .    .    |
 904      *             |  .    .     |
 905      *             --------------
 906      *
 907      * The x, y arrays below define the pattern used to create a "torn" edge
 908      * segment which is repeated to fill the edge of the tab.
 909      * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by
 910      * line segments which are defined by coordinates obtained by
 911      * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i]
 912      * to (tab.y).
 913      * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by
 914      * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i]
 915      * to (tab.x).
 916      */
 917     private static int xCropLen[] = {1,1,0,0,1,1,2,2};
 918     private static int yCropLen[] = {0,3,3,6,6,9,9,12};
 919     private static final int CROP_SEGMENT = 12;
 920 
 921     private static Polygon createCroppedTabShape(int tabPlacement, Rectangle tabRect, int cropline) {
 922         int rlen;
 923         int start;
 924         int end;
 925         int ostart;
 926 
 927         switch(tabPlacement) {
 928           case LEFT:
 929           case RIGHT:
 930               rlen = tabRect.width;
 931               start = tabRect.x;
 932               end = tabRect.x + tabRect.width;
 933               ostart = tabRect.y + tabRect.height;
 934               break;
 935           case TOP:
 936           case BOTTOM:
 937           default:
 938              rlen = tabRect.height;
 939              start = tabRect.y;
 940              end = tabRect.y + tabRect.height;
 941              ostart = tabRect.x + tabRect.width;
 942         }
 943         int rcnt = rlen/CROP_SEGMENT;
 944         if (rlen%CROP_SEGMENT > 0) {
 945             rcnt++;
 946         }
 947         int npts = 2 + (rcnt*8);
 948         int xp[] = new int[npts];
 949         int yp[] = new int[npts];
 950         int pcnt = 0;
 951 
 952         xp[pcnt] = ostart;
 953         yp[pcnt++] = end;
 954         xp[pcnt] = ostart;
 955         yp[pcnt++] = start;
 956         for(int i = 0; i < rcnt; i++) {
 957             for(int j = 0; j < xCropLen.length; j++) {
 958                 xp[pcnt] = cropline - xCropLen[j];
 959                 yp[pcnt] = start + (i*CROP_SEGMENT) + yCropLen[j];
 960                 if (yp[pcnt] >= end) {
 961                     yp[pcnt] = end;
 962                     pcnt++;
 963                     break;
 964                 }
 965                 pcnt++;
 966             }
 967         }
 968         if (tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM) {
 969            return new Polygon(xp, yp, pcnt);
 970 
 971         } else { // LEFT or RIGHT
 972            return new Polygon(yp, xp, pcnt);
 973         }
 974     }
 975 
 976     /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
 977      * indicating the tab is cropped in the viewport display
 978      */
 979     private void paintCroppedTabEdge(Graphics g) {
 980         int tabIndex = tabScroller.croppedEdge.getTabIndex();
 981         int cropline = tabScroller.croppedEdge.getCropline();
 982         int x,y;
 983         switch(tabPane.getTabPlacement()) {
 984           case LEFT:
 985           case RIGHT:
 986             x = rects[tabIndex].x;
 987             y = cropline;
 988             int xx = x;
 989             g.setColor(shadow);
 990             while(xx <= x+rects[tabIndex].width) {
 991                 for (int i=0; i < xCropLen.length; i+=2) {
 992                     g.drawLine(xx+yCropLen[i],y-xCropLen[i],
 993                                xx+yCropLen[i+1]-1,y-xCropLen[i+1]);
 994                 }
 995                 xx+=CROP_SEGMENT;
 996             }
 997             break;
 998           case TOP:
 999           case BOTTOM:
1000           default:
1001             x = cropline;
1002             y = rects[tabIndex].y;
1003             int yy = y;
1004             g.setColor(shadow);
1005             while(yy <= y+rects[tabIndex].height) {
1006                 for (int i=0; i < xCropLen.length; i+=2) {
1007                     g.drawLine(x-xCropLen[i],yy+yCropLen[i],
1008                                x-xCropLen[i+1],yy+yCropLen[i+1]-1);
1009                 }
1010                 yy+=CROP_SEGMENT;
1011             }
1012         }
1013     }
1014 
1015     protected void layoutLabel(int tabPlacement,
1016                                FontMetrics metrics, int tabIndex,
1017                                String title, Icon icon,
1018                                Rectangle tabRect, Rectangle iconRect,
1019                                Rectangle textRect, boolean isSelected ) {
1020         textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
1021 
1022         View v = getTextViewForTab(tabIndex);
1023         if (v != null) {
1024             tabPane.putClientProperty("html", v);
1025         }
1026 
1027         SwingUtilities.layoutCompoundLabel(tabPane,
1028                                            metrics, title, icon,
1029                                            SwingUtilities.CENTER,
1030                                            SwingUtilities.CENTER,
1031                                            SwingUtilities.CENTER,
1032                                            SwingUtilities.TRAILING,
1033                                            tabRect,
1034                                            iconRect,
1035                                            textRect,
1036                                            textIconGap);
1037 
1038         tabPane.putClientProperty("html", null);
1039 
1040         int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
1041         int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
1042         iconRect.x += xNudge;
1043         iconRect.y += yNudge;
1044         textRect.x += xNudge;
1045         textRect.y += yNudge;
1046     }
1047 
1048     protected void paintIcon(Graphics g, int tabPlacement,
1049                              int tabIndex, Icon icon, Rectangle iconRect,
1050                              boolean isSelected ) {
1051         if (icon != null) {
1052             icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
1053         }
1054     }
1055 
1056     protected void paintText(Graphics g, int tabPlacement,
1057                              Font font, FontMetrics metrics, int tabIndex,
1058                              String title, Rectangle textRect,
1059                              boolean isSelected) {
1060 
1061         g.setFont(font);
1062 
1063         View v = getTextViewForTab(tabIndex);
1064         if (v != null) {
1065             // html
1066             v.paint(g, textRect);
1067         } else {
1068             // plain text
1069             int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1070 
1071             if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
1072                 Color fg = tabPane.getForegroundAt(tabIndex);
1073                 if (isSelected && (fg instanceof UIResource)) {
1074                     Color selectedFG = UIManager.getColor(
1075                                   "TabbedPane.selectedForeground");
1076                     if (selectedFG != null) {
1077                         fg = selectedFG;
1078                     }
1079                 }
1080                 g.setColor(fg);
1081                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1082                              title, mnemIndex,
1083                              textRect.x, textRect.y + metrics.getAscent());
1084 
1085             } else { // tab disabled
1086                 g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
1087                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1088                              title, mnemIndex,
1089                              textRect.x, textRect.y + metrics.getAscent());
1090                 g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
1091                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1092                              title, mnemIndex,
1093                              textRect.x - 1, textRect.y + metrics.getAscent() - 1);
1094 
1095             }
1096         }
1097     }
1098 
1099 
1100     protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
1101         Rectangle tabRect = rects[tabIndex];
1102         String propKey = (isSelected ? "selectedLabelShift" : "labelShift");
1103         int nudge = DefaultLookup.getInt(
1104                 tabPane, this, "TabbedPane." + propKey, 1);
1105 
1106         switch (tabPlacement) {
1107             case LEFT:
1108                 return nudge;
1109             case RIGHT:
1110                 return -nudge;
1111             case BOTTOM:
1112             case TOP:
1113             default:
1114                 return tabRect.width % 2;
1115         }
1116     }
1117 
1118     protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
1119         Rectangle tabRect = rects[tabIndex];
1120         int nudge = (isSelected ? DefaultLookup.getInt(tabPane, this, "TabbedPane.selectedLabelShift", -1) :
1121                 DefaultLookup.getInt(tabPane, this, "TabbedPane.labelShift", 1));
1122 
1123         switch (tabPlacement) {
1124             case BOTTOM:
1125                 return -nudge;
1126             case LEFT:
1127             case RIGHT:
1128                 return tabRect.height % 2;
1129             case TOP:
1130             default:
1131                 return nudge;
1132         }
1133     }
1134 
1135     protected void paintFocusIndicator(Graphics g, int tabPlacement,
1136                                        Rectangle[] rects, int tabIndex,
1137                                        Rectangle iconRect, Rectangle textRect,
1138                                        boolean isSelected) {
1139         Rectangle tabRect = rects[tabIndex];
1140         if (tabPane.hasFocus() && isSelected) {
1141             int x, y, w, h;
1142             g.setColor(focus);
1143             switch(tabPlacement) {
1144               case LEFT:
1145                   x = tabRect.x + 3;
1146                   y = tabRect.y + 3;
1147                   w = tabRect.width - 5;
1148                   h = tabRect.height - 6;
1149                   break;
1150               case RIGHT:
1151                   x = tabRect.x + 2;
1152                   y = tabRect.y + 3;
1153                   w = tabRect.width - 5;
1154                   h = tabRect.height - 6;
1155                   break;
1156               case BOTTOM:
1157                   x = tabRect.x + 3;
1158                   y = tabRect.y + 2;
1159                   w = tabRect.width - 6;
1160                   h = tabRect.height - 5;
1161                   break;
1162               case TOP:
1163               default:
1164                   x = tabRect.x + 3;
1165                   y = tabRect.y + 3;
1166                   w = tabRect.width - 6;
1167                   h = tabRect.height - 5;
1168             }
1169             BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
1170         }
1171     }
1172 
1173     /**
1174       * this function draws the border around each tab
1175       * note that this function does now draw the background of the tab.
1176       * that is done elsewhere
1177       *
1178       * @param g             the graphics context in which to paint
1179       * @param tabPlacement  the placement (left, right, bottom, top) of the tab
1180       * @param tabIndex      the index of the tab with respect to other tabs
1181       * @param x             the x coordinate of tab
1182       * @param y             the y coordinate of tab
1183       * @param w             the width of the tab
1184       * @param h             the height of the tab
1185       * @param isSelected    a {@code boolean} which determines whether or not
1186       * the tab is selected
1187       */
1188     protected void paintTabBorder(Graphics g, int tabPlacement,
1189                                   int tabIndex,
1190                                   int x, int y, int w, int h,
1191                                   boolean isSelected ) {
1192         g.setColor(lightHighlight);
1193 
1194         switch (tabPlacement) {
1195           case LEFT:
1196               g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1197               g.drawLine(x, y+2, x, y+h-3); // left highlight
1198               g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1199               g.drawLine(x+2, y, x+w-1, y); // top highlight
1200 
1201               g.setColor(shadow);
1202               g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow
1203 
1204               g.setColor(darkShadow);
1205               g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow
1206               break;
1207           case RIGHT:
1208               g.drawLine(x, y, x+w-3, y); // top highlight
1209 
1210               g.setColor(shadow);
1211               g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow
1212               g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow
1213 
1214               g.setColor(darkShadow);
1215               g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow
1216               g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1217               g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow
1218               g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1219               break;
1220           case BOTTOM:
1221               g.drawLine(x, y, x, y+h-3); // left highlight
1222               g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1223 
1224               g.setColor(shadow);
1225               g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow
1226               g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow
1227 
1228               g.setColor(darkShadow);
1229               g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1230               g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1231               g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
1232               break;
1233           case TOP:
1234           default:
1235               g.drawLine(x, y+2, x, y+h-1); // left highlight
1236               g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1237               g.drawLine(x+2, y, x+w-3, y); // top highlight
1238 
1239               g.setColor(shadow);
1240               g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow
1241 
1242               g.setColor(darkShadow);
1243               g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow
1244               g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow
1245         }
1246     }
1247 
1248     protected void paintTabBackground(Graphics g, int tabPlacement,
1249                                       int tabIndex,
1250                                       int x, int y, int w, int h,
1251                                       boolean isSelected ) {
1252         g.setColor(!isSelected || selectedColor == null?
1253                    tabPane.getBackgroundAt(tabIndex) : selectedColor);
1254         switch(tabPlacement) {
1255           case LEFT:
1256               g.fillRect(x+1, y+1, w-1, h-3);
1257               break;
1258           case RIGHT:
1259               g.fillRect(x, y+1, w-2, h-3);
1260               break;
1261           case BOTTOM:
1262               g.fillRect(x+1, y, w-3, h-1);
1263               break;
1264           case TOP:
1265           default:
1266               g.fillRect(x+1, y+1, w-3, h-1);
1267         }
1268     }
1269 
1270     protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
1271         int width = tabPane.getWidth();
1272         int height = tabPane.getHeight();
1273         Insets insets = tabPane.getInsets();
1274         Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1275 
1276         int x = insets.left;
1277         int y = insets.top;
1278         int w = width - insets.right - insets.left;
1279         int h = height - insets.top - insets.bottom;
1280 
1281         switch(tabPlacement) {
1282           case LEFT:
1283               x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1284               if (tabsOverlapBorder) {
1285                   x -= tabAreaInsets.right;
1286               }
1287               w -= (x - insets.left);
1288               break;
1289           case RIGHT:
1290               w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1291               if (tabsOverlapBorder) {
1292                   w += tabAreaInsets.left;
1293               }
1294               break;
1295           case BOTTOM:
1296               h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1297               if (tabsOverlapBorder) {
1298                   h += tabAreaInsets.top;
1299               }
1300               break;
1301           case TOP:
1302           default:
1303               y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1304               if (tabsOverlapBorder) {
1305                   y -= tabAreaInsets.bottom;
1306               }
1307               h -= (y - insets.top);
1308         }
1309 
1310             if ( tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque()) ) {
1311             // Fill region behind content area
1312             Color color = UIManager.getColor("TabbedPane.contentAreaColor");
1313             if (color != null) {
1314                 g.setColor(color);
1315             }
1316             else if ( selectedColor == null || selectedIndex == -1 ) {
1317                 g.setColor(tabPane.getBackground());
1318             }
1319             else {
1320                 g.setColor(selectedColor);
1321             }
1322             g.fillRect(x,y,w,h);
1323         }
1324 
1325         paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1326         paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1327         paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1328         paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1329 
1330     }
1331 
1332     protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
1333                                          int selectedIndex,
1334                                          int x, int y, int w, int h) {
1335         Rectangle selRect = selectedIndex < 0? null :
1336                                getTabBounds(selectedIndex, calcRect);
1337 
1338         g.setColor(lightHighlight);
1339 
1340         // Draw unbroken line if tabs are not on TOP, OR
1341         // selected tab is not in run adjacent to content, OR
1342         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1343         //
1344         if (tabPlacement != TOP || selectedIndex < 0 ||
1345             (selRect.y + selRect.height + 1 < y) ||
1346             (selRect.x < x || selRect.x > x + w)) {
1347             g.drawLine(x, y, x+w-2, y);
1348         } else {
1349             // Break line to show visual connection to selected tab
1350             g.drawLine(x, y, selRect.x - 1, y);
1351             if (selRect.x + selRect.width < x + w - 2) {
1352                 g.drawLine(selRect.x + selRect.width, y,
1353                            x+w-2, y);
1354             } else {
1355                 g.setColor(shadow);
1356                 g.drawLine(x+w-2, y, x+w-2, y);
1357             }
1358         }
1359     }
1360 
1361     protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
1362                                                int selectedIndex,
1363                                                int x, int y, int w, int h) {
1364         Rectangle selRect = selectedIndex < 0? null :
1365                                getTabBounds(selectedIndex, calcRect);
1366 
1367         g.setColor(lightHighlight);
1368 
1369         // Draw unbroken line if tabs are not on LEFT, OR
1370         // selected tab is not in run adjacent to content, OR
1371         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1372         //
1373         if (tabPlacement != LEFT || selectedIndex < 0 ||
1374             (selRect.x + selRect.width + 1 < x) ||
1375             (selRect.y < y || selRect.y > y + h)) {
1376             g.drawLine(x, y, x, y+h-2);
1377         } else {
1378             // Break line to show visual connection to selected tab
1379             g.drawLine(x, y, x, selRect.y - 1);
1380             if (selRect.y + selRect.height < y + h - 2) {
1381                 g.drawLine(x, selRect.y + selRect.height,
1382                            x, y+h-2);
1383             }
1384         }
1385     }
1386 
1387     protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
1388                                                int selectedIndex,
1389                                                int x, int y, int w, int h) {
1390         Rectangle selRect = selectedIndex < 0? null :
1391                                getTabBounds(selectedIndex, calcRect);
1392 
1393         g.setColor(shadow);
1394 
1395         // Draw unbroken line if tabs are not on BOTTOM, OR
1396         // selected tab is not in run adjacent to content, OR
1397         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1398         //
1399         if (tabPlacement != BOTTOM || selectedIndex < 0 ||
1400              (selRect.y - 1 > h) ||
1401              (selRect.x < x || selRect.x > x + w)) {
1402             g.drawLine(x+1, y+h-2, x+w-2, y+h-2);
1403             g.setColor(darkShadow);
1404             g.drawLine(x, y+h-1, x+w-1, y+h-1);
1405         } else {
1406             // Break line to show visual connection to selected tab
1407             g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2);
1408             g.setColor(darkShadow);
1409             g.drawLine(x, y+h-1, selRect.x - 1, y+h-1);
1410             if (selRect.x + selRect.width < x + w - 2) {
1411                 g.setColor(shadow);
1412                 g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
1413                 g.setColor(darkShadow);
1414                 g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1);
1415             }
1416         }
1417 
1418     }
1419 
1420     protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
1421                                                int selectedIndex,
1422                                                int x, int y, int w, int h) {
1423         Rectangle selRect = selectedIndex < 0? null :
1424                                getTabBounds(selectedIndex, calcRect);
1425 
1426         g.setColor(shadow);
1427 
1428         // Draw unbroken line if tabs are not on RIGHT, OR
1429         // selected tab is not in run adjacent to content, OR
1430         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1431         //
1432         if (tabPlacement != RIGHT || selectedIndex < 0 ||
1433              (selRect.x - 1 > w) ||
1434              (selRect.y < y || selRect.y > y + h)) {
1435             g.drawLine(x+w-2, y+1, x+w-2, y+h-3);
1436             g.setColor(darkShadow);
1437             g.drawLine(x+w-1, y, x+w-1, y+h-1);
1438         } else {
1439             // Break line to show visual connection to selected tab
1440             g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1);
1441             g.setColor(darkShadow);
1442             g.drawLine(x+w-1, y, x+w-1, selRect.y - 1);
1443 
1444             if (selRect.y + selRect.height < y + h - 2) {
1445                 g.setColor(shadow);
1446                 g.drawLine(x+w-2, selRect.y + selRect.height,
1447                            x+w-2, y+h-2);
1448                 g.setColor(darkShadow);
1449                 g.drawLine(x+w-1, selRect.y + selRect.height,
1450                            x+w-1, y+h-2);
1451             }
1452         }
1453     }
1454 
1455     private void ensureCurrentLayout() {
1456         if (!tabPane.isValid()) {
1457             tabPane.validate();
1458         }
1459         /* If tabPane doesn't have a peer yet, the validate() call will
1460          * silently fail.  We handle that by forcing a layout if tabPane
1461          * is still invalid.  See bug 4237677.
1462          */
1463         if (!tabPane.isValid()) {
1464             TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
1465             layout.calculateLayoutInfo();
1466         }
1467     }
1468 
1469 
1470 // TabbedPaneUI methods
1471 
1472     /**
1473      * Returns the bounds of the specified tab index.  The bounds are
1474      * with respect to the JTabbedPane's coordinate space.
1475      */
1476     public Rectangle getTabBounds(JTabbedPane pane, int i) {
1477         ensureCurrentLayout();
1478         Rectangle tabRect = new Rectangle();
1479         return getTabBounds(i, tabRect);
1480     }
1481 
1482     public int getTabRunCount(JTabbedPane pane) {
1483         ensureCurrentLayout();
1484         return runCount;
1485     }
1486 
1487     /**
1488      * Returns the tab index which intersects the specified point
1489      * in the JTabbedPane's coordinate space.
1490      */
1491     public int tabForCoordinate(JTabbedPane pane, int x, int y) {
1492         return tabForCoordinate(pane, x, y, true);
1493     }
1494 
1495     private int tabForCoordinate(JTabbedPane pane, int x, int y,
1496                                  boolean validateIfNecessary) {
1497         if (validateIfNecessary) {
1498             ensureCurrentLayout();
1499         }
1500         if (isRunsDirty) {
1501             // We didn't recalculate the layout, runs and tabCount may not
1502             // line up, bail.
1503             return -1;
1504         }
1505         Point p = new Point(x, y);
1506 
1507         if (scrollableTabLayoutEnabled()) {
1508             translatePointToTabPanel(x, y, p);
1509             Rectangle viewRect = tabScroller.viewport.getViewRect();
1510             if (!viewRect.contains(p)) {
1511                 return -1;
1512             }
1513         }
1514         int tabCount = tabPane.getTabCount();
1515         for (int i = 0; i < tabCount; i++) {
1516             if (rects[i].contains(p.x, p.y)) {
1517                 return i;
1518             }
1519         }
1520         return -1;
1521     }
1522 
1523     /**
1524      * Returns the bounds of the specified tab in the coordinate space
1525      * of the JTabbedPane component.  This is required because the tab rects
1526      * are by default defined in the coordinate space of the component where
1527      * they are rendered, which could be the JTabbedPane
1528      * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
1529      * This method should be used whenever the tab rectangle must be relative
1530      * to the JTabbedPane itself and the result should be placed in a
1531      * designated Rectangle object (rather than instantiating and returning
1532      * a new Rectangle each time). The tab index parameter must be a valid
1533      * tabbed pane tab index (0 to tab count - 1, inclusive).  The destination
1534      * rectangle parameter must be a valid <code>Rectangle</code> instance.
1535      * The handling of invalid parameters is unspecified.
1536      *
1537      * @param tabIndex the index of the tab
1538      * @param dest the rectangle where the result should be placed
1539      * @return the resulting rectangle
1540      *
1541      * @since 1.4
1542      */
1543     protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
1544         dest.width = rects[tabIndex].width;
1545         dest.height = rects[tabIndex].height;
1546 
1547         if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
1548             // Need to translate coordinates based on viewport location &
1549             // view position
1550             Point vpp = tabScroller.viewport.getLocation();
1551             Point viewp = tabScroller.viewport.getViewPosition();
1552             dest.x = rects[tabIndex].x + vpp.x - viewp.x;
1553             dest.y = rects[tabIndex].y + vpp.y - viewp.y;
1554 
1555         } else { // WRAP_TAB_LAYOUT
1556             dest.x = rects[tabIndex].x;
1557             dest.y = rects[tabIndex].y;
1558         }
1559         return dest;
1560     }
1561 
1562     /**
1563      * Returns the index of the tab closest to the passed in location, note
1564      * that the returned tab may not contain the location x,y.
1565      */
1566     private int getClosestTab(int x, int y) {
1567         int min = 0;
1568         int tabCount = Math.min(rects.length, tabPane.getTabCount());
1569         int max = tabCount;
1570         int tabPlacement = tabPane.getTabPlacement();
1571         boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
1572         int want = (useX) ? x : y;
1573 
1574         while (min != max) {
1575             int current = (max + min) / 2;
1576             int minLoc;
1577             int maxLoc;
1578 
1579             if (useX) {
1580                 minLoc = rects[current].x;
1581                 maxLoc = minLoc + rects[current].width;
1582             }
1583             else {
1584                 minLoc = rects[current].y;
1585                 maxLoc = minLoc + rects[current].height;
1586             }
1587             if (want < minLoc) {
1588                 max = current;
1589                 if (min == max) {
1590                     return Math.max(0, current - 1);
1591                 }
1592             }
1593             else if (want >= maxLoc) {
1594                 min = current;
1595                 if (max - min <= 1) {
1596                     return Math.max(current + 1, tabCount - 1);
1597                 }
1598             }
1599             else {
1600                 return current;
1601             }
1602         }
1603         return min;
1604     }
1605 
1606     /**
1607      * Returns a point which is translated from the specified point in the
1608      * JTabbedPane's coordinate space to the coordinate space of the
1609      * ScrollableTabPanel.  This is used for SCROLL_TAB_LAYOUT ONLY.
1610      */
1611     private Point translatePointToTabPanel(int srcx, int srcy, Point dest) {
1612         Point vpp = tabScroller.viewport.getLocation();
1613         Point viewp = tabScroller.viewport.getViewPosition();
1614         dest.x = srcx - vpp.x + viewp.x;
1615         dest.y = srcy - vpp.y + viewp.y;
1616         return dest;
1617     }
1618 
1619 // BasicTabbedPaneUI methods
1620 
1621     protected Component getVisibleComponent() {
1622         return visibleComponent;
1623     }
1624 
1625     protected void setVisibleComponent(Component component) {
1626         if (visibleComponent != null
1627                 && visibleComponent != component
1628                 && visibleComponent.getParent() == tabPane
1629                 && visibleComponent.isVisible()) {
1630 
1631             visibleComponent.setVisible(false);
1632         }
1633         if (component != null && !component.isVisible()) {
1634             component.setVisible(true);
1635         }
1636         visibleComponent = component;
1637     }
1638 
1639     protected void assureRectsCreated(int tabCount) {
1640         int rectArrayLen = rects.length;
1641         if (tabCount != rectArrayLen ) {
1642             Rectangle[] tempRectArray = new Rectangle[tabCount];
1643             System.arraycopy(rects, 0, tempRectArray, 0,
1644                              Math.min(rectArrayLen, tabCount));
1645             rects = tempRectArray;
1646             for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
1647                 rects[rectIndex] = new Rectangle();
1648             }
1649         }
1650 
1651     }
1652 
1653     protected void expandTabRunsArray() {
1654         int rectLen = tabRuns.length;
1655         int[] newArray = new int[rectLen+10];
1656         System.arraycopy(tabRuns, 0, newArray, 0, runCount);
1657         tabRuns = newArray;
1658     }
1659 
1660     protected int getRunForTab(int tabCount, int tabIndex) {
1661         for (int i = 0; i < runCount; i++) {
1662             int first = tabRuns[i];
1663             int last = lastTabInRun(tabCount, i);
1664             if (tabIndex >= first && tabIndex <= last) {
1665                 return i;
1666             }
1667         }
1668         return 0;
1669     }
1670 
1671     protected int lastTabInRun(int tabCount, int run) {
1672         if (runCount == 1) {
1673             return tabCount - 1;
1674         }
1675         int nextRun = (run == runCount - 1? 0 : run + 1);
1676         if (tabRuns[nextRun] == 0) {
1677             return tabCount - 1;
1678         }
1679         return tabRuns[nextRun]-1;
1680     }
1681 
1682     protected int getTabRunOverlay(int tabPlacement) {
1683         return tabRunOverlay;
1684     }
1685 
1686     protected int getTabRunIndent(int tabPlacement, int run) {
1687         return 0;
1688     }
1689 
1690     protected boolean shouldPadTabRun(int tabPlacement, int run) {
1691         return runCount > 1;
1692     }
1693 
1694     protected boolean shouldRotateTabRuns(int tabPlacement) {
1695         return true;
1696     }
1697 
1698     protected Icon getIconForTab(int tabIndex) {
1699         return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))?
1700                           tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex);
1701     }
1702 
1703     /**
1704      * Returns the text View object required to render stylized text (HTML) for
1705      * the specified tab or null if no specialized text rendering is needed
1706      * for this tab. This is provided to support html rendering inside tabs.
1707      *
1708      * @param tabIndex the index of the tab
1709      * @return the text view to render the tab's text or null if no
1710      *         specialized rendering is required
1711      *
1712      * @since 1.4
1713      */
1714     protected View getTextViewForTab(int tabIndex) {
1715         if (htmlViews != null) {
1716             return htmlViews.elementAt(tabIndex);
1717         }
1718         return null;
1719     }
1720 
1721     protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
1722         int height = 0;
1723         Component c = tabPane.getTabComponentAt(tabIndex);
1724         if (c != null) {
1725             height = c.getPreferredSize().height;
1726         } else {
1727             View v = getTextViewForTab(tabIndex);
1728             if (v != null) {
1729                 // html
1730                 height += (int) v.getPreferredSpan(View.Y_AXIS);
1731             } else {
1732                 // plain text
1733                 height += fontHeight;
1734             }
1735             Icon icon = getIconForTab(tabIndex);
1736 
1737             if (icon != null) {
1738                 height = Math.max(height, icon.getIconHeight());
1739             }
1740         }
1741         Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1742         height += tabInsets.top + tabInsets.bottom + 2;
1743         return height;
1744     }
1745 
1746     protected int calculateMaxTabHeight(int tabPlacement) {
1747         FontMetrics metrics = getFontMetrics();
1748         int tabCount = tabPane.getTabCount();
1749         int result = 0;
1750         int fontHeight = metrics.getHeight();
1751         for(int i = 0; i < tabCount; i++) {
1752             result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
1753         }
1754         return result;
1755     }
1756 
1757     protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
1758         Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1759         int width = tabInsets.left + tabInsets.right + 3;
1760         Component tabComponent = tabPane.getTabComponentAt(tabIndex);
1761         if (tabComponent != null) {
1762             width += tabComponent.getPreferredSize().width;
1763         } else {
1764             Icon icon = getIconForTab(tabIndex);
1765             if (icon != null) {
1766                 width += icon.getIconWidth() + textIconGap;
1767             }
1768             View v = getTextViewForTab(tabIndex);
1769             if (v != null) {
1770                 // html
1771                 width += (int) v.getPreferredSpan(View.X_AXIS);
1772             } else {
1773                 // plain text
1774                 String title = tabPane.getTitleAt(tabIndex);
1775                 width += SwingUtilities2.stringWidth(tabPane, metrics, title);
1776             }
1777         }
1778         return width;
1779     }
1780 
1781     protected int calculateMaxTabWidth(int tabPlacement) {
1782         FontMetrics metrics = getFontMetrics();
1783         int tabCount = tabPane.getTabCount();
1784         int result = 0;
1785         for(int i = 0; i < tabCount; i++) {
1786             result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
1787         }
1788         return result;
1789     }
1790 
1791     protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) {
1792         Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1793         int tabRunOverlay = getTabRunOverlay(tabPlacement);
1794         return (horizRunCount > 0?
1795                 horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay +
1796                 tabAreaInsets.top + tabAreaInsets.bottom :
1797                 0);
1798     }
1799 
1800     protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) {
1801         Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1802         int tabRunOverlay = getTabRunOverlay(tabPlacement);
1803         return (vertRunCount > 0?
1804                 vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay +
1805                 tabAreaInsets.left + tabAreaInsets.right :
1806                 0);
1807     }
1808 
1809     protected Insets getTabInsets(int tabPlacement, int tabIndex) {
1810         return tabInsets;
1811     }
1812 
1813     protected Insets getSelectedTabPadInsets(int tabPlacement) {
1814         rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
1815         return currentPadInsets;
1816     }
1817 
1818     protected Insets getTabAreaInsets(int tabPlacement) {
1819         rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
1820         return currentTabAreaInsets;
1821     }
1822 
1823     protected Insets getContentBorderInsets(int tabPlacement) {
1824         return contentBorderInsets;
1825     }
1826 
1827     protected FontMetrics getFontMetrics() {
1828         Font font = tabPane.getFont();
1829         return tabPane.getFontMetrics(font);
1830     }
1831 
1832 
1833 // Tab Navigation methods
1834 
1835     protected void navigateSelectedTab(int direction) {
1836         int tabPlacement = tabPane.getTabPlacement();
1837         int current = DefaultLookup.getBoolean(tabPane, this,
1838                              "TabbedPane.selectionFollowsFocus", true) ?
1839                              tabPane.getSelectedIndex() : getFocusIndex();
1840         int tabCount = tabPane.getTabCount();
1841         boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
1842 
1843         // If we have no tabs then don't navigate.
1844         if (tabCount <= 0) {
1845             return;
1846         }
1847 
1848         int offset;
1849         switch(tabPlacement) {
1850           case LEFT:
1851           case RIGHT:
1852               switch(direction) {
1853                  case NEXT:
1854                      selectNextTab(current);
1855                      break;
1856                  case PREVIOUS:
1857                      selectPreviousTab(current);
1858                      break;
1859                 case NORTH:
1860                     selectPreviousTabInRun(current);
1861                     break;
1862                 case SOUTH:
1863                     selectNextTabInRun(current);
1864                     break;
1865                 case WEST:
1866                     offset = getTabRunOffset(tabPlacement, tabCount, current, false);
1867                     selectAdjacentRunTab(tabPlacement, current, offset);
1868                     break;
1869                 case EAST:
1870                     offset = getTabRunOffset(tabPlacement, tabCount, current, true);
1871                     selectAdjacentRunTab(tabPlacement, current, offset);
1872                     break;
1873                 default:
1874               }
1875               break;
1876           case BOTTOM:
1877           case TOP:
1878           default:
1879               switch(direction) {
1880                 case NEXT:
1881                     selectNextTab(current);
1882                     break;
1883                 case PREVIOUS:
1884                     selectPreviousTab(current);
1885                     break;
1886                 case NORTH:
1887                     offset = getTabRunOffset(tabPlacement, tabCount, current, false);
1888                     selectAdjacentRunTab(tabPlacement, current, offset);
1889                     break;
1890                 case SOUTH:
1891                     offset = getTabRunOffset(tabPlacement, tabCount, current, true);
1892                     selectAdjacentRunTab(tabPlacement, current, offset);
1893                     break;
1894                 case EAST:
1895                     if (leftToRight) {
1896                         selectNextTabInRun(current);
1897                     } else {
1898                         selectPreviousTabInRun(current);
1899                     }
1900                     break;
1901                 case WEST:
1902                     if (leftToRight) {
1903                         selectPreviousTabInRun(current);
1904                     } else {
1905                         selectNextTabInRun(current);
1906                     }
1907                     break;
1908                 default:
1909               }
1910         }
1911     }
1912 
1913     protected void selectNextTabInRun(int current) {
1914         int tabCount = tabPane.getTabCount();
1915         int tabIndex = getNextTabIndexInRun(tabCount, current);
1916 
1917         while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1918             tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
1919         }
1920         navigateTo(tabIndex);
1921     }
1922 
1923     protected void selectPreviousTabInRun(int current) {
1924         int tabCount = tabPane.getTabCount();
1925         int tabIndex = getPreviousTabIndexInRun(tabCount, current);
1926 
1927         while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1928             tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
1929         }
1930         navigateTo(tabIndex);
1931     }
1932 
1933     protected void selectNextTab(int current) {
1934         int tabIndex = getNextTabIndex(current);
1935 
1936         while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1937             tabIndex = getNextTabIndex(tabIndex);
1938         }
1939         navigateTo(tabIndex);
1940     }
1941 
1942     protected void selectPreviousTab(int current) {
1943         int tabIndex = getPreviousTabIndex(current);
1944 
1945         while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1946             tabIndex = getPreviousTabIndex(tabIndex);
1947         }
1948         navigateTo(tabIndex);
1949     }
1950 
1951     protected void selectAdjacentRunTab(int tabPlacement,
1952                                         int tabIndex, int offset) {
1953         if ( runCount < 2 ) {
1954             return;
1955         }
1956         int newIndex;
1957         Rectangle r = rects[tabIndex];
1958         switch(tabPlacement) {
1959           case LEFT:
1960           case RIGHT:
1961               newIndex = tabForCoordinate(tabPane, r.x + r.width/2 + offset,
1962                                        r.y + r.height/2);
1963               break;
1964           case BOTTOM:
1965           case TOP:
1966           default:
1967               newIndex = tabForCoordinate(tabPane, r.x + r.width/2,
1968                                        r.y + r.height/2 + offset);
1969         }
1970         if (newIndex != -1) {
1971             while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
1972                 newIndex = getNextTabIndex(newIndex);
1973             }
1974             navigateTo(newIndex);
1975         }
1976     }
1977 
1978     private void navigateTo(int index) {
1979         if (DefaultLookup.getBoolean(tabPane, this,
1980                              "TabbedPane.selectionFollowsFocus", true)) {
1981             tabPane.setSelectedIndex(index);
1982         } else {
1983             // Just move focus (not selection)
1984             setFocusIndex(index, true);
1985         }
1986     }
1987 
1988     void setFocusIndex(int index, boolean repaint) {
1989         if (repaint && !isRunsDirty) {
1990             repaintTab(focusIndex);
1991             focusIndex = index;
1992             repaintTab(focusIndex);
1993         }
1994         else {
1995             focusIndex = index;
1996         }
1997     }
1998 
1999     /**
2000      * Repaints the specified tab.
2001      */
2002     private void repaintTab(int index) {
2003         // If we're not valid that means we will shortly be validated and
2004         // painted, which means we don't have to do anything here.
2005         if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) {
2006             tabPane.repaint(getTabBounds(tabPane, index));
2007         }
2008     }
2009 
2010     /**
2011      * Makes sure the focusIndex is valid.
2012      */
2013     private void validateFocusIndex() {
2014         if (focusIndex >= tabPane.getTabCount()) {
2015             setFocusIndex(tabPane.getSelectedIndex(), false);
2016         }
2017     }
2018 
2019     /**
2020      * Returns the index of the tab that has focus.
2021      *
2022      * @return index of tab that has focus
2023      * @since 1.5
2024      */
2025     protected int getFocusIndex() {
2026         return focusIndex;
2027     }
2028 
2029     protected int getTabRunOffset(int tabPlacement, int tabCount,
2030                                   int tabIndex, boolean forward) {
2031         int run = getRunForTab(tabCount, tabIndex);
2032         int offset;
2033         switch(tabPlacement) {
2034           case LEFT: {
2035               if (run == 0) {
2036                   offset = (forward?
2037                             -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2038                             -maxTabWidth);
2039 
2040               } else if (run == runCount - 1) {
2041                   offset = (forward?
2042                             maxTabWidth :
2043                             calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2044               } else {
2045                   offset = (forward? maxTabWidth : -maxTabWidth);
2046               }
2047               break;
2048           }
2049           case RIGHT: {
2050               if (run == 0) {
2051                   offset = (forward?
2052                             maxTabWidth :
2053                             calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2054               } else if (run == runCount - 1) {
2055                   offset = (forward?
2056                             -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2057                             -maxTabWidth);
2058               } else {
2059                   offset = (forward? maxTabWidth : -maxTabWidth);
2060               }
2061               break;
2062           }
2063           case BOTTOM: {
2064               if (run == 0) {
2065                   offset = (forward?
2066                             maxTabHeight :
2067                             calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2068               } else if (run == runCount - 1) {
2069                   offset = (forward?
2070                             -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2071                             -maxTabHeight);
2072               } else {
2073                   offset = (forward? maxTabHeight : -maxTabHeight);
2074               }
2075               break;
2076           }
2077           case TOP:
2078           default: {
2079               if (run == 0) {
2080                   offset = (forward?
2081                             -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2082                             -maxTabHeight);
2083               } else if (run == runCount - 1) {
2084                   offset = (forward?
2085                             maxTabHeight :
2086                             calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2087               } else {
2088                   offset = (forward? maxTabHeight : -maxTabHeight);
2089               }
2090           }
2091         }
2092         return offset;
2093     }
2094 
2095     protected int getPreviousTabIndex(int base) {
2096         int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1);
2097         return (tabIndex >= 0? tabIndex : 0);
2098     }
2099 
2100     protected int getNextTabIndex(int base) {
2101         return (base+1)%tabPane.getTabCount();
2102     }
2103 
2104     protected int getNextTabIndexInRun(int tabCount, int base) {
2105         if (runCount < 2) {
2106             return getNextTabIndex(base);
2107         }
2108         int currentRun = getRunForTab(tabCount, base);
2109         int next = getNextTabIndex(base);
2110         if (next == tabRuns[getNextTabRun(currentRun)]) {
2111             return tabRuns[currentRun];
2112         }
2113         return next;
2114     }
2115 
2116     protected int getPreviousTabIndexInRun(int tabCount, int base) {
2117         if (runCount < 2) {
2118             return getPreviousTabIndex(base);
2119         }
2120         int currentRun = getRunForTab(tabCount, base);
2121         if (base == tabRuns[currentRun]) {
2122             int previous = tabRuns[getNextTabRun(currentRun)]-1;
2123             return (previous != -1? previous : tabCount-1);
2124         }
2125         return getPreviousTabIndex(base);
2126     }
2127 
2128     protected int getPreviousTabRun(int baseRun) {
2129         int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1);
2130         return (runIndex >= 0? runIndex : 0);
2131     }
2132 
2133     protected int getNextTabRun(int baseRun) {
2134         return (baseRun+1)%runCount;
2135     }
2136 
2137     protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) {
2138 
2139         switch(targetPlacement) {
2140           case LEFT:
2141               targetInsets.top = topInsets.left;
2142               targetInsets.left = topInsets.top;
2143               targetInsets.bottom = topInsets.right;
2144               targetInsets.right = topInsets.bottom;
2145               break;
2146           case BOTTOM:
2147               targetInsets.top = topInsets.bottom;
2148               targetInsets.left = topInsets.left;
2149               targetInsets.bottom = topInsets.top;
2150               targetInsets.right = topInsets.right;
2151               break;
2152           case RIGHT:
2153               targetInsets.top = topInsets.left;
2154               targetInsets.left = topInsets.bottom;
2155               targetInsets.bottom = topInsets.right;
2156               targetInsets.right = topInsets.top;
2157               break;
2158           case TOP:
2159           default:
2160               targetInsets.top = topInsets.top;
2161               targetInsets.left = topInsets.left;
2162               targetInsets.bottom = topInsets.bottom;
2163               targetInsets.right = topInsets.right;
2164         }
2165     }
2166 
2167     // REMIND(aim,7/29/98): This method should be made
2168     // protected in the next release where
2169     // API changes are allowed
2170     boolean requestFocusForVisibleComponent() {
2171         return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent());
2172     }
2173 
2174     private static class Actions extends UIAction {
2175         final static String NEXT = "navigateNext";
2176         final static String PREVIOUS = "navigatePrevious";
2177         final static String RIGHT = "navigateRight";
2178         final static String LEFT = "navigateLeft";
2179         final static String UP = "navigateUp";
2180         final static String DOWN = "navigateDown";
2181         final static String PAGE_UP = "navigatePageUp";
2182         final static String PAGE_DOWN = "navigatePageDown";
2183         final static String REQUEST_FOCUS = "requestFocus";
2184         final static String REQUEST_FOCUS_FOR_VISIBLE =
2185                                     "requestFocusForVisibleComponent";
2186         final static String SET_SELECTED = "setSelectedIndex";
2187         final static String SELECT_FOCUSED = "selectTabWithFocus";
2188         final static String SCROLL_FORWARD = "scrollTabsForwardAction";
2189         final static String SCROLL_BACKWARD = "scrollTabsBackwardAction";
2190 
2191         Actions(String key) {
2192             super(key);
2193         }
2194 
2195         public void actionPerformed(ActionEvent e) {
2196             String key = getName();
2197             JTabbedPane pane = (JTabbedPane)e.getSource();
2198             BasicTabbedPaneUI ui = (BasicTabbedPaneUI)BasicLookAndFeel.
2199                        getUIOfType(pane.getUI(), BasicTabbedPaneUI.class);
2200 
2201             if (ui == null) {
2202                 return;
2203             }
2204             if (key == NEXT) {
2205                 ui.navigateSelectedTab(SwingConstants.NEXT);
2206             }
2207             else if (key == PREVIOUS) {
2208                 ui.navigateSelectedTab(SwingConstants.PREVIOUS);
2209             }
2210             else if (key == RIGHT) {
2211                 ui.navigateSelectedTab(SwingConstants.EAST);
2212             }
2213             else if (key == LEFT) {
2214                 ui.navigateSelectedTab(SwingConstants.WEST);
2215             }
2216             else if (key == UP) {
2217                 ui.navigateSelectedTab(SwingConstants.NORTH);
2218             }
2219             else if (key == DOWN) {
2220                 ui.navigateSelectedTab(SwingConstants.SOUTH);
2221             }
2222             else if (key == PAGE_UP) {
2223                 int tabPlacement = pane.getTabPlacement();
2224                 if (tabPlacement == TOP|| tabPlacement == BOTTOM) {
2225                     ui.navigateSelectedTab(SwingConstants.WEST);
2226                 } else {
2227                     ui.navigateSelectedTab(SwingConstants.NORTH);
2228                 }
2229             }
2230             else if (key == PAGE_DOWN) {
2231                 int tabPlacement = pane.getTabPlacement();
2232                 if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2233                     ui.navigateSelectedTab(SwingConstants.EAST);
2234                 } else {
2235                     ui.navigateSelectedTab(SwingConstants.SOUTH);
2236                 }
2237             }
2238             else if (key == REQUEST_FOCUS) {
2239                 pane.requestFocus();
2240             }
2241             else if (key == REQUEST_FOCUS_FOR_VISIBLE) {
2242                 ui.requestFocusForVisibleComponent();
2243             }
2244             else if (key == SET_SELECTED) {
2245                 String command = e.getActionCommand();
2246 
2247                 if (command != null && command.length() > 0) {
2248                     int mnemonic = (int)e.getActionCommand().charAt(0);
2249                     if (mnemonic >= 'a' && mnemonic <='z') {
2250                         mnemonic  -= ('a' - 'A');
2251                     }
2252                     Integer index = ui.mnemonicToIndexMap.get(Integer.valueOf(mnemonic));
2253                     if (index != null && pane.isEnabledAt(index.intValue())) {
2254                         pane.setSelectedIndex(index.intValue());
2255                     }
2256                 }
2257             }
2258             else if (key == SELECT_FOCUSED) {
2259                 int focusIndex = ui.getFocusIndex();
2260                 if (focusIndex != -1) {
2261                     pane.setSelectedIndex(focusIndex);
2262                 }
2263             }
2264             else if (key == SCROLL_FORWARD) {
2265                 if (ui.scrollableTabLayoutEnabled()) {
2266                     ui.tabScroller.scrollForward(pane.getTabPlacement());
2267                 }
2268             }
2269             else if (key == SCROLL_BACKWARD) {
2270                 if (ui.scrollableTabLayoutEnabled()) {
2271                     ui.tabScroller.scrollBackward(pane.getTabPlacement());
2272                 }
2273             }
2274         }
2275     }
2276 
2277     /**
2278      * This class should be treated as a &quot;protected&quot; inner class.
2279      * Instantiate it only within subclasses of BasicTabbedPaneUI.
2280      */
2281     public class TabbedPaneLayout implements LayoutManager {
2282 
2283         public void addLayoutComponent(String name, Component comp) {}
2284 
2285         public void removeLayoutComponent(Component comp) {}
2286 
2287         public Dimension preferredLayoutSize(Container parent) {
2288             return calculateSize(false);
2289         }
2290 
2291         public Dimension minimumLayoutSize(Container parent) {
2292             return calculateSize(true);
2293         }
2294 
2295         protected Dimension calculateSize(boolean minimum) {
2296             int tabPlacement = tabPane.getTabPlacement();
2297             Insets insets = tabPane.getInsets();
2298             Insets contentInsets = getContentBorderInsets(tabPlacement);
2299             Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2300 
2301             Dimension zeroSize = new Dimension(0,0);
2302             int height = 0;
2303             int width = 0;
2304             int cWidth = 0;
2305             int cHeight = 0;
2306 
2307             // Determine minimum size required to display largest
2308             // child in each dimension
2309             //
2310             for (int i = 0; i < tabPane.getTabCount(); i++) {
2311                 Component component = tabPane.getComponentAt(i);
2312                 if (component != null) {
2313                     Dimension size = minimum ? component.getMinimumSize() :
2314                                 component.getPreferredSize();
2315 
2316                     if (size != null) {
2317                         cHeight = Math.max(size.height, cHeight);
2318                         cWidth = Math.max(size.width, cWidth);
2319                     }
2320                 }
2321             }
2322             // Add content border insets to minimum size
2323             width += cWidth;
2324             height += cHeight;
2325             int tabExtent;
2326 
2327             // Calculate how much space the tabs will need, based on the
2328             // minimum size required to display largest child + content border
2329             //
2330             switch(tabPlacement) {
2331               case LEFT:
2332               case RIGHT:
2333                   height = Math.max(height, calculateMaxTabHeight(tabPlacement));
2334                   tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
2335                   width += tabExtent;
2336                   break;
2337               case TOP:
2338               case BOTTOM:
2339               default:
2340                   width = Math.max(width, calculateMaxTabWidth(tabPlacement));
2341                   tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
2342                   height += tabExtent;
2343             }
2344             return new Dimension(width + insets.left + insets.right + contentInsets.left + contentInsets.right,
2345                              height + insets.bottom + insets.top + contentInsets.top + contentInsets.bottom);
2346 
2347         }
2348 
2349         protected int preferredTabAreaHeight(int tabPlacement, int width) {
2350             FontMetrics metrics = getFontMetrics();
2351             int tabCount = tabPane.getTabCount();
2352             int total = 0;
2353             if (tabCount > 0) {
2354                 int rows = 1;
2355                 int x = 0;
2356 
2357                 int maxTabHeight = calculateMaxTabHeight(tabPlacement);
2358 
2359                 for (int i = 0; i < tabCount; i++) {
2360                     int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
2361 
2362                     if (x != 0 && x + tabWidth > width) {
2363                         rows++;
2364                         x = 0;
2365                     }
2366                     x += tabWidth;
2367                 }
2368                 total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
2369             }
2370             return total;
2371         }
2372 
2373         protected int preferredTabAreaWidth(int tabPlacement, int height) {
2374             FontMetrics metrics = getFontMetrics();
2375             int tabCount = tabPane.getTabCount();
2376             int total = 0;
2377             if (tabCount > 0) {
2378                 int columns = 1;
2379                 int y = 0;
2380                 int fontHeight = metrics.getHeight();
2381 
2382                 maxTabWidth = calculateMaxTabWidth(tabPlacement);
2383 
2384                 for (int i = 0; i < tabCount; i++) {
2385                     int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
2386 
2387                     if (y != 0 && y + tabHeight > height) {
2388                         columns++;
2389                         y = 0;
2390                     }
2391                     y += tabHeight;
2392                 }
2393                 total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
2394             }
2395             return total;
2396         }
2397 
2398         public void layoutContainer(Container parent) {
2399             /* Some of the code in this method deals with changing the
2400             * visibility of components to hide and show the contents for the
2401             * selected tab. This is older code that has since been duplicated
2402             * in JTabbedPane.fireStateChanged(), so as to allow visibility
2403             * changes to happen sooner (see the note there). This code remains
2404             * for backward compatibility as there are some cases, such as
2405             * subclasses that don't fireStateChanged() where it may be used.
2406             * Any changes here need to be kept in synch with
2407             * JTabbedPane.fireStateChanged().
2408             */
2409 
2410             setRolloverTab(-1);
2411 
2412             int tabPlacement = tabPane.getTabPlacement();
2413             Insets insets = tabPane.getInsets();
2414             int selectedIndex = tabPane.getSelectedIndex();
2415             Component visibleComponent = getVisibleComponent();
2416 
2417             calculateLayoutInfo();
2418 
2419             Component selectedComponent = null;
2420             if (selectedIndex < 0) {
2421                 if (visibleComponent != null) {
2422                     // The last tab was removed, so remove the component
2423                     setVisibleComponent(null);
2424                 }
2425             } else {
2426                 selectedComponent = tabPane.getComponentAt(selectedIndex);
2427             }
2428             int cx, cy, cw, ch;
2429             int totalTabWidth = 0;
2430             int totalTabHeight = 0;
2431             Insets contentInsets = getContentBorderInsets(tabPlacement);
2432 
2433             boolean shouldChangeFocus = false;
2434 
2435             // In order to allow programs to use a single component
2436             // as the display for multiple tabs, we will not change
2437             // the visible compnent if the currently selected tab
2438             // has a null component.  This is a bit dicey, as we don't
2439             // explicitly state we support this in the spec, but since
2440             // programs are now depending on this, we're making it work.
2441             //
2442             if(selectedComponent != null) {
2443                 if(selectedComponent != visibleComponent &&
2444                         visibleComponent != null) {
2445                     if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
2446                         shouldChangeFocus = true;
2447                     }
2448                 }
2449                 setVisibleComponent(selectedComponent);
2450             }
2451 
2452             Rectangle bounds = tabPane.getBounds();
2453             int numChildren = tabPane.getComponentCount();
2454 
2455             if(numChildren > 0) {
2456 
2457                 switch(tabPlacement) {
2458                     case LEFT:
2459                         totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2460                         cx = insets.left + totalTabWidth + contentInsets.left;
2461                         cy = insets.top + contentInsets.top;
2462                         break;
2463                     case RIGHT:
2464                         totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2465                         cx = insets.left + contentInsets.left;
2466                         cy = insets.top + contentInsets.top;
2467                         break;
2468                     case BOTTOM:
2469                         totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2470                         cx = insets.left + contentInsets.left;
2471                         cy = insets.top + contentInsets.top;
2472                         break;
2473                     case TOP:
2474                     default:
2475                         totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2476                         cx = insets.left + contentInsets.left;
2477                         cy = insets.top + totalTabHeight + contentInsets.top;
2478                 }
2479 
2480                 cw = bounds.width - totalTabWidth -
2481                         insets.left - insets.right -
2482                         contentInsets.left - contentInsets.right;
2483                 ch = bounds.height - totalTabHeight -
2484                         insets.top - insets.bottom -
2485                         contentInsets.top - contentInsets.bottom;
2486 
2487                 for(int i = 0; i < numChildren; i++) {
2488                     Component child = tabPane.getComponent(i);
2489                     if(child == tabContainer) {
2490 
2491                         int tabContainerWidth = totalTabWidth == 0 ? bounds.width :
2492                                 totalTabWidth + insets.left + insets.right +
2493                                         contentInsets.left + contentInsets.right;
2494                         int tabContainerHeight = totalTabHeight == 0 ? bounds.height :
2495                                 totalTabHeight + insets.top + insets.bottom +
2496                                         contentInsets.top + contentInsets.bottom;
2497 
2498                         int tabContainerX = 0;
2499                         int tabContainerY = 0;
2500                         if(tabPlacement == BOTTOM) {
2501                             tabContainerY = bounds.height - tabContainerHeight;
2502                         } else if(tabPlacement == RIGHT) {
2503                             tabContainerX = bounds.width - tabContainerWidth;
2504                         }
2505                         child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
2506                     } else {
2507                         child.setBounds(cx, cy, cw, ch);
2508                     }
2509                 }
2510             }
2511             layoutTabComponents();
2512             if(shouldChangeFocus) {
2513                 if(!requestFocusForVisibleComponent()) {
2514                     tabPane.requestFocus();
2515                 }
2516             }
2517         }
2518 
2519         public void calculateLayoutInfo() {
2520             int tabCount = tabPane.getTabCount();
2521             assureRectsCreated(tabCount);
2522             calculateTabRects(tabPane.getTabPlacement(), tabCount);
2523             isRunsDirty = false;
2524         }
2525 
2526         private void layoutTabComponents() {
2527             if (tabContainer == null) {
2528                 return;
2529             }
2530             Rectangle rect = new Rectangle();
2531             Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
2532             if (scrollableTabLayoutEnabled()) {
2533                 translatePointToTabPanel(0, 0, delta);
2534             }
2535             for (int i = 0; i < tabPane.getTabCount(); i++) {
2536                 Component c = tabPane.getTabComponentAt(i);
2537                 if (c == null) {
2538                     continue;
2539                 }
2540                 getTabBounds(i, rect);
2541                 Dimension preferredSize = c.getPreferredSize();
2542                 Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
2543                 int outerX = rect.x + insets.left + delta.x;
2544                 int outerY = rect.y + insets.top + delta.y;
2545                 int outerWidth = rect.width - insets.left - insets.right;
2546                 int outerHeight = rect.height - insets.top - insets.bottom;
2547                 //centralize component
2548                 int x = outerX + (outerWidth - preferredSize.width) / 2;
2549                 int y = outerY + (outerHeight - preferredSize.height) / 2;
2550                 int tabPlacement = tabPane.getTabPlacement();
2551                 boolean isSeleceted = i == tabPane.getSelectedIndex();
2552                 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted),
2553                             y + getTabLabelShiftY(tabPlacement, i, isSeleceted),
2554                         preferredSize.width, preferredSize.height);
2555             }
2556         }
2557 
2558         protected void calculateTabRects(int tabPlacement, int tabCount) {
2559             FontMetrics metrics = getFontMetrics();
2560             Dimension size = tabPane.getSize();
2561             Insets insets = tabPane.getInsets();
2562             Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2563             int fontHeight = metrics.getHeight();
2564             int selectedIndex = tabPane.getSelectedIndex();
2565             int tabRunOverlay;
2566             int i, j;
2567             int x, y;
2568             int returnAt;
2569             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2570             boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
2571 
2572             //
2573             // Calculate bounds within which a tab run must fit
2574             //
2575             switch(tabPlacement) {
2576               case LEFT:
2577                   maxTabWidth = calculateMaxTabWidth(tabPlacement);
2578                   x = insets.left + tabAreaInsets.left;
2579                   y = insets.top + tabAreaInsets.top;
2580                   returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2581                   break;
2582               case RIGHT:
2583                   maxTabWidth = calculateMaxTabWidth(tabPlacement);
2584                   x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
2585                   y = insets.top + tabAreaInsets.top;
2586                   returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2587                   break;
2588               case BOTTOM:
2589                   maxTabHeight = calculateMaxTabHeight(tabPlacement);
2590                   x = insets.left + tabAreaInsets.left;
2591                   y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
2592                   returnAt = size.width - (insets.right + tabAreaInsets.right);
2593                   break;
2594               case TOP:
2595               default:
2596                   maxTabHeight = calculateMaxTabHeight(tabPlacement);
2597                   x = insets.left + tabAreaInsets.left;
2598                   y = insets.top + tabAreaInsets.top;
2599                   returnAt = size.width - (insets.right + tabAreaInsets.right);
2600                   break;
2601             }
2602 
2603             tabRunOverlay = getTabRunOverlay(tabPlacement);
2604 
2605             runCount = 0;
2606             selectedRun = -1;
2607 
2608             if (tabCount == 0) {
2609                 return;
2610             }
2611 
2612             // Run through tabs and partition them into runs
2613             Rectangle rect;
2614             for (i = 0; i < tabCount; i++) {
2615                 rect = rects[i];
2616 
2617                 if (!verticalTabRuns) {
2618                     // Tabs on TOP or BOTTOM....
2619                     if (i > 0) {
2620                         rect.x = rects[i-1].x + rects[i-1].width;
2621                     } else {
2622                         tabRuns[0] = 0;
2623                         runCount = 1;
2624                         maxTabWidth = 0;
2625                         rect.x = x;
2626                     }
2627                     rect.width = calculateTabWidth(tabPlacement, i, metrics);
2628                     maxTabWidth = Math.max(maxTabWidth, rect.width);
2629 
2630                     // Never move a TAB down a run if it is in the first column.
2631                     // Even if there isn't enough room, moving it to a fresh
2632                     // line won't help.
2633                     if (rect.x != x && rect.x + rect.width > returnAt) {
2634                         if (runCount > tabRuns.length - 1) {
2635                             expandTabRunsArray();
2636                         }
2637                         tabRuns[runCount] = i;
2638                         runCount++;
2639                         rect.x = x;
2640                     }
2641                     // Initialize y position in case there's just one run
2642                     rect.y = y;
2643                     rect.height = maxTabHeight/* - 2*/;
2644 
2645                 } else {
2646                     // Tabs on LEFT or RIGHT...
2647                     if (i > 0) {
2648                         rect.y = rects[i-1].y + rects[i-1].height;
2649                     } else {
2650                         tabRuns[0] = 0;
2651                         runCount = 1;
2652                         maxTabHeight = 0;
2653                         rect.y = y;
2654                     }
2655                     rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
2656                     maxTabHeight = Math.max(maxTabHeight, rect.height);
2657 
2658                     // Never move a TAB over a run if it is in the first run.
2659                     // Even if there isn't enough room, moving it to a fresh
2660                     // column won't help.
2661                     if (rect.y != y && rect.y + rect.height > returnAt) {
2662                         if (runCount > tabRuns.length - 1) {
2663                             expandTabRunsArray();
2664                         }
2665                         tabRuns[runCount] = i;
2666                         runCount++;
2667                         rect.y = y;
2668                     }
2669                     // Initialize x position in case there's just one column
2670                     rect.x = x;
2671                     rect.width = maxTabWidth/* - 2*/;
2672 
2673                 }
2674                 if (i == selectedIndex) {
2675                     selectedRun = runCount - 1;
2676                 }
2677             }
2678 
2679             if (runCount > 1) {
2680                 // Re-distribute tabs in case last run has leftover space
2681                 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
2682 
2683                 selectedRun = getRunForTab(tabCount, selectedIndex);
2684 
2685                 // Rotate run array so that selected run is first
2686                 if (shouldRotateTabRuns(tabPlacement)) {
2687                     rotateTabRuns(tabPlacement, selectedRun);
2688                 }
2689             }
2690 
2691             // Step through runs from back to front to calculate
2692             // tab y locations and to pad runs appropriately
2693             for (i = runCount - 1; i >= 0; i--) {
2694                 int start = tabRuns[i];
2695                 int next = tabRuns[i == (runCount - 1)? 0 : i + 1];
2696                 int end = (next != 0? next - 1 : tabCount - 1);
2697                 if (!verticalTabRuns) {
2698                     for (j = start; j <= end; j++) {
2699                         rect = rects[j];
2700                         rect.y = y;
2701                         rect.x += getTabRunIndent(tabPlacement, i);
2702                     }
2703                     if (shouldPadTabRun(tabPlacement, i)) {
2704                         padTabRun(tabPlacement, start, end, returnAt);
2705                     }
2706                     if (tabPlacement == BOTTOM) {
2707                         y -= (maxTabHeight - tabRunOverlay);
2708                     } else {
2709                         y += (maxTabHeight - tabRunOverlay);
2710                     }
2711                 } else {
2712                     for (j = start; j <= end; j++) {
2713                         rect = rects[j];
2714                         rect.x = x;
2715                         rect.y += getTabRunIndent(tabPlacement, i);
2716                     }
2717                     if (shouldPadTabRun(tabPlacement, i)) {
2718                         padTabRun(tabPlacement, start, end, returnAt);
2719                     }
2720                     if (tabPlacement == RIGHT) {
2721                         x -= (maxTabWidth - tabRunOverlay);
2722                     } else {
2723                         x += (maxTabWidth - tabRunOverlay);
2724                     }
2725                 }
2726             }
2727 
2728             // Pad the selected tab so that it appears raised in front
2729             padSelectedTab(tabPlacement, selectedIndex);
2730 
2731             // if right to left and tab placement on the top or
2732             // the bottom, flip x positions and adjust by widths
2733             if (!leftToRight && !verticalTabRuns) {
2734                 int rightMargin = size.width
2735                                   - (insets.right + tabAreaInsets.right);
2736                 for (i = 0; i < tabCount; i++) {
2737                     rects[i].x = rightMargin - rects[i].x - rects[i].width;
2738                 }
2739             }
2740         }
2741 
2742 
2743        /*
2744        * Rotates the run-index array so that the selected run is run[0]
2745        */
2746         protected void rotateTabRuns(int tabPlacement, int selectedRun) {
2747             for (int i = 0; i < selectedRun; i++) {
2748                 int save = tabRuns[0];
2749                 for (int j = 1; j < runCount; j++) {
2750                     tabRuns[j - 1] = tabRuns[j];
2751                 }
2752                 tabRuns[runCount-1] = save;
2753             }
2754         }
2755 
2756         protected void normalizeTabRuns(int tabPlacement, int tabCount,
2757                                      int start, int max) {
2758             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2759             int run = runCount - 1;
2760             boolean keepAdjusting = true;
2761             double weight = 1.25;
2762 
2763             // At this point the tab runs are packed to fit as many
2764             // tabs as possible, which can leave the last run with a lot
2765             // of extra space (resulting in very fat tabs on the last run).
2766             // So we'll attempt to distribute this extra space more evenly
2767             // across the runs in order to make the runs look more consistent.
2768             //
2769             // Starting with the last run, determine whether the last tab in
2770             // the previous run would fit (generously) in this run; if so,
2771             // move tab to current run and shift tabs accordingly.  Cycle
2772             // through remaining runs using the same algorithm.
2773             //
2774             while (keepAdjusting) {
2775                 int last = lastTabInRun(tabCount, run);
2776                 int prevLast = lastTabInRun(tabCount, run-1);
2777                 int end;
2778                 int prevLastLen;
2779 
2780                 if (!verticalTabRuns) {
2781                     end = rects[last].x + rects[last].width;
2782                     prevLastLen = (int)(maxTabWidth*weight);
2783                 } else {
2784                     end = rects[last].y + rects[last].height;
2785                     prevLastLen = (int)(maxTabHeight*weight*2);
2786                 }
2787 
2788                 // Check if the run has enough extra space to fit the last tab
2789                 // from the previous row...
2790                 if (max - end > prevLastLen) {
2791 
2792                     // Insert tab from previous row and shift rest over
2793                     tabRuns[run] = prevLast;
2794                     if (!verticalTabRuns) {
2795                         rects[prevLast].x = start;
2796                     } else {
2797                         rects[prevLast].y = start;
2798                     }
2799                     for (int i = prevLast+1; i <= last; i++) {
2800                         if (!verticalTabRuns) {
2801                             rects[i].x = rects[i-1].x + rects[i-1].width;
2802                         } else {
2803                             rects[i].y = rects[i-1].y + rects[i-1].height;
2804                         }
2805                     }
2806 
2807                 } else if (run == runCount - 1) {
2808                     // no more room left in last run, so we're done!
2809                     keepAdjusting = false;
2810                 }
2811                 if (run - 1 > 0) {
2812                     // check previous run next...
2813                     run -= 1;
2814                 } else {
2815                     // check last run again...but require a higher ratio
2816                     // of extraspace-to-tabsize because we don't want to
2817                     // end up with too many tabs on the last run!
2818                     run = runCount - 1;
2819                     weight += .25;
2820                 }
2821             }
2822         }
2823 
2824         protected void padTabRun(int tabPlacement, int start, int end, int max) {
2825             Rectangle lastRect = rects[end];
2826             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2827                 int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
2828                 int deltaWidth = max - (lastRect.x + lastRect.width);
2829                 float factor = (float)deltaWidth / (float)runWidth;
2830 
2831                 for (int j = start; j <= end; j++) {
2832                     Rectangle pastRect = rects[j];
2833                     if (j > start) {
2834                         pastRect.x = rects[j-1].x + rects[j-1].width;
2835                     }
2836                     pastRect.width += Math.round((float)pastRect.width * factor);
2837                 }
2838                 lastRect.width = max - lastRect.x;
2839             } else {
2840                 int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
2841                 int deltaHeight = max - (lastRect.y + lastRect.height);
2842                 float factor = (float)deltaHeight / (float)runHeight;
2843 
2844                 for (int j = start; j <= end; j++) {
2845                     Rectangle pastRect = rects[j];
2846                     if (j > start) {
2847                         pastRect.y = rects[j-1].y + rects[j-1].height;
2848                     }
2849                     pastRect.height += Math.round((float)pastRect.height * factor);
2850                 }
2851                 lastRect.height = max - lastRect.y;
2852             }
2853         }
2854 
2855         protected void padSelectedTab(int tabPlacement, int selectedIndex) {
2856 
2857             if (selectedIndex >= 0) {
2858                 Rectangle selRect = rects[selectedIndex];
2859                 Insets padInsets = getSelectedTabPadInsets(tabPlacement);
2860                 selRect.x -= padInsets.left;
2861                 selRect.width += (padInsets.left + padInsets.right);
2862                 selRect.y -= padInsets.top;
2863                 selRect.height += (padInsets.top + padInsets.bottom);
2864 
2865                 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
2866                     // do not expand selected tab more then necessary
2867                     Dimension size = tabPane.getSize();
2868                     Insets insets = tabPane.getInsets();
2869 
2870                     if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) {
2871                         int top = insets.top - selRect.y;
2872                         if (top > 0) {
2873                             selRect.y += top;
2874                             selRect.height -= top;
2875                         }
2876                         int bottom = (selRect.y + selRect.height) + insets.bottom - size.height;
2877                         if (bottom > 0) {
2878                             selRect.height -= bottom;
2879                         }
2880                     } else {
2881                         int left = insets.left - selRect.x;
2882                         if (left > 0) {
2883                             selRect.x += left;
2884                             selRect.width -= left;
2885                         }
2886                         int right = (selRect.x + selRect.width) + insets.right - size.width;
2887                         if (right > 0) {
2888                             selRect.width -= right;
2889                         }
2890                     }
2891                 }
2892             }
2893         }
2894     }
2895 
2896     private class TabbedPaneScrollLayout extends TabbedPaneLayout {
2897 
2898         protected int preferredTabAreaHeight(int tabPlacement, int width) {
2899             return calculateMaxTabHeight(tabPlacement);
2900         }
2901 
2902         protected int preferredTabAreaWidth(int tabPlacement, int height) {
2903             return calculateMaxTabWidth(tabPlacement);
2904         }
2905 
2906         public void layoutContainer(Container parent) {
2907             /* Some of the code in this method deals with changing the
2908              * visibility of components to hide and show the contents for the
2909              * selected tab. This is older code that has since been duplicated
2910              * in JTabbedPane.fireStateChanged(), so as to allow visibility
2911              * changes to happen sooner (see the note there). This code remains
2912              * for backward compatibility as there are some cases, such as
2913              * subclasses that don't fireStateChanged() where it may be used.
2914              * Any changes here need to be kept in synch with
2915              * JTabbedPane.fireStateChanged().
2916              */
2917 
2918             setRolloverTab(-1);
2919 
2920             int tabPlacement = tabPane.getTabPlacement();
2921             int tabCount = tabPane.getTabCount();
2922             Insets insets = tabPane.getInsets();
2923             int selectedIndex = tabPane.getSelectedIndex();
2924             Component visibleComponent = getVisibleComponent();
2925 
2926             calculateLayoutInfo();
2927 
2928             Component selectedComponent = null;
2929             if (selectedIndex < 0) {
2930                 if (visibleComponent != null) {
2931                     // The last tab was removed, so remove the component
2932                     setVisibleComponent(null);
2933                 }
2934             } else {
2935                 selectedComponent = tabPane.getComponentAt(selectedIndex);
2936             }
2937 
2938             if (tabPane.getTabCount() == 0) {
2939                 tabScroller.croppedEdge.resetParams();
2940                 tabScroller.scrollForwardButton.setVisible(false);
2941                 tabScroller.scrollBackwardButton.setVisible(false);
2942                 return;
2943             }
2944 
2945             boolean shouldChangeFocus = false;
2946 
2947             // In order to allow programs to use a single component
2948             // as the display for multiple tabs, we will not change
2949             // the visible compnent if the currently selected tab
2950             // has a null component.  This is a bit dicey, as we don't
2951             // explicitly state we support this in the spec, but since
2952             // programs are now depending on this, we're making it work.
2953             //
2954             if(selectedComponent != null) {
2955                 if(selectedComponent != visibleComponent &&
2956                         visibleComponent != null) {
2957                     if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
2958                         shouldChangeFocus = true;
2959                     }
2960                 }
2961                 setVisibleComponent(selectedComponent);
2962             }
2963             int tx, ty, tw, th; // tab area bounds
2964             int cx, cy, cw, ch; // content area bounds
2965             Insets contentInsets = getContentBorderInsets(tabPlacement);
2966             Rectangle bounds = tabPane.getBounds();
2967             int numChildren = tabPane.getComponentCount();
2968 
2969             if(numChildren > 0) {
2970                 switch(tabPlacement) {
2971                     case LEFT:
2972                         // calculate tab area bounds
2973                         tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2974                         th = bounds.height - insets.top - insets.bottom;
2975                         tx = insets.left;
2976                         ty = insets.top;
2977 
2978                         // calculate content area bounds
2979                         cx = tx + tw + contentInsets.left;
2980                         cy = ty + contentInsets.top;
2981                         cw = bounds.width - insets.left - insets.right - tw -
2982                                 contentInsets.left - contentInsets.right;
2983                         ch = bounds.height - insets.top - insets.bottom -
2984                                 contentInsets.top - contentInsets.bottom;
2985                         break;
2986                     case RIGHT:
2987                         // calculate tab area bounds
2988                         tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2989                         th = bounds.height - insets.top - insets.bottom;
2990                         tx = bounds.width - insets.right - tw;
2991                         ty = insets.top;
2992 
2993                         // calculate content area bounds
2994                         cx = insets.left + contentInsets.left;
2995                         cy = insets.top + contentInsets.top;
2996                         cw = bounds.width - insets.left - insets.right - tw -
2997                                 contentInsets.left - contentInsets.right;
2998                         ch = bounds.height - insets.top - insets.bottom -
2999                                 contentInsets.top - contentInsets.bottom;
3000                         break;
3001                     case BOTTOM:
3002                         // calculate tab area bounds
3003                         tw = bounds.width - insets.left - insets.right;
3004                         th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
3005                         tx = insets.left;
3006                         ty = bounds.height - insets.bottom - th;
3007 
3008                         // calculate content area bounds
3009                         cx = insets.left + contentInsets.left;
3010                         cy = insets.top + contentInsets.top;
3011                         cw = bounds.width - insets.left - insets.right -
3012                                 contentInsets.left - contentInsets.right;
3013                         ch = bounds.height - insets.top - insets.bottom - th -
3014                                 contentInsets.top - contentInsets.bottom;
3015                         break;
3016                     case TOP:
3017                     default:
3018                         // calculate tab area bounds
3019                         tw = bounds.width - insets.left - insets.right;
3020                         th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
3021                         tx = insets.left;
3022                         ty = insets.top;
3023 
3024                         // calculate content area bounds
3025                         cx = tx + contentInsets.left;
3026                         cy = ty + th + contentInsets.top;
3027                         cw = bounds.width - insets.left - insets.right -
3028                                 contentInsets.left - contentInsets.right;
3029                         ch = bounds.height - insets.top - insets.bottom - th -
3030                                 contentInsets.top - contentInsets.bottom;
3031                 }
3032 
3033                 for(int i = 0; i < numChildren; i++) {
3034                     Component child = tabPane.getComponent(i);
3035 
3036                     if(tabScroller != null && child == tabScroller.viewport) {
3037                         JViewport viewport = (JViewport) child;
3038                         Rectangle viewRect = viewport.getViewRect();
3039                         int vw = tw;
3040                         int vh = th;
3041                         Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
3042                         switch(tabPlacement) {
3043                             case LEFT:
3044                             case RIGHT:
3045                                 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3046                                 if(totalTabHeight > th) {
3047                                     // Allow space for scrollbuttons
3048                                     vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0;
3049                                     if(totalTabHeight - viewRect.y <= vh) {
3050                                         // Scrolled to the end, so ensure the viewport size is
3051                                         // such that the scroll offset aligns with a tab
3052                                         vh = totalTabHeight - viewRect.y;
3053                                     }
3054                                 }
3055                                 break;
3056                             case BOTTOM:
3057                             case TOP:
3058                             default:
3059                                 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3060                                 if(totalTabWidth > tw) {
3061                                     // Need to allow space for scrollbuttons
3062                                     vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0;
3063                                     if(totalTabWidth - viewRect.x <= vw) {
3064                                         // Scrolled to the end, so ensure the viewport size is
3065                                         // such that the scroll offset aligns with a tab
3066                                         vw = totalTabWidth - viewRect.x;
3067                                     }
3068                                 }
3069                         }
3070                         child.setBounds(tx, ty, vw, vh);
3071 
3072                     } else if(tabScroller != null &&
3073                             (child == tabScroller.scrollForwardButton ||
3074                             child == tabScroller.scrollBackwardButton)) {
3075                         Component scrollbutton = child;
3076                         Dimension bsize = scrollbutton.getPreferredSize();
3077                         int bx = 0;
3078                         int by = 0;
3079                         int bw = bsize.width;
3080                         int bh = bsize.height;
3081                         boolean visible = false;
3082 
3083                         switch(tabPlacement) {
3084                             case LEFT:
3085                             case RIGHT:
3086                                 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3087                                 if(totalTabHeight > th) {
3088                                     visible = true;
3089                                     bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx);
3090                                     by = (child == tabScroller.scrollForwardButton) ?
3091                                             bounds.height - insets.bottom - bsize.height :
3092                                             bounds.height - insets.bottom - 2 * bsize.height;
3093                                 }
3094                                 break;
3095 
3096                             case BOTTOM:
3097                             case TOP:
3098                             default:
3099                                 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3100 
3101                                 if(totalTabWidth > tw) {
3102                                     visible = true;
3103                                     bx = (child == tabScroller.scrollForwardButton) ?
3104                                             bounds.width - insets.left - bsize.width :
3105                                             bounds.width - insets.left - 2 * bsize.width;
3106                                     by = (tabPlacement == TOP ? ty + th - bsize.height : ty);
3107                                 }
3108                         }
3109                         child.setVisible(visible);
3110                         if(visible) {
3111                             child.setBounds(bx, by, bw, bh);
3112                         }
3113 
3114                     } else {
3115                         // All content children...
3116                         child.setBounds(cx, cy, cw, ch);
3117                     }
3118                 }
3119                 super.layoutTabComponents();
3120                 layoutCroppedEdge();
3121                 if(shouldChangeFocus) {
3122                     if(!requestFocusForVisibleComponent()) {
3123                         tabPane.requestFocus();
3124                     }
3125                 }
3126             }
3127         }
3128 
3129         private void layoutCroppedEdge() {
3130             tabScroller.croppedEdge.resetParams();
3131             Rectangle viewRect = tabScroller.viewport.getViewRect();
3132             int cropline;
3133             for (int i = 0; i < rects.length; i++) {
3134                 Rectangle tabRect = rects[i];
3135                 switch (tabPane.getTabPlacement()) {
3136                     case LEFT:
3137                     case RIGHT:
3138                         cropline = viewRect.y + viewRect.height;
3139                         if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
3140                             tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1,
3141                                     -currentTabAreaInsets.left,  0);
3142                         }
3143                         break;
3144                     case TOP:
3145                     case BOTTOM:
3146                     default:
3147                         cropline = viewRect.x + viewRect.width;
3148                         if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) {
3149                             tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1,
3150                                     0, -currentTabAreaInsets.top);
3151                         }
3152                 }
3153             }
3154         }
3155 
3156         protected void calculateTabRects(int tabPlacement, int tabCount) {
3157             FontMetrics metrics = getFontMetrics();
3158             Dimension size = tabPane.getSize();
3159             Insets insets = tabPane.getInsets();
3160             Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
3161             int fontHeight = metrics.getHeight();
3162             int selectedIndex = tabPane.getSelectedIndex();
3163             int i;
3164             boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
3165             boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
3166             int x = tabAreaInsets.left;
3167             int y = tabAreaInsets.top;
3168             int totalWidth = 0;
3169             int totalHeight = 0;
3170 
3171             //
3172             // Calculate bounds within which a tab run must fit
3173             //
3174             switch(tabPlacement) {
3175               case LEFT:
3176               case RIGHT:
3177                   maxTabWidth = calculateMaxTabWidth(tabPlacement);
3178                   break;
3179               case BOTTOM:
3180               case TOP:
3181               default:
3182                   maxTabHeight = calculateMaxTabHeight(tabPlacement);
3183             }
3184 
3185             runCount = 0;
3186             selectedRun = -1;
3187 
3188             if (tabCount == 0) {
3189                 return;
3190             }
3191 
3192             selectedRun = 0;
3193             runCount = 1;
3194 
3195             // Run through tabs and lay them out in a single run
3196             Rectangle rect;
3197             for (i = 0; i < tabCount; i++) {
3198                 rect = rects[i];
3199 
3200                 if (!verticalTabRuns) {
3201                     // Tabs on TOP or BOTTOM....
3202                     if (i > 0) {
3203                         rect.x = rects[i-1].x + rects[i-1].width;
3204                     } else {
3205                         tabRuns[0] = 0;
3206                         maxTabWidth = 0;
3207                         totalHeight += maxTabHeight;
3208                         rect.x = x;
3209                     }
3210                     rect.width = calculateTabWidth(tabPlacement, i, metrics);
3211                     totalWidth = rect.x + rect.width;
3212                     maxTabWidth = Math.max(maxTabWidth, rect.width);
3213 
3214                     rect.y = y;
3215                     rect.height = maxTabHeight/* - 2*/;
3216 
3217                 } else {
3218                     // Tabs on LEFT or RIGHT...
3219                     if (i > 0) {
3220                         rect.y = rects[i-1].y + rects[i-1].height;
3221                     } else {
3222                         tabRuns[0] = 0;
3223                         maxTabHeight = 0;
3224                         totalWidth = maxTabWidth;
3225                         rect.y = y;
3226                     }
3227                     rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3228                     totalHeight = rect.y + rect.height;
3229                     maxTabHeight = Math.max(maxTabHeight, rect.height);
3230 
3231                     rect.x = x;
3232                     rect.width = maxTabWidth/* - 2*/;
3233 
3234                 }
3235             }
3236 
3237             if (tabsOverlapBorder) {
3238                 // Pad the selected tab so that it appears raised in front
3239                 padSelectedTab(tabPlacement, selectedIndex);
3240             }
3241 
3242             // if right to left and tab placement on the top or
3243             // the bottom, flip x positions and adjust by widths
3244             if (!leftToRight && !verticalTabRuns) {
3245                 int rightMargin = size.width
3246                                   - (insets.right + tabAreaInsets.right);
3247                 for (i = 0; i < tabCount; i++) {
3248                     rects[i].x = rightMargin - rects[i].x - rects[i].width;
3249                 }
3250             }
3251             tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
3252             tabScroller.tabPanel.invalidate();
3253         }
3254     }
3255 
3256     private class ScrollableTabSupport implements ActionListener,
3257                             ChangeListener {
3258         public ScrollableTabViewport viewport;
3259         public ScrollableTabPanel tabPanel;
3260         public JButton scrollForwardButton;
3261         public JButton scrollBackwardButton;
3262         public CroppedEdge croppedEdge;
3263         public int leadingTabIndex;
3264 
3265         private Point tabViewPosition = new Point(0,0);
3266 
3267         ScrollableTabSupport(int tabPlacement) {
3268             viewport = new ScrollableTabViewport();
3269             tabPanel = new ScrollableTabPanel();
3270             viewport.setView(tabPanel);
3271             viewport.addChangeListener(this);
3272             croppedEdge = new CroppedEdge();
3273             createButtons();
3274         }
3275 
3276         /**
3277          * Recreates the scroll buttons and adds them to the TabbedPane.
3278          */
3279         void createButtons() {
3280             if (scrollForwardButton != null) {
3281                 tabPane.remove(scrollForwardButton);
3282                 scrollForwardButton.removeActionListener(this);
3283                 tabPane.remove(scrollBackwardButton);
3284                 scrollBackwardButton.removeActionListener(this);
3285             }
3286             int tabPlacement = tabPane.getTabPlacement();
3287             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3288                 scrollForwardButton = createScrollButton(EAST);
3289                 scrollBackwardButton = createScrollButton(WEST);
3290 
3291             } else { // tabPlacement = LEFT || RIGHT
3292                 scrollForwardButton = createScrollButton(SOUTH);
3293                 scrollBackwardButton = createScrollButton(NORTH);
3294             }
3295             scrollForwardButton.addActionListener(this);
3296             scrollBackwardButton.addActionListener(this);
3297             tabPane.add(scrollForwardButton);
3298             tabPane.add(scrollBackwardButton);
3299         }
3300 
3301         public void scrollForward(int tabPlacement) {
3302             Dimension viewSize = viewport.getViewSize();
3303             Rectangle viewRect = viewport.getViewRect();
3304 
3305             if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3306                 if (viewRect.width >= viewSize.width - viewRect.x) {
3307                     return; // no room left to scroll
3308                 }
3309             } else { // tabPlacement == LEFT || tabPlacement == RIGHT
3310                 if (viewRect.height >= viewSize.height - viewRect.y) {
3311                     return;
3312                 }
3313             }
3314             setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
3315         }
3316 
3317         public void scrollBackward(int tabPlacement) {
3318             if (leadingTabIndex == 0) {
3319                 return; // no room left to scroll
3320             }
3321             setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
3322         }
3323 
3324         public void setLeadingTabIndex(int tabPlacement, int index) {
3325             leadingTabIndex = index;
3326             Dimension viewSize = viewport.getViewSize();
3327             Rectangle viewRect = viewport.getViewRect();
3328 
3329             switch(tabPlacement) {
3330               case TOP:
3331               case BOTTOM:
3332                 tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x;
3333 
3334                 if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
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(viewSize.width - tabViewPosition.x,
3338                                                          viewRect.height);
3339                     viewport.setExtentSize(extentSize);
3340                 }
3341                 break;
3342               case LEFT:
3343               case RIGHT:
3344                 tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
3345 
3346                 if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
3347                 // We've scrolled to the end, so adjust the viewport size
3348                 // to ensure the view position remains aligned on a tab boundary
3349                      Dimension extentSize = new Dimension(viewRect.width,
3350                                                           viewSize.height - tabViewPosition.y);
3351                      viewport.setExtentSize(extentSize);
3352                 }
3353             }
3354             viewport.setViewPosition(tabViewPosition);
3355         }
3356 
3357         public void stateChanged(ChangeEvent e) {
3358             updateView();
3359         }
3360 
3361         private void updateView() {
3362             int tabPlacement = tabPane.getTabPlacement();
3363             int tabCount = tabPane.getTabCount();
3364             assureRectsCreated(tabCount);
3365             Rectangle vpRect = viewport.getBounds();
3366             Dimension viewSize = viewport.getViewSize();
3367             Rectangle viewRect = viewport.getViewRect();
3368 
3369             leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
3370 
3371             // If the tab isn't right aligned, adjust it.
3372             if (leadingTabIndex + 1 < tabCount) {
3373                 switch (tabPlacement) {
3374                 case TOP:
3375                 case BOTTOM:
3376                     if (rects[leadingTabIndex].x < viewRect.x) {
3377                         leadingTabIndex++;
3378                     }
3379                     break;
3380                 case LEFT:
3381                 case RIGHT:
3382                     if (rects[leadingTabIndex].y < viewRect.y) {
3383                         leadingTabIndex++;
3384                     }
3385                     break;
3386                 }
3387             }
3388             Insets contentInsets = getContentBorderInsets(tabPlacement);
3389             switch(tabPlacement) {
3390               case LEFT:
3391                   tabPane.repaint(vpRect.x+vpRect.width, vpRect.y,
3392                                   contentInsets.left, vpRect.height);
3393                   scrollBackwardButton.setEnabled(
3394                           viewRect.y > 0 && leadingTabIndex > 0);
3395                   scrollForwardButton.setEnabled(
3396                           leadingTabIndex < tabCount-1 &&
3397                           viewSize.height-viewRect.y > viewRect.height);
3398                   break;
3399               case RIGHT:
3400                   tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y,
3401                                   contentInsets.right, vpRect.height);
3402                   scrollBackwardButton.setEnabled(
3403                           viewRect.y > 0 && leadingTabIndex > 0);
3404                   scrollForwardButton.setEnabled(
3405                           leadingTabIndex < tabCount-1 &&
3406                           viewSize.height-viewRect.y > viewRect.height);
3407                   break;
3408               case BOTTOM:
3409                   tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom,
3410                                   vpRect.width, contentInsets.bottom);
3411                   scrollBackwardButton.setEnabled(
3412                           viewRect.x > 0 && leadingTabIndex > 0);
3413                   scrollForwardButton.setEnabled(
3414                           leadingTabIndex < tabCount-1 &&
3415                           viewSize.width-viewRect.x > viewRect.width);
3416                   break;
3417               case TOP:
3418               default:
3419                   tabPane.repaint(vpRect.x, vpRect.y+vpRect.height,
3420                                   vpRect.width, contentInsets.top);
3421                   scrollBackwardButton.setEnabled(
3422                           viewRect.x > 0 && leadingTabIndex > 0);
3423                   scrollForwardButton.setEnabled(
3424                           leadingTabIndex < tabCount-1 &&
3425                           viewSize.width-viewRect.x > viewRect.width);
3426             }
3427         }
3428 
3429         /**
3430          * ActionListener for the scroll buttons.
3431          */
3432         public void actionPerformed(ActionEvent e) {
3433             ActionMap map = tabPane.getActionMap();
3434 
3435             if (map != null) {
3436                 String actionKey;
3437 
3438                 if (e.getSource() == scrollForwardButton) {
3439                     actionKey = "scrollTabsForwardAction";
3440                 }
3441                 else {
3442                     actionKey = "scrollTabsBackwardAction";
3443                 }
3444                 Action action = map.get(actionKey);
3445 
3446                 if (action != null && action.isEnabled()) {
3447                     action.actionPerformed(new ActionEvent(tabPane,
3448                         ActionEvent.ACTION_PERFORMED, null, e.getWhen(),
3449                         e.getModifiers()));
3450                 }
3451             }
3452         }
3453 
3454         public String toString() {
3455             return "viewport.viewSize=" + viewport.getViewSize() + "\n" +
3456                               "viewport.viewRectangle="+viewport.getViewRect()+"\n"+
3457                               "leadingTabIndex="+leadingTabIndex+"\n"+
3458                               "tabViewPosition=" + tabViewPosition;
3459         }
3460 
3461     }
3462 
3463     @SuppressWarnings("serial") // Superclass is not serializable across versions
3464     private class ScrollableTabViewport extends JViewport implements UIResource {
3465         public ScrollableTabViewport() {
3466             super();
3467             setName("TabbedPane.scrollableViewport");
3468             setScrollMode(SIMPLE_SCROLL_MODE);
3469             setOpaque(tabPane.isOpaque());
3470             Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3471             if (bgColor == null) {
3472                 bgColor = tabPane.getBackground();
3473             }
3474             setBackground(bgColor);
3475         }
3476     }
3477 
3478     @SuppressWarnings("serial") // Superclass is not serializable across versions
3479     private class ScrollableTabPanel extends JPanel implements UIResource {
3480         public ScrollableTabPanel() {
3481             super(null);
3482             setOpaque(tabPane.isOpaque());
3483             Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3484             if (bgColor == null) {
3485                 bgColor = tabPane.getBackground();
3486             }
3487             setBackground(bgColor);
3488         }
3489         public void paintComponent(Graphics g) {
3490             super.paintComponent(g);
3491             BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(),
3492                                                 tabPane.getSelectedIndex());
3493             if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
3494                 Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
3495                 g.translate(croppedRect.x, croppedRect.y);
3496                 tabScroller.croppedEdge.paintComponent(g);
3497                 g.translate(-croppedRect.x, -croppedRect.y);
3498             }
3499         }
3500 
3501         public void doLayout() {
3502             if (getComponentCount() > 0) {
3503                 Component child = getComponent(0);
3504                 child.setBounds(0, 0, getWidth(), getHeight());
3505             }
3506         }
3507     }
3508 
3509     @SuppressWarnings("serial") // Superclass is not serializable across versions
3510     private class ScrollableTabButton extends BasicArrowButton implements UIResource,
3511                                                                             SwingConstants {
3512         public ScrollableTabButton(int direction) {
3513             super(direction,
3514                   UIManager.getColor("TabbedPane.selected"),
3515                   UIManager.getColor("TabbedPane.shadow"),
3516                   UIManager.getColor("TabbedPane.darkShadow"),
3517                   UIManager.getColor("TabbedPane.highlight"));
3518         }
3519     }
3520 
3521 
3522 // Controller: event listeners
3523 
3524     private class Handler implements ChangeListener, ContainerListener,
3525                   FocusListener, MouseListener, MouseMotionListener,
3526                   PropertyChangeListener {
3527         //
3528         // PropertyChangeListener
3529         //
3530         public void propertyChange(PropertyChangeEvent e) {
3531             JTabbedPane pane = (JTabbedPane)e.getSource();
3532             String name = e.getPropertyName();
3533             boolean isScrollLayout = scrollableTabLayoutEnabled();
3534             if (name == "mnemonicAt") {
3535                 updateMnemonics();
3536                 pane.repaint();
3537             }
3538             else if (name == "displayedMnemonicIndexAt") {
3539                 pane.repaint();
3540             }
3541             else if (name =="indexForTitle") {
3542                 calculatedBaseline = false;
3543                 Integer index = (Integer) e.getNewValue();
3544                 updateHtmlViews(index, false);
3545             } else if (name == "tabLayoutPolicy") {
3546                 BasicTabbedPaneUI.this.uninstallUI(pane);
3547                 BasicTabbedPaneUI.this.installUI(pane);
3548                 calculatedBaseline = false;
3549             } else if (name == "tabPlacement") {
3550                 if (scrollableTabLayoutEnabled()) {
3551                     tabScroller.createButtons();
3552                 }
3553                 calculatedBaseline = false;
3554             } else if (name == "opaque" && isScrollLayout) {
3555                 boolean newVal = ((Boolean)e.getNewValue()).booleanValue();
3556                 tabScroller.tabPanel.setOpaque(newVal);
3557                 tabScroller.viewport.setOpaque(newVal);
3558             } else if (name == "background" && isScrollLayout) {
3559                 Color newVal = (Color)e.getNewValue();
3560                 tabScroller.tabPanel.setBackground(newVal);
3561                 tabScroller.viewport.setBackground(newVal);
3562                 Color newColor = selectedColor == null ? newVal : selectedColor;
3563                 tabScroller.scrollForwardButton.setBackground(newColor);
3564                 tabScroller.scrollBackwardButton.setBackground(newColor);
3565             } else if (name == "indexForTabComponent") {
3566                 if (tabContainer != null) {
3567                     tabContainer.removeUnusedTabComponents();
3568                 }
3569                 Component c = tabPane.getTabComponentAt(
3570                         (Integer)e.getNewValue());
3571                 if (c != null) {
3572                     if (tabContainer == null) {
3573                         installTabContainer();
3574                     } else {
3575                         tabContainer.add(c);
3576                     }
3577                 }
3578                 tabPane.revalidate();
3579                 tabPane.repaint();
3580                 calculatedBaseline = false;
3581             } else if (name == "indexForNullComponent") {
3582                 isRunsDirty = true;
3583                 updateHtmlViews((Integer)e.getNewValue(), true);
3584             } else if (name == "font") {
3585                 calculatedBaseline = false;
3586             }
3587         }
3588 
3589         private void updateHtmlViews(int index, boolean inserted) {
3590             String title = tabPane.getTitleAt(index);
3591             boolean isHTML = BasicHTML.isHTMLString(title);
3592             if (isHTML) {
3593                 if (htmlViews==null) {    // Initialize vector
3594                     htmlViews = createHTMLVector();
3595                 } else {                  // Vector already exists
3596                     View v = BasicHTML.createHTMLView(tabPane, title);
3597                     setHtmlView(v, inserted, index);
3598                 }
3599             } else {                             // Not HTML
3600                 if (htmlViews!=null) {           // Add placeholder
3601                     setHtmlView(null, inserted, index);
3602                 }                                // else nada!
3603             }
3604             updateMnemonics();
3605         }
3606 
3607         private void setHtmlView(View v, boolean inserted, int index) {
3608             if (inserted || index >= htmlViews.size()) {
3609                 htmlViews.insertElementAt(v, index);
3610             } else {
3611                 htmlViews.setElementAt(v, index);
3612             }
3613         }
3614 
3615         //
3616         // ChangeListener
3617         //
3618         public void stateChanged(ChangeEvent e) {
3619             JTabbedPane tabPane = (JTabbedPane)e.getSource();
3620             tabPane.revalidate();
3621             tabPane.repaint();
3622 
3623             setFocusIndex(tabPane.getSelectedIndex(), false);
3624 
3625             if (scrollableTabLayoutEnabled()) {
3626                 ensureCurrentLayout();
3627                 int index = tabPane.getSelectedIndex();
3628                 if (index < rects.length && index != -1) {
3629                     tabScroller.tabPanel.scrollRectToVisible(
3630                             (Rectangle)rects[index].clone());
3631                 }
3632             }
3633         }
3634 
3635         //
3636         // MouseListener
3637         //
3638         public void mouseClicked(MouseEvent e) {
3639         }
3640 
3641         public void mouseReleased(MouseEvent e) {
3642         }
3643 
3644         public void mouseEntered(MouseEvent e) {
3645             setRolloverTab(e.getX(), e.getY());
3646         }
3647 
3648         public void mouseExited(MouseEvent e) {
3649             setRolloverTab(-1);
3650         }
3651 
3652         public void mousePressed(MouseEvent e) {
3653             if (!tabPane.isEnabled()) {
3654                 return;
3655             }
3656             int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
3657             if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
3658                 if (tabIndex != tabPane.getSelectedIndex()) {
3659                     // Clicking on unselected tab, change selection, do NOT
3660                     // request focus.
3661                     // This will trigger the focusIndex to change by way
3662                     // of stateChanged.
3663                     tabPane.setSelectedIndex(tabIndex);
3664                 }
3665                 else if (tabPane.isRequestFocusEnabled()) {
3666                     // Clicking on selected tab, try and give the tabbedpane
3667                     // focus.  Repaint will occur in focusGained.
3668                     tabPane.requestFocus();
3669                 }
3670             }
3671         }
3672 
3673         //
3674         // MouseMotionListener
3675         //
3676         public void mouseDragged(MouseEvent e) {
3677         }
3678 
3679         public void mouseMoved(MouseEvent e) {
3680             setRolloverTab(e.getX(), e.getY());
3681         }
3682 
3683         //
3684         // FocusListener
3685         //
3686         public void focusGained(FocusEvent e) {
3687            setFocusIndex(tabPane.getSelectedIndex(), true);
3688         }
3689         public void focusLost(FocusEvent e) {
3690            repaintTab(focusIndex);
3691         }
3692 
3693 
3694         //
3695         // ContainerListener
3696         //
3697     /* GES 2/3/99:
3698        The container listener code was added to support HTML
3699        rendering of tab titles.
3700 
3701        Ideally, we would be able to listen for property changes
3702        when a tab is added or its text modified.  At the moment
3703        there are no such events because the Beans spec doesn't
3704        allow 'indexed' property changes (i.e. tab 2's text changed
3705        from A to B).
3706 
3707        In order to get around this, we listen for tabs to be added
3708        or removed by listening for the container events.  we then
3709        queue up a runnable (so the component has a chance to complete
3710        the add) which checks the tab title of the new component to see
3711        if it requires HTML rendering.
3712 
3713        The Views (one per tab title requiring HTML rendering) are
3714        stored in the htmlViews Vector, which is only allocated after
3715        the first time we run into an HTML tab.  Note that this vector
3716        is kept in step with the number of pages, and nulls are added
3717        for those pages whose tab title do not require HTML rendering.
3718 
3719        This makes it easy for the paint and layout code to tell
3720        whether to invoke the HTML engine without having to check
3721        the string during time-sensitive operations.
3722 
3723        When we have added a way to listen for tab additions and
3724        changes to tab text, this code should be removed and
3725        replaced by something which uses that.  */
3726 
3727         public void componentAdded(ContainerEvent e) {
3728             JTabbedPane tp = (JTabbedPane)e.getContainer();
3729             Component child = e.getChild();
3730             if (child instanceof UIResource) {
3731                 return;
3732             }
3733             isRunsDirty = true;
3734             updateHtmlViews(tp.indexOfComponent(child), true);
3735         }
3736         public void componentRemoved(ContainerEvent e) {
3737             JTabbedPane tp = (JTabbedPane)e.getContainer();
3738             Component child = e.getChild();
3739             if (child instanceof UIResource) {
3740                 return;
3741             }
3742 
3743             // NOTE 4/15/2002 (joutwate):
3744             // This fix is implemented using client properties since there is
3745             // currently no IndexPropertyChangeEvent.  Once
3746             // IndexPropertyChangeEvents have been added this code should be
3747             // modified to use it.
3748             Integer indexObj =
3749                 (Integer)tp.getClientProperty("__index_to_remove__");
3750             if (indexObj != null) {
3751                 int index = indexObj.intValue();
3752                 if (htmlViews != null && htmlViews.size() > index) {
3753                     htmlViews.removeElementAt(index);
3754                 }
3755                 tp.putClientProperty("__index_to_remove__", null);
3756             }
3757             isRunsDirty = true;
3758             updateMnemonics();
3759 
3760             validateFocusIndex();
3761         }
3762     }
3763 
3764     /**
3765      * This class should be treated as a &quot;protected&quot; inner class.
3766      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3767      */
3768     public class PropertyChangeHandler implements PropertyChangeListener {
3769         // NOTE: This class exists only for backward compatibility. All
3770         // its functionality has been moved into Handler. If you need to add
3771         // new functionality add it to the Handler, but make sure this
3772         // class calls into the Handler.
3773         public void propertyChange(PropertyChangeEvent e) {
3774             getHandler().propertyChange(e);
3775         }
3776     }
3777 
3778     /**
3779      * This class should be treated as a &quot;protected&quot; inner class.
3780      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3781      */
3782     public class TabSelectionHandler implements ChangeListener {
3783         // NOTE: This class exists only for backward compatibility. All
3784         // its functionality has been moved into Handler. If you need to add
3785         // new functionality add it to the Handler, but make sure this
3786         // class calls into the Handler.
3787         public void stateChanged(ChangeEvent e) {
3788             getHandler().stateChanged(e);
3789         }
3790     }
3791 
3792     /**
3793      * This class should be treated as a &quot;protected&quot; inner class.
3794      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3795      */
3796     public class MouseHandler extends MouseAdapter {
3797         // NOTE: This class exists only for backward compatibility. All
3798         // its functionality has been moved into Handler. If you need to add
3799         // new functionality add it to the Handler, but make sure this
3800         // class calls into the Handler.
3801         public void mousePressed(MouseEvent e) {
3802             getHandler().mousePressed(e);
3803         }
3804     }
3805 
3806     /**
3807      * This class should be treated as a &quot;protected&quot; inner class.
3808      * Instantiate it only within subclasses of BasicTabbedPaneUI.
3809      */
3810     public class FocusHandler extends FocusAdapter {
3811         // NOTE: This class exists only for backward compatibility. All
3812         // its functionality has been moved into Handler. If you need to add
3813         // new functionality add it to the Handler, but make sure this
3814         // class calls into the Handler.
3815         public void focusGained(FocusEvent e) {
3816             getHandler().focusGained(e);
3817         }
3818         public void focusLost(FocusEvent e) {
3819             getHandler().focusLost(e);
3820         }
3821     }
3822 
3823     private Vector<View> createHTMLVector() {
3824         Vector<View> htmlViews = new Vector<View>();
3825         int count = tabPane.getTabCount();
3826         if (count>0) {
3827             for (int i=0 ; i<count; i++) {
3828                 String title = tabPane.getTitleAt(i);
3829                 if (BasicHTML.isHTMLString(title)) {
3830                     htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
3831                 } else {
3832                     htmlViews.addElement(null);
3833                 }
3834             }
3835         }
3836         return htmlViews;
3837     }
3838 
3839     @SuppressWarnings("serial") // Superclass is not serializable across versions
3840     private class TabContainer extends JPanel implements UIResource {
3841         private boolean notifyTabbedPane = true;
3842 
3843         public TabContainer() {
3844             super(null);
3845             setOpaque(false);
3846         }
3847 
3848         public void remove(Component comp) {
3849             int index = tabPane.indexOfTabComponent(comp);
3850             super.remove(comp);
3851             if (notifyTabbedPane && index != -1) {
3852                 tabPane.setTabComponentAt(index, null);
3853             }
3854         }
3855 
3856         private void removeUnusedTabComponents() {
3857             for (Component c : getComponents()) {
3858                 if (!(c instanceof UIResource)) {
3859                     int index = tabPane.indexOfTabComponent(c);
3860                     if (index == -1) {
3861                         super.remove(c);
3862                     }
3863                 }
3864             }
3865         }
3866 
3867         public boolean isOptimizedDrawingEnabled() {
3868             return tabScroller != null && !tabScroller.croppedEdge.isParamsSet();
3869         }
3870 
3871         public void doLayout() {
3872             // We layout tabComponents in JTabbedPane's layout manager
3873             // and use this method as a hook for repainting tabs
3874             // to update tabs area e.g. when the size of tabComponent was changed
3875             if (scrollableTabLayoutEnabled()) {
3876                 tabScroller.tabPanel.repaint();
3877                 tabScroller.updateView();
3878             } else {
3879                 tabPane.repaint(getBounds());
3880             }
3881         }
3882     }
3883 
3884     @SuppressWarnings("serial") // Superclass is not serializable across versions
3885     private class CroppedEdge extends JPanel implements UIResource {
3886         private Shape shape;
3887         private int tabIndex;
3888         private int cropline;
3889         private int cropx, cropy;
3890 
3891         public CroppedEdge() {
3892             setOpaque(false);
3893         }
3894 
3895         public void setParams(int tabIndex, int cropline, int cropx, int cropy) {
3896             this.tabIndex = tabIndex;
3897             this.cropline = cropline;
3898             this.cropx = cropx;
3899             this.cropy = cropy;
3900             Rectangle tabRect = rects[tabIndex];
3901             setBounds(tabRect);
3902             shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline);
3903             if (getParent() == null && tabContainer != null) {
3904                 tabContainer.add(this, 0);
3905             }
3906         }
3907 
3908         public void resetParams() {
3909             shape = null;
3910             if (getParent() == tabContainer && tabContainer != null) {
3911                 tabContainer.remove(this);
3912             }
3913         }
3914 
3915         public boolean isParamsSet() {
3916             return shape != null;
3917         }
3918 
3919         public int getTabIndex() {
3920             return tabIndex;
3921         }
3922 
3923         public int getCropline() {
3924             return cropline;
3925         }
3926 
3927         public int getCroppedSideWidth() {
3928             return 3;
3929         }
3930 
3931         private Color getBgColor() {
3932             Component parent = tabPane.getParent();
3933             if (parent != null) {
3934                 Color bg = parent.getBackground();
3935                 if (bg != null) {
3936                     return bg;
3937                 }
3938             }
3939             return UIManager.getColor("control");
3940         }
3941 
3942         protected void paintComponent(Graphics g) {
3943             super.paintComponent(g);
3944             if (isParamsSet() && g instanceof Graphics2D) {
3945                 Graphics2D g2 = (Graphics2D) g;
3946                 g2.clipRect(0, 0, getWidth(), getHeight());
3947                 g2.setColor(getBgColor());
3948                 g2.translate(cropx, cropy);
3949                 g2.fill(shape);
3950                 paintCroppedTabEdge(g);
3951                 g2.translate(-cropx, -cropy);
3952             }
3953         }
3954     }
3955 }