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