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