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