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