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