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