       1 /*
       2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package javax.swing.plaf.basic;
      27
      28 import sun.swing.SwingUtilities2;
      29
      30 import javax.swing.*;
      31 import javax.swing.event.*;
      32 import javax.swing.plaf.*;
      33 import javax.swing.text.View;
      34
      35 import java.awt.*;
      36 import java.awt.event.*;
      37 import java.beans.PropertyChangeListener;
      38 import java.beans.PropertyChangeEvent;
      39 import java.util.Vector;
      40 import java.util.Hashtable;
      41
      42 import sun.swing.DefaultLookup;
      43 import sun.swing.UIAction;
      44
      45 /**
      46  * A Basic L&amp;F implementation of TabbedPaneUI.
      47  *
      48  * @author Amy Fowler
      49  * @author Philip Milne
      50  * @author Steve Wilson
      51  * @author Tom Santos
      52  * @author Dave Moore
      53  */
      54 public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants {
      55
      56
      57 // Instance variables initialized at installation
      58
      59     /** The tab pane */
      60     protected JTabbedPane tabPane;
      61
      62     /** Highlight color */
      63     protected Color highlight;
      64     /** Light highlight color */
      65     protected Color lightHighlight;
      66     /** Shadow color */
      67     protected Color shadow;
      68     /** Dark shadow color */
      69     protected Color darkShadow;
      70     /** Focus color */
      71     protected Color focus;
      72     private   Color selectedColor;
      73
      74     /** Text icon gap */
      75     protected int textIconGap;
      76     /** Tab run overlay */
      77     protected int tabRunOverlay;
      78
      79     /** Tab insets */
      80     protected Insets tabInsets;
      81     /** Selected tab insets */
      82     protected Insets selectedTabPadInsets;
      83     /** Tab area insets */
      84     protected Insets tabAreaInsets;
      85     /** Content border insets */
      86     protected Insets contentBorderInsets;
      87     private boolean tabsOverlapBorder;
      88     private boolean tabsOpaque = true;
      89     private boolean contentOpaque = true;
      90
      91     /**
      92      * As of Java 2 platform v1.3 this previously undocumented field is no
      93      * longer used.
      94      * Key bindings are now defined by the LookAndFeel, please refer to
      95      * the key bindings specification for further details.
      96      *
      97      * @deprecated As of Java 2 platform v1.3.
      98      */
      99     @Deprecated
     100     protected KeyStroke upKey;
     101     /**
     102      * As of Java 2 platform v1.3 this previously undocumented field is no
     103      * longer used.
     104      * Key bindings are now defined by the LookAndFeel, please refer to
     105      * the key bindings specification for further details.
     106      *
     107      * @deprecated As of Java 2 platform v1.3.
     108      */
     109     @Deprecated
     110     protected KeyStroke downKey;
     111     /**
     112      * As of Java 2 platform v1.3 this previously undocumented field is no
     113      * longer used.
     114      * Key bindings are now defined by the LookAndFeel, please refer to
     115      * the key bindings specification for further details.
     116      *
     117      * @deprecated As of Java 2 platform v1.3.
     118      */
     119     @Deprecated
     120     protected KeyStroke leftKey;
     121     /**
     122      * As of Java 2 platform v1.3 this previously undocumented field is no
     123      * longer used.
     124      * Key bindings are now defined by the LookAndFeel, please refer to
     125      * the key bindings specification for further details.
     126      *
     127      * @deprecated As of Java 2 platform v1.3.
     128      */
     129     @Deprecated
     130     protected KeyStroke rightKey;
     131
     132
     133 // Transient variables (recalculated each time TabbedPane is layed out)
     134     /** Tab runs */
     135     protected int tabRuns[] = new int[10];
     136     /** Run count */
     137     protected int runCount = 0;
     138     /** Selected run */
     139     protected int selectedRun = -1;
     140     /** Tab rects */
     141     protected Rectangle rects[] = new Rectangle[0];
     142     /** Maximum tab height */
     143     protected int maxTabHeight;
     144     /** Maximum tab width */
     145     protected int maxTabWidth;
     146
     147 // Listeners
     148
     149     /** Tab change listener */
     150     protected ChangeListener tabChangeListener;
     151     /** Property change listener */
     152     protected PropertyChangeListener propertyChangeListener;
     153     /** Mouse change listener */
     154     protected MouseListener mouseListener;
     155     /** Focus change listener */
     156     protected FocusListener focusListener;
     157
     158 // Private instance data
     159
     160     private Insets currentPadInsets = new Insets(0,0,0,0);
     161     private Insets currentTabAreaInsets = new Insets(0,0,0,0);
     162
     163     private Component visibleComponent;
     164     // PENDING(api): See comment for ContainerHandler
     165     private Vector<View> htmlViews;
     166
     167     private Hashtable<Integer, Integer> mnemonicToIndexMap;
     168
     169     /**
     170      * InputMap used for mnemonics. Only non-null if the JTabbedPane has
     171      * mnemonics associated with it. Lazily created in initMnemonics.
     172      */
     173     private InputMap mnemonicInputMap;
     174
     175     // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
     176     private ScrollableTabSupport tabScroller;
     177
     178     private TabContainer tabContainer;
     179
     180     /**
     181      * A rectangle used for general layout calculations in order
     182      * to avoid constructing many new Rectangles on the fly.
     183      */
     184     protected transient Rectangle calcRect = new Rectangle(0,0,0,0);
     185
     186     /**
     187      * Tab that has focus.
     188      */
     189     private int focusIndex;
     190
     191     /**
     192      * Combined listeners.
     193      */
     194     private Handler handler;
     195
     196     /**
     197      * Index of the tab the mouse is over.
     198      */
     199     private int rolloverTabIndex;
     200
     201     /**
     202      * This is set to true when a component is added/removed from the tab
     203      * pane and set to false when layout happens.  If true it indicates that
     204      * tabRuns is not valid and shouldn't be used.
     205      */
     206     private boolean isRunsDirty;
     207
     208     private boolean calculatedBaseline;
     209     private int baseline;
     210
     211 // UI creation
     212
     213     /**
     214      * Create a UI.
     215      * @param c a component
     216      * @return a UI
     217      */
     218     public static ComponentUI createUI(JComponent c) {
     219         return new BasicTabbedPaneUI();
     220     }
     221
     222     static void loadActionMap(LazyActionMap map) {
     223         map.put(new Actions(Actions.NEXT));
     224         map.put(new Actions(Actions.PREVIOUS));
     225         map.put(new Actions(Actions.RIGHT));
     226         map.put(new Actions(Actions.LEFT));
     227         map.put(new Actions(Actions.UP));
     228         map.put(new Actions(Actions.DOWN));
     229         map.put(new Actions(Actions.PAGE_UP));
     230         map.put(new Actions(Actions.PAGE_DOWN));
     231         map.put(new Actions(Actions.REQUEST_FOCUS));
     232         map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE));
     233         map.put(new Actions(Actions.SET_SELECTED));
     234         map.put(new Actions(Actions.SELECT_FOCUSED));
     235         map.put(new Actions(Actions.SCROLL_FORWARD));
     236         map.put(new Actions(Actions.SCROLL_BACKWARD));
     237     }
     238
     239 // UI Installation/De-installation
     240
     241     public void installUI(JComponent c) {
     242         this.tabPane = (JTabbedPane)c;
     243
     244         calculatedBaseline = false;
     245         rolloverTabIndex = -1;
     246         focusIndex = -1;
     247         c.setLayout(createLayoutManager());
     248         installComponents();
     249         installDefaults();
     250         installListeners();
     251         installKeyboardActions();
     252     }
     253
     254     public void uninstallUI(JComponent c) {
     255         uninstallKeyboardActions();
     256         uninstallListeners();
     257         uninstallDefaults();
     258         uninstallComponents();
     259         c.setLayout(null);
     260
     261         this.tabPane = null;
     262     }
     263
     264     /**
     265      * Invoked by <code>installUI</code> to create
     266      * a layout manager object to manage
     267      * the <code>JTabbedPane</code>.
     268      *
     269      * @return a layout manager object
     270      *
     271      * @see TabbedPaneLayout
     272      * @see javax.swing.JTabbedPane#getTabLayoutPolicy
     273      */
     274     protected LayoutManager createLayoutManager() {
     275         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
     276             return new TabbedPaneScrollLayout();
     277         } else { /* WRAP_TAB_LAYOUT */
     278             return new TabbedPaneLayout();
     279         }
     280     }
     281
     282     /* In an attempt to preserve backward compatibility for programs
     283      * which have extended BasicTabbedPaneUI to do their own layout, the
     284      * UI uses the installed layoutManager (and not tabLayoutPolicy) to
     285      * determine if scrollTabLayout is enabled.
     286      */
     287     private boolean scrollableTabLayoutEnabled() {
     288         return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
     289     }
     290
     291     /**
     292      * Creates and installs any required subcomponents for the JTabbedPane.
     293      * Invoked by installUI.
     294      *
     295      * @since 1.4
     296      */
     297     protected void installComponents() {
     298         if (scrollableTabLayoutEnabled()) {
     299             if (tabScroller == null) {
     300                 tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
     301                 tabPane.add(tabScroller.viewport);
     302             }
     303         }
     304         installTabContainer();
     305     }
     306
     307     private void installTabContainer() {
     308          for (int i = 0; i < tabPane.getTabCount(); i++) {
     309              Component tabComponent = tabPane.getTabComponentAt(i);
     310              if (tabComponent != null) {
     311                  if(tabContainer == null) {
     312                      tabContainer = new TabContainer();
     313                  }
     314                  tabContainer.add(tabComponent);
     315              }
     316          }
     317          if(tabContainer == null) {
     318              return;
     319          }
     320          if (scrollableTabLayoutEnabled()) {
     321              tabScroller.tabPanel.add(tabContainer);
     322          } else {
     323              tabPane.add(tabContainer);
     324          }
     325     }
     326
     327     /**
     328      * Creates and returns a JButton that will provide the user
     329      * with a way to scroll the tabs in a particular direction. The
     330      * returned JButton must be instance of UIResource.
     331      *
     332      * @param direction One of the SwingConstants constants:
     333      * SOUTH, NORTH, EAST or WEST
     334      * @return Widget for user to
     335      * @see javax.swing.JTabbedPane#setTabPlacement
     336      * @see javax.swing.SwingConstants
     337      * @throws IllegalArgumentException if direction is not one of
     338      *         NORTH, SOUTH, EAST or WEST
     339      * @since 1.5
     340      */
     341     protected JButton createScrollButton(int direction) {
     342         if (direction != SOUTH && direction != NORTH && direction != EAST &&
     343                                   direction != WEST) {
     344             throw new IllegalArgumentException("Direction must be one of: " +
     345                                                "SOUTH, NORTH, EAST or WEST");
     346         }
     347         return new ScrollableTabButton(direction);
     348     }
     349
     350     /**
     351      * Removes any installed subcomponents from the JTabbedPane.
     352      * Invoked by uninstallUI.
     353      *
     354      * @since 1.4
     355      */
     356     protected void uninstallComponents() {
     357         uninstallTabContainer();
     358         if (scrollableTabLayoutEnabled()) {
     359             tabPane.remove(tabScroller.viewport);
     360             tabPane.remove(tabScroller.scrollForwardButton);
     361             tabPane.remove(tabScroller.scrollBackwardButton);
     362             tabScroller = null;
     363         }
     364     }
     365
     366     private void uninstallTabContainer() {
     367          if(tabContainer == null) {
     368              return;
     369          }
     370          // Remove all the tabComponents, making sure not to notify
     371          // the tabbedpane.
     372          tabContainer.notifyTabbedPane = false;
     373          tabContainer.removeAll();
     374          if(scrollableTabLayoutEnabled()) {
     375              tabContainer.remove(tabScroller.croppedEdge);
     376              tabScroller.tabPanel.remove(tabContainer);
     377          } else {
     378            tabPane.remove(tabContainer);
     379          }
     380          tabContainer = null;
     381     }
     382
     383     /**
     384      * Install the defaults.
     385      */
     386     protected void installDefaults() {
     387         LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
     388                                     "TabbedPane.foreground", "TabbedPane.font");
     389         highlight = UIManager.getColor("TabbedPane.light");
     390         lightHighlight = UIManager.getColor("TabbedPane.highlight");
     391         shadow = UIManager.getColor("TabbedPane.shadow");
     392         darkShadow = UIManager.getColor("TabbedPane.darkShadow");
     393         focus = UIManager.getColor("TabbedPane.focus");
     394         selectedColor = UIManager.getColor("TabbedPane.selected");
     395
     396         textIconGap = UIManager.getInt("TabbedPane.textIconGap");
     397         tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
     398         selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
     399         tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
     400         tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder");
     401         contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
     402         tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
     403         tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
     404         contentOpaque = UIManager.getBoolean("TabbedPane.contentOpaque");
     405         Object opaque = UIManager.get("TabbedPane.opaque");
     406         if (opaque == null) {
     407             opaque = Boolean.FALSE;
     408         }
     409         LookAndFeel.installProperty(tabPane, "opaque", opaque);
     410
     411         // Fix for 6711145 BasicTabbedPanuUI should not throw a NPE if these
     412         // keys are missing. So we are setting them to there default values here
     413         // if the keys are missing.
     414         if (tabInsets == null) tabInsets = new Insets(0,4,1,4);
     415         if (selectedTabPadInsets == null) selectedTabPadInsets = new Insets(2,2,2,1);
     416         if (tabAreaInsets == null) tabAreaInsets = new Insets(3,2,0,2);
     417         if (contentBorderInsets == null) contentBorderInsets = new Insets(2,2,3,3);
     418     }
     419
     420     /**
     421      * Uninstall the defaults.
     422      */
     423     protected void uninstallDefaults() {
     424         highlight = null;
     425         lightHighlight = null;
     426         shadow = null;
     427         darkShadow = null;
     428         focus = null;
     429         tabInsets = null;
     430         selectedTabPadInsets = null;
     431         tabAreaInsets = null;
     432         contentBorderInsets = null;
     433     }
     434
     435     /**
     436      * Install the listeners.
     437      */
     438     protected void installListeners() {
     439         if ((propertyChangeListener = createPropertyChangeListener()) != null) {
     440             tabPane.addPropertyChangeListener(propertyChangeListener);
     441         }
     442         if ((tabChangeListener = createChangeListener()) != null) {
     443             tabPane.addChangeListener(tabChangeListener);
     444         }
     445         if ((mouseListener = createMouseListener()) != null) {
     446             tabPane.addMouseListener(mouseListener);
     447         }
     448         tabPane.addMouseMotionListener(getHandler());
     449         if ((focusListener = createFocusListener()) != null) {
     450             tabPane.addFocusListener(focusListener);
     451         }
     452         tabPane.addContainerListener(getHandler());
     453         if (tabPane.getTabCount()>0) {
     454             htmlViews = createHTMLVector();
     455         }
     456     }
     457
     458     /**
     459      * Uninstall the listeners.
     460      */
     461     protected void uninstallListeners() {
     462         if (mouseListener != null) {
     463             tabPane.removeMouseListener(mouseListener);
     464             mouseListener = null;
     465         }
     466         tabPane.removeMouseMotionListener(getHandler());
     467         if (focusListener != null) {
     468             tabPane.removeFocusListener(focusListener);
     469             focusListener = null;
     470         }
     471
     472         tabPane.removeContainerListener(getHandler());
     473         if (htmlViews!=null) {
     474             htmlViews.removeAllElements();
     475             htmlViews = null;
     476         }
     477         if (tabChangeListener != null) {
     478             tabPane.removeChangeListener(tabChangeListener);
     479             tabChangeListener = null;
     480         }
     481         if (propertyChangeListener != null) {
     482             tabPane.removePropertyChangeListener(propertyChangeListener);
     483             propertyChangeListener = null;
     484         }
     485         handler = null;
     486     }
     487
     488     /**
     489      * Creates a mouse listener.
     490      * @return a mouse listener
     491      */
     492     protected MouseListener createMouseListener() {
     493         return getHandler();
     494     }
     495
     496     /**
     497      * Creates a focus listener.
     498      * @return a focus listener
     499      */
     500     protected FocusListener createFocusListener() {
     501         return getHandler();
     502     }
     503
     504     /**
     505      * Creates a change listener.
     506      * @return a change listener
     507      */
     508     protected ChangeListener createChangeListener() {
     509         return getHandler();
     510     }
     511
     512     /**
     513      * Creates a property change listener.
     514      * @return a property change listener
     515      */
     516     protected PropertyChangeListener createPropertyChangeListener() {
     517         return getHandler();
     518     }
     519
     520     private Handler getHandler() {
     521         if (handler == null) {
     522             handler = new Handler();
     523         }
     524         return handler;
     525     }
     526
     527     /**
     528      * Installs the keyboard actions.
     529      */
     530     protected void installKeyboardActions() {
     531         InputMap km = getInputMap(JComponent.
     532                                   WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
     533
     534         SwingUtilities.replaceUIInputMap(tabPane, JComponent.
     535                                          WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
     536                                          km);
     537         km = getInputMap(JComponent.WHEN_FOCUSED);
     538         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
     539
     540         LazyActionMap.installLazyActionMap(tabPane, BasicTabbedPaneUI.class,
     541                                            "TabbedPane.actionMap");
     542         updateMnemonics();
     543     }
     544
     545     InputMap getInputMap(int condition) {
     546         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
     547             return (InputMap)DefaultLookup.get(tabPane, this,
     548                                                "TabbedPane.ancestorInputMap");
     549         }
     550         else if (condition == JComponent.WHEN_FOCUSED) {
     551             return (InputMap)DefaultLookup.get(tabPane, this,
     552                                                "TabbedPane.focusInputMap");
     553         }
     554         return null;
     555     }
     556
     557     /**
     558      * Uninstalls the keyboard actions.
     559      */
     560     protected void uninstallKeyboardActions() {
     561         SwingUtilities.replaceUIActionMap(tabPane, null);
     562         SwingUtilities.replaceUIInputMap(tabPane, JComponent.
     563                                          WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
     564                                          null);
     565         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED,
     566                                          null);
     567         SwingUtilities.replaceUIInputMap(tabPane,
     568                                          JComponent.WHEN_IN_FOCUSED_WINDOW,
     569                                          null);
     570         mnemonicToIndexMap = null;
     571         mnemonicInputMap = null;
     572     }
     573
     574     /**
     575      * Reloads the mnemonics. This should be invoked when a memonic changes,
     576      * when the title of a mnemonic changes, or when tabs are added/removed.
     577      */
     578     private void updateMnemonics() {
     579         resetMnemonics();
     580         for (int counter = tabPane.getTabCount() - 1; counter >= 0;
     581              counter--) {
     582             int mnemonic = tabPane.getMnemonicAt(counter);
     583
     584             if (mnemonic > 0) {
     585                 addMnemonic(counter, mnemonic);
     586             }
     587         }
     588     }
     589
     590     /**
     591      * Resets the mnemonics bindings to an empty state.
     592      */
     593     private void resetMnemonics() {
     594         if (mnemonicToIndexMap != null) {
     595             mnemonicToIndexMap.clear();
     596             mnemonicInputMap.clear();
     597         }
     598     }
     599
     600     /**
     601      * Adds the specified mnemonic at the specified index.
     602      */
     603     private void addMnemonic(int index, int mnemonic) {
     604         if (mnemonicToIndexMap == null) {
     605             initMnemonics();
     606         }
     607         mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, BasicLookAndFeel.getFocusAcceleratorKeyMask()),
     608                              "setSelectedIndex");

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