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