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