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