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