1 /*
   2  * Copyright (c) 2002, 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 package javax.swing.plaf.synth;
  27 
  28 import javax.swing.*;
  29 import javax.swing.plaf.*;
  30 import javax.swing.plaf.basic.*;
  31 import javax.swing.text.View;
  32 
  33 import java.awt.*;
  34 import java.awt.event.*;
  35 import java.beans.PropertyChangeListener;
  36 import java.beans.PropertyChangeEvent;
  37 import sun.swing.SwingUtilities2;
  38 
  39 /**
  40  * Provides the Synth L&F UI delegate for
  41  * {@link javax.swing.JTabbedPane}.
  42  *
  43  * <p>Looks up the {@code selectedTabPadInsets} property from the Style,
  44  * which represents additional insets for the selected tab.
  45  *
  46  * @author Scott Violet
  47  * @since 1.7
  48  */
  49 public class SynthTabbedPaneUI extends BasicTabbedPaneUI
  50                                implements PropertyChangeListener, SynthUI {
  51 
  52     /**
  53      * <p>If non-zero, tabOverlap indicates the amount that the tab bounds
  54      * should be altered such that they would overlap with a tab on either the
  55      * leading or trailing end of a run (ie: in TOP, this would be on the left
  56      * or right).</p>
  57 
  58      * <p>A positive overlap indicates that tabs should overlap right/down,
  59      * while a negative overlap indicates tha tabs should overlap left/up.</p>
  60      *
  61      * <p>When tabOverlap is specified, it both changes the x position and width
  62      * of the tab if in TOP or BOTTOM placement, and changes the y position and
  63      * height if in LEFT or RIGHT placement.</p>
  64      *
  65      * <p>This is done for the following reason. Consider a run of 10 tabs.
  66      * There are 9 gaps between these tabs. If you specified a tabOverlap of
  67      * "-1", then each of the tabs "x" values will be shifted left. This leaves
  68      * 9 pixels of space to the right of the right-most tab unpainted. So, each
  69      * tab's width is also extended by 1 pixel to make up the difference.</p>
  70      *
  71      * <p>This property respects the RTL component orientation.</p>
  72      */
  73     private int tabOverlap = 0;
  74 
  75     /**
  76      * When a tabbed pane has multiple rows of tabs, this indicates whether
  77      * the tabs in the upper row(s) should extend to the base of the tab area,
  78      * or whether they should remain at their normal tab height. This does not
  79      * affect the bounds of the tabs, only the bounds of area painted by the
  80      * tabs. The text position does not change. The result is that the bottom
  81      * border of the upper row of tabs becomes fully obscured by the lower tabs,
  82      * resulting in a cleaner look.
  83      */
  84     private boolean extendTabsToBase = false;
  85 
  86     private SynthContext tabAreaContext;
  87     private SynthContext tabContext;
  88     private SynthContext tabContentContext;
  89 
  90     private SynthStyle style;
  91     private SynthStyle tabStyle;
  92     private SynthStyle tabAreaStyle;
  93     private SynthStyle tabContentStyle;
  94 
  95     private Rectangle textRect = new Rectangle();
  96     private Rectangle iconRect = new Rectangle();
  97 
  98     private Rectangle tabAreaBounds = new Rectangle();
  99 
 100     //added for the Nimbus look and feel, where the tab area is painted differently depending on the
 101     //state for the selected tab
 102     private boolean tabAreaStatesMatchSelectedTab = false;
 103     //added for the Nimbus LAF to ensure that the labels don't move whether the tab is selected or not
 104     private boolean nudgeSelectedLabel = true;
 105 
 106     private boolean selectedTabIsPressed = false;
 107 
 108     /**
 109      * Creates a new UI object for the given component.
 110      *
 111      * @param c component to create UI object for
 112      * @return the UI object
 113      */
 114     public static ComponentUI createUI(JComponent c) {
 115         return new SynthTabbedPaneUI();
 116     }
 117 
 118      private boolean scrollableTabLayoutEnabled() {
 119         return (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT);
 120     }
 121 
 122     /**
 123      * {@inheritDoc}
 124      */
 125     @Override
 126     protected void installDefaults() {
 127         updateStyle(tabPane);
 128     }
 129 
 130     private void updateStyle(JTabbedPane c) {
 131         SynthContext context = getContext(c, ENABLED);
 132         SynthStyle oldStyle = style;
 133         style = SynthLookAndFeel.updateStyle(context, this);
 134         // Add properties other than JComponent colors, Borders and
 135         // opacity settings here:
 136         if (style != oldStyle) {
 137             tabRunOverlay =
 138                 style.getInt(context, "TabbedPane.tabRunOverlay", 0);
 139             tabOverlap = style.getInt(context, "TabbedPane.tabOverlap", 0);
 140             extendTabsToBase = style.getBoolean(context,
 141                     "TabbedPane.extendTabsToBase", false);
 142             textIconGap = style.getInt(context, "TabbedPane.textIconGap", 0);
 143             selectedTabPadInsets = (Insets)style.get(context,
 144                 "TabbedPane.selectedTabPadInsets");
 145             if (selectedTabPadInsets == null) {
 146                 selectedTabPadInsets = new Insets(0, 0, 0, 0);
 147             }
 148             tabAreaStatesMatchSelectedTab = style.getBoolean(context,
 149                     "TabbedPane.tabAreaStatesMatchSelectedTab", false);
 150             nudgeSelectedLabel = style.getBoolean(context,
 151                     "TabbedPane.nudgeSelectedLabel", true);
 152             if (oldStyle != null) {
 153                 uninstallKeyboardActions();
 154                 installKeyboardActions();
 155             }
 156         }
 157         context.dispose();
 158 
 159         if (tabContext != null) {
 160             tabContext.dispose();
 161         }
 162         tabContext = getContext(c, Region.TABBED_PANE_TAB, ENABLED);
 163         this.tabStyle = SynthLookAndFeel.updateStyle(tabContext, this);
 164         tabInsets = tabStyle.getInsets(tabContext, null);
 165 
 166 
 167         if (tabAreaContext != null) {
 168             tabAreaContext.dispose();
 169         }
 170         tabAreaContext = getContext(c, Region.TABBED_PANE_TAB_AREA, ENABLED);
 171         this.tabAreaStyle = SynthLookAndFeel.updateStyle(tabAreaContext, this);
 172         tabAreaInsets = tabAreaStyle.getInsets(tabAreaContext, null);
 173 
 174 
 175         if (tabContentContext != null) {
 176             tabContentContext.dispose();
 177         }
 178         tabContentContext = getContext(c, Region.TABBED_PANE_CONTENT, ENABLED);
 179         this.tabContentStyle = SynthLookAndFeel.updateStyle(tabContentContext,
 180                                                             this);
 181         contentBorderInsets =
 182             tabContentStyle.getInsets(tabContentContext, null);
 183     }
 184 
 185     /**
 186      * {@inheritDoc}
 187      */
 188     @Override
 189     protected void installListeners() {
 190         super.installListeners();
 191         tabPane.addPropertyChangeListener(this);
 192     }
 193 
 194     /**
 195      * {@inheritDoc}
 196      */
 197     @Override
 198     protected void uninstallListeners() {
 199         super.uninstallListeners();
 200         tabPane.removePropertyChangeListener(this);
 201     }
 202 
 203     /**
 204      * {@inheritDoc}
 205      */
 206     @Override
 207     protected void uninstallDefaults() {
 208         SynthContext context = getContext(tabPane, ENABLED);
 209         style.uninstallDefaults(context);
 210         context.dispose();
 211         style = null;
 212 
 213         tabStyle.uninstallDefaults(tabContext);
 214         tabContext.dispose();
 215         tabContext = null;
 216         tabStyle = null;
 217 
 218         tabAreaStyle.uninstallDefaults(tabAreaContext);
 219         tabAreaContext.dispose();
 220         tabAreaContext = null;
 221         tabAreaStyle = null;
 222 
 223         tabContentStyle.uninstallDefaults(tabContentContext);
 224         tabContentContext.dispose();
 225         tabContentContext = null;
 226         tabContentStyle = null;
 227     }
 228 
 229     /**
 230      * {@inheritDoc}
 231      */
 232     @Override
 233     public SynthContext getContext(JComponent c) {
 234         return getContext(c, SynthLookAndFeel.getComponentState(c));
 235     }
 236 
 237     private SynthContext getContext(JComponent c, int state) {
 238         return SynthContext.getContext(SynthContext.class, c,
 239                     SynthLookAndFeel.getRegion(c),style, state);
 240     }
 241 
 242     private SynthContext getContext(JComponent c, Region subregion, int state){
 243         SynthStyle style = null;
 244         Class klass = SynthContext.class;
 245 
 246         if (subregion == Region.TABBED_PANE_TAB) {
 247             style = tabStyle;
 248         }
 249         else if (subregion == Region.TABBED_PANE_TAB_AREA) {
 250             style = tabAreaStyle;
 251         }
 252         else if (subregion == Region.TABBED_PANE_CONTENT) {
 253             style = tabContentStyle;
 254         }
 255         return SynthContext.getContext(klass, c, subregion, style, state);
 256     }
 257 
 258     /**
 259      * {@inheritDoc}
 260      */
 261     @Override
 262     protected JButton createScrollButton(int direction) {
 263         // added for Nimbus LAF so that it can use the basic arrow buttons
 264         // UIManager is queried directly here because this is called before
 265         // updateStyle is called so the style can not be queried directly
 266         if (UIManager.getBoolean("TabbedPane.useBasicArrows")) {
 267             JButton btn = super.createScrollButton(direction);
 268             btn.setBorder(BorderFactory.createEmptyBorder());
 269             return btn;
 270         }
 271         return new SynthScrollableTabButton(direction);
 272     }
 273 
 274     /**
 275      * {@inheritDoc}
 276      */
 277     @Override
 278     public void propertyChange(PropertyChangeEvent e) {
 279         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
 280             updateStyle(tabPane);
 281         }
 282     }
 283 
 284     /**
 285      * {@inheritDoc}
 286      *
 287      * Overridden to keep track of whether the selected tab is also pressed.
 288      */
 289     @Override
 290     protected MouseListener createMouseListener() {
 291         final MouseListener delegate = super.createMouseListener();
 292         final MouseMotionListener delegate2 = (MouseMotionListener)delegate;
 293         return new MouseListener() {
 294             public void mouseClicked(MouseEvent e) { delegate.mouseClicked(e); }
 295             public void mouseEntered(MouseEvent e) { delegate.mouseEntered(e); }
 296             public void mouseExited(MouseEvent e) { delegate.mouseExited(e); }
 297 
 298             public void mousePressed(MouseEvent e) {
 299                 if (!tabPane.isEnabled()) {
 300                     return;
 301                 }
 302 
 303                 int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
 304                 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
 305                     if (tabIndex == tabPane.getSelectedIndex()) {
 306                         // Clicking on selected tab
 307                         selectedTabIsPressed = true;
 308                         //TODO need to just repaint the tab area!
 309                         tabPane.repaint();
 310                     }
 311                 }
 312 
 313                 //forward the event (this will set the selected index, or none at all
 314                 delegate.mousePressed(e);
 315             }
 316 
 317             public void mouseReleased(MouseEvent e) {
 318                 if (selectedTabIsPressed) {
 319                     selectedTabIsPressed = false;
 320                     //TODO need to just repaint the tab area!
 321                     tabPane.repaint();
 322                 }
 323                 //forward the event
 324                 delegate.mouseReleased(e);
 325 
 326                 //hack: The super method *should* be setting the mouse-over property correctly
 327                 //here, but it doesn't. That is, when the mouse is released, whatever tab is below the
 328                 //released mouse should be in rollover state. But, if you select a tab and don't
 329                 //move the mouse, this doesn't happen. Hence, forwarding the event.
 330                 delegate2.mouseMoved(e);
 331             }
 332         };
 333     }
 334 
 335     /**
 336      * {@inheritDoc}
 337      */
 338     @Override
 339     protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
 340         if (nudgeSelectedLabel) {
 341             return super.getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
 342         } else {
 343             return 0;
 344         }
 345     }
 346 
 347     /**
 348      * {@inheritDoc}
 349      */
 350     @Override
 351     protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
 352         if (nudgeSelectedLabel) {
 353             return super.getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
 354         } else {
 355             return 0;
 356         }
 357     }
 358 
 359     /**
 360      * Notifies this UI delegate to repaint the specified component.
 361      * This method paints the component background, then calls
 362      * the {@link #paint(SynthContext,Graphics)} method.
 363      *
 364      * <p>In general, this method does not need to be overridden by subclasses.
 365      * All Look and Feel rendering code should reside in the {@code paint} method.
 366      *
 367      * @param g the {@code Graphics} object used for painting
 368      * @param c the component being painted
 369      * @see #paint(SynthContext,Graphics)
 370      */
 371     @Override
 372     public void update(Graphics g, JComponent c) {
 373         SynthContext context = getContext(c);
 374 
 375         SynthLookAndFeel.update(context, g);
 376         context.getPainter().paintTabbedPaneBackground(context,
 377                           g, 0, 0, c.getWidth(), c.getHeight());
 378         paint(context, g);
 379         context.dispose();
 380     }
 381 
 382     /**
 383      * {@inheritDoc}
 384      */
 385     @Override
 386     protected int getBaseline(int tab) {
 387         if (tabPane.getTabComponentAt(tab) != null ||
 388                 getTextViewForTab(tab) != null) {
 389             return super.getBaseline(tab);
 390         }
 391         String title = tabPane.getTitleAt(tab);
 392         Font font = tabContext.getStyle().getFont(tabContext);
 393         FontMetrics metrics = getFontMetrics(font);
 394         Icon icon = getIconForTab(tab);
 395         textRect.setBounds(0, 0, 0, 0);
 396         iconRect.setBounds(0, 0, 0, 0);
 397         calcRect.setBounds(0, 0, Short.MAX_VALUE, maxTabHeight);
 398         tabContext.getStyle().getGraphicsUtils(tabContext).layoutText(
 399                 tabContext, metrics, title, icon, SwingUtilities.CENTER,
 400                 SwingUtilities.CENTER, SwingUtilities.LEADING,
 401                 SwingUtilities.CENTER, calcRect,
 402                 iconRect, textRect, textIconGap);
 403         return textRect.y + metrics.getAscent() + getBaselineOffset();
 404     }
 405 
 406     /**
 407      * {@inheritDoc}
 408      */
 409     @Override
 410     public void paintBorder(SynthContext context, Graphics g, int x,
 411                             int y, int w, int h) {
 412         context.getPainter().paintTabbedPaneBorder(context, g, x, y, w, h);
 413     }
 414 
 415     /**
 416      * Paints the specified component according to the Look and Feel.
 417      * <p>This method is not used by Synth Look and Feel.
 418      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 419      *
 420      * @param g the {@code Graphics} object used for painting
 421      * @param c the component being painted
 422      * @see #paint(SynthContext,Graphics)
 423      */
 424     @Override
 425     public void paint(Graphics g, JComponent c) {
 426         SynthContext context = getContext(c);
 427 
 428         paint(context, g);
 429         context.dispose();
 430     }
 431 
 432     /**
 433      * Paints the specified component.
 434      *
 435      * @param context context for the component being painted
 436      * @param g the {@code Graphics} object used for painting
 437      * @see #update(Graphics,JComponent)
 438      */
 439     protected void paint(SynthContext context, Graphics g) {
 440         int selectedIndex = tabPane.getSelectedIndex();
 441         int tabPlacement = tabPane.getTabPlacement();
 442 
 443         ensureCurrentLayout();
 444 
 445         // Paint tab area
 446         // If scrollable tabs are enabled, the tab area will be
 447         // painted by the scrollable tab panel instead.
 448         //
 449         if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
 450             Insets insets = tabPane.getInsets();
 451             int x = insets.left;
 452             int y = insets.top;
 453             int width = tabPane.getWidth() - insets.left - insets.right;
 454             int height = tabPane.getHeight() - insets.top - insets.bottom;
 455             int size;
 456             switch(tabPlacement) {
 457             case LEFT:
 458                 width = calculateTabAreaWidth(tabPlacement, runCount,
 459                                               maxTabWidth);
 460                 break;
 461             case RIGHT:
 462                 size = calculateTabAreaWidth(tabPlacement, runCount,
 463                                              maxTabWidth);
 464                 x = x + width - size;
 465                 width = size;
 466                 break;
 467             case BOTTOM:
 468                 size = calculateTabAreaHeight(tabPlacement, runCount,
 469                                               maxTabHeight);
 470                 y = y + height - size;
 471                 height = size;
 472                 break;
 473             case TOP:
 474             default:
 475                 height = calculateTabAreaHeight(tabPlacement, runCount,
 476                                                 maxTabHeight);
 477             }
 478 
 479             tabAreaBounds.setBounds(x, y, width, height);
 480 
 481             if (g.getClipBounds().intersects(tabAreaBounds)) {
 482                 paintTabArea(tabAreaContext, g, tabPlacement,
 483                          selectedIndex, tabAreaBounds);
 484             }
 485         }
 486 
 487         // Paint content border
 488         paintContentBorder(tabContentContext, g, tabPlacement, selectedIndex);
 489     }
 490 
 491     protected void paintTabArea(Graphics g, int tabPlacement,
 492                                 int selectedIndex) {
 493         // This can be invoked from ScrollabeTabPanel
 494         Insets insets = tabPane.getInsets();
 495         int x = insets.left;
 496         int y = insets.top;
 497         int width = tabPane.getWidth() - insets.left - insets.right;
 498         int height = tabPane.getHeight() - insets.top - insets.bottom;
 499 
 500         paintTabArea(tabAreaContext, g, tabPlacement, selectedIndex,
 501                      new Rectangle(x, y, width, height));
 502     }
 503 
 504     private void paintTabArea(SynthContext ss, Graphics g,
 505                                 int tabPlacement, int selectedIndex,
 506                                 Rectangle tabAreaBounds) {
 507         Rectangle clipRect = g.getClipBounds();
 508 
 509         //if the tab area's states should match that of the selected tab, then
 510         //first update the selected tab's states, then set the state
 511         //for the tab area to match
 512         //otherwise, restore the tab area's state to ENABLED (which is the
 513         //only supported state otherwise).
 514         if (tabAreaStatesMatchSelectedTab && selectedIndex >= 0) {
 515             updateTabContext(selectedIndex, true, selectedTabIsPressed,
 516                               (getRolloverTab() == selectedIndex),
 517                               (getFocusIndex() == selectedIndex));
 518             ss.setComponentState(tabContext.getComponentState());
 519         } else {
 520             ss.setComponentState(SynthConstants.ENABLED);
 521         }
 522 
 523         // Paint the tab area.
 524         SynthLookAndFeel.updateSubregion(ss, g, tabAreaBounds);
 525         ss.getPainter().paintTabbedPaneTabAreaBackground(ss, g,
 526              tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width,
 527              tabAreaBounds.height, tabPlacement);
 528         ss.getPainter().paintTabbedPaneTabAreaBorder(ss, g, tabAreaBounds.x,
 529              tabAreaBounds.y, tabAreaBounds.width, tabAreaBounds.height,
 530              tabPlacement);
 531 
 532         int tabCount = tabPane.getTabCount();
 533 
 534         iconRect.setBounds(0, 0, 0, 0);
 535         textRect.setBounds(0, 0, 0, 0);
 536 
 537         // Paint tabRuns of tabs from back to front
 538         for (int i = runCount - 1; i >= 0; i--) {
 539             int start = tabRuns[i];
 540             int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
 541             int end = (next != 0? next - 1: tabCount - 1);
 542             for (int j = start; j <= end; j++) {
 543                 if (rects[j].intersects(clipRect) && selectedIndex != j) {
 544                     paintTab(tabContext, g, tabPlacement, rects, j, iconRect,
 545                              textRect);
 546                 }
 547             }
 548         }
 549 
 550         if (selectedIndex >= 0) {
 551             if (rects[selectedIndex].intersects(clipRect)) {
 552                 paintTab(tabContext, g, tabPlacement, rects, selectedIndex,
 553                          iconRect, textRect);
 554             }
 555         }
 556     }
 557 
 558     /**
 559      * {@inheritDoc}
 560      */
 561     @Override
 562     protected void setRolloverTab(int index) {
 563         int oldRolloverTab = getRolloverTab();
 564         super.setRolloverTab(index);
 565 
 566         Rectangle r = null;
 567 
 568         if (oldRolloverTab != index && tabAreaStatesMatchSelectedTab) {
 569             //TODO need to just repaint the tab area!
 570             tabPane.repaint();
 571         } else {
 572             if ((oldRolloverTab >= 0) && (oldRolloverTab < tabPane.getTabCount())) {
 573                 r = getTabBounds(tabPane, oldRolloverTab);
 574                 if (r != null) {
 575                     tabPane.repaint(r);
 576                 }
 577             }
 578 
 579             if (index >= 0) {
 580                 r = getTabBounds(tabPane, index);
 581                 if (r != null) {
 582                     tabPane.repaint(r);
 583                 }
 584             }
 585         }
 586     }
 587 
 588     private void paintTab(SynthContext ss, Graphics g,
 589                             int tabPlacement, Rectangle[] rects, int tabIndex,
 590                             Rectangle iconRect, Rectangle textRect) {
 591         Rectangle tabRect = rects[tabIndex];
 592         int selectedIndex = tabPane.getSelectedIndex();
 593         boolean isSelected = selectedIndex == tabIndex;
 594         updateTabContext(tabIndex, isSelected, isSelected && selectedTabIsPressed,
 595                             (getRolloverTab() == tabIndex),
 596                             (getFocusIndex() == tabIndex));
 597 
 598         SynthLookAndFeel.updateSubregion(ss, g, tabRect);
 599         int x = tabRect.x;
 600         int y = tabRect.y;
 601         int height = tabRect.height;
 602         int width = tabRect.width;
 603         int placement = tabPane.getTabPlacement();
 604         if (extendTabsToBase && runCount > 1) {
 605             //paint this tab such that its edge closest to the base is equal to
 606             //edge of the selected tab closest to the base. In terms of the TOP
 607             //tab placement, this will cause the bottom of each tab to be
 608             //painted even with the bottom of the selected tab. This is because
 609             //in each tab placement (TOP, LEFT, BOTTOM, RIGHT) the selected tab
 610             //is closest to the base.
 611             if (selectedIndex >= 0) {
 612                 Rectangle r = rects[selectedIndex];
 613                 switch (placement) {
 614                     case TOP:
 615                         int bottomY = r.y + r.height;
 616                         height = bottomY - tabRect.y;
 617                         break;
 618                     case LEFT:
 619                         int rightX = r.x + r.width;
 620                         width = rightX - tabRect.x;
 621                         break;
 622                     case BOTTOM:
 623                         int topY = r.y;
 624                         height = (tabRect.y + tabRect.height) - topY;
 625                         y = topY;
 626                         break;
 627                     case RIGHT:
 628                         int leftX = r.x;
 629                         width = (tabRect.x + tabRect.width) - leftX;
 630                         x = leftX;
 631                         break;
 632                 }
 633             }
 634         }
 635         tabContext.getPainter().paintTabbedPaneTabBackground(tabContext, g,
 636                 x, y, width, height, tabIndex, placement);
 637         tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g,
 638                 x, y, width, height, tabIndex, placement);
 639 
 640         if (tabPane.getTabComponentAt(tabIndex) == null) {
 641             String title = tabPane.getTitleAt(tabIndex);
 642             Font font = ss.getStyle().getFont(ss);
 643             FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
 644             Icon icon = getIconForTab(tabIndex);
 645 
 646             layoutLabel(ss, tabPlacement, metrics, tabIndex, title, icon,
 647                     tabRect, iconRect, textRect, isSelected);
 648 
 649             paintText(ss, g, tabPlacement, font, metrics,
 650                     tabIndex, title, textRect, isSelected);
 651 
 652             paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
 653         }
 654     }
 655 
 656     private void layoutLabel(SynthContext ss, int tabPlacement,
 657                                FontMetrics metrics, int tabIndex,
 658                                String title, Icon icon,
 659                                Rectangle tabRect, Rectangle iconRect,
 660                                Rectangle textRect, boolean isSelected ) {
 661         View v = getTextViewForTab(tabIndex);
 662         if (v != null) {
 663             tabPane.putClientProperty("html", v);
 664         }
 665 
 666         textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
 667 
 668         ss.getStyle().getGraphicsUtils(ss).layoutText(ss, metrics, title,
 669                          icon, SwingUtilities.CENTER, SwingUtilities.CENTER,
 670                          SwingUtilities.LEADING, SwingUtilities.CENTER,
 671                          tabRect, iconRect, textRect, textIconGap);
 672 
 673         tabPane.putClientProperty("html", null);
 674 
 675         int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
 676         int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
 677         iconRect.x += xNudge;
 678         iconRect.y += yNudge;
 679         textRect.x += xNudge;
 680         textRect.y += yNudge;
 681     }
 682 
 683     private void paintText(SynthContext ss,
 684                              Graphics g, int tabPlacement,
 685                              Font font, FontMetrics metrics, int tabIndex,
 686                              String title, Rectangle textRect,
 687                              boolean isSelected) {
 688         g.setFont(font);
 689 
 690         View v = getTextViewForTab(tabIndex);
 691         if (v != null) {
 692             // html
 693             v.paint(g, textRect);
 694         } else {
 695             // plain text
 696             int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
 697 
 698             g.setColor(ss.getStyle().getColor(ss, ColorType.TEXT_FOREGROUND));
 699             ss.getStyle().getGraphicsUtils(ss).paintText(ss, g, title,
 700                                   textRect, mnemIndex);
 701         }
 702     }
 703 
 704 
 705     private void paintContentBorder(SynthContext ss, Graphics g,
 706                                       int tabPlacement, int selectedIndex) {
 707         int width = tabPane.getWidth();
 708         int height = tabPane.getHeight();
 709         Insets insets = tabPane.getInsets();
 710 
 711         int x = insets.left;
 712         int y = insets.top;
 713         int w = width - insets.right - insets.left;
 714         int h = height - insets.top - insets.bottom;
 715 
 716         switch(tabPlacement) {
 717           case LEFT:
 718               x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
 719               w -= (x - insets.left);
 720               break;
 721           case RIGHT:
 722               w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
 723               break;
 724           case BOTTOM:
 725               h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
 726               break;
 727           case TOP:
 728           default:
 729               y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
 730               h -= (y - insets.top);
 731         }
 732         SynthLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w, h));
 733         ss.getPainter().paintTabbedPaneContentBackground(ss, g, x, y,
 734                                                            w, h);
 735         ss.getPainter().paintTabbedPaneContentBorder(ss, g, x, y, w, h);
 736     }
 737 
 738     private void ensureCurrentLayout() {
 739         if (!tabPane.isValid()) {
 740             tabPane.validate();
 741         }
 742         /* If tabPane doesn't have a peer yet, the validate() call will
 743          * silently fail.  We handle that by forcing a layout if tabPane
 744          * is still invalid.  See bug 4237677.
 745          */
 746         if (!tabPane.isValid()) {
 747             TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
 748             layout.calculateLayoutInfo();
 749         }
 750     }
 751 
 752     /**
 753      * {@inheritDoc}
 754      */
 755     @Override
 756     protected int calculateMaxTabHeight(int tabPlacement) {
 757         FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(
 758                                              tabContext));
 759         int tabCount = tabPane.getTabCount();
 760         int result = 0;
 761         int fontHeight = metrics.getHeight();
 762         for(int i = 0; i < tabCount; i++) {
 763             result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
 764         }
 765         return result;
 766     }
 767 
 768     /**
 769      * {@inheritDoc}
 770      */
 771     @Override
 772     protected int calculateTabWidth(int tabPlacement, int tabIndex,
 773                                     FontMetrics metrics) {
 774         Icon icon = getIconForTab(tabIndex);
 775         Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
 776         int width = tabInsets.left + tabInsets.right;
 777         Component tabComponent = tabPane.getTabComponentAt(tabIndex);
 778         if (tabComponent != null) {
 779             width += tabComponent.getPreferredSize().width;
 780         } else {
 781             if (icon != null) {
 782                 width += icon.getIconWidth() + textIconGap;
 783             }
 784             View v = getTextViewForTab(tabIndex);
 785             if (v != null) {
 786                 // html
 787                 width += (int) v.getPreferredSpan(View.X_AXIS);
 788             } else {
 789                 // plain text
 790                 String title = tabPane.getTitleAt(tabIndex);
 791                 width += tabContext.getStyle().getGraphicsUtils(tabContext).
 792                         computeStringWidth(tabContext, metrics.getFont(),
 793                                 metrics, title);
 794             }
 795         }
 796         return width;
 797     }
 798 
 799     /**
 800      * {@inheritDoc}
 801      */
 802     @Override
 803     protected int calculateMaxTabWidth(int tabPlacement) {
 804         FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(
 805                                      tabContext));
 806         int tabCount = tabPane.getTabCount();
 807         int result = 0;
 808         for(int i = 0; i < tabCount; i++) {
 809             result = Math.max(calculateTabWidth(tabPlacement, i, metrics),
 810                               result);
 811         }
 812         return result;
 813     }
 814 
 815     /**
 816      * {@inheritDoc}
 817      */
 818     @Override
 819     protected Insets getTabInsets(int tabPlacement, int tabIndex) {
 820         updateTabContext(tabIndex, false, false, false,
 821                           (getFocusIndex() == tabIndex));
 822         return tabInsets;
 823     }
 824 
 825     /**
 826      * {@inheritDoc}
 827      */
 828     @Override
 829     protected FontMetrics getFontMetrics() {
 830         return getFontMetrics(tabContext.getStyle().getFont(tabContext));
 831     }
 832 
 833     private FontMetrics getFontMetrics(Font font) {
 834         return tabPane.getFontMetrics(font);
 835     }
 836 
 837     private void updateTabContext(int index, boolean selected,
 838                                   boolean isMouseDown, boolean isMouseOver, boolean hasFocus) {
 839         int state = 0;
 840         if (!tabPane.isEnabled() || !tabPane.isEnabledAt(index)) {
 841             state |= SynthConstants.DISABLED;
 842             if (selected) {
 843                 state |= SynthConstants.SELECTED;
 844             }
 845         }
 846         else if (selected) {
 847             state |= (SynthConstants.ENABLED | SynthConstants.SELECTED);
 848             if (isMouseOver && UIManager.getBoolean("TabbedPane.isTabRollover")) {
 849                 state |= SynthConstants.MOUSE_OVER;
 850             }
 851         }
 852         else if (isMouseOver) {
 853             state |= (SynthConstants.ENABLED | SynthConstants.MOUSE_OVER);
 854         }
 855         else {
 856             state = SynthLookAndFeel.getComponentState(tabPane);
 857             state &= ~SynthConstants.FOCUSED; // don't use tabbedpane focus state
 858         }
 859         if (hasFocus && tabPane.hasFocus()) {
 860             state |= SynthConstants.FOCUSED; // individual tab has focus
 861         }
 862         if (isMouseDown) {
 863             state |= SynthConstants.PRESSED;
 864         }
 865 
 866         tabContext.setComponentState(state);
 867     }
 868 
 869     /**
 870      * {@inheritDoc}
 871      *
 872      * Overridden to create a TabbedPaneLayout subclass which takes into
 873      * account tabOverlap.
 874      */
 875     @Override
 876     protected LayoutManager createLayoutManager() {
 877         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
 878             return super.createLayoutManager();
 879         } else { /* WRAP_TAB_LAYOUT */
 880             return new TabbedPaneLayout() {
 881                 @Override
 882                 public void calculateLayoutInfo() {
 883                     super.calculateLayoutInfo();
 884                     //shift all the tabs, if necessary
 885                     if (tabOverlap != 0) {
 886                         int tabCount = tabPane.getTabCount();
 887                         //left-to-right/right-to-left only affects layout
 888                         //when placement is TOP or BOTTOM
 889                         boolean ltr = tabPane.getComponentOrientation().isLeftToRight();
 890                         for (int i = runCount - 1; i >= 0; i--) {
 891                             int start = tabRuns[i];
 892                             int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
 893                             int end = (next != 0? next - 1: tabCount - 1);
 894                             for (int j = start+1; j <= end; j++) {
 895                                 // xshift and yshift represent the amount &
 896                                 // direction to shift the tab in their
 897                                 // respective axis.
 898                                 int xshift = 0;
 899                                 int yshift = 0;
 900                                 // configure xshift and y shift based on tab
 901                                 // position and ltr/rtl
 902                                 switch (tabPane.getTabPlacement()) {
 903                                     case JTabbedPane.TOP:
 904                                     case JTabbedPane.BOTTOM:
 905                                         xshift = ltr ? tabOverlap : -tabOverlap;
 906                                         break;
 907                                     case JTabbedPane.LEFT:
 908                                     case JTabbedPane.RIGHT:
 909                                         yshift = tabOverlap;
 910                                         break;
 911                                     default: //do nothing
 912                                 }
 913                                 rects[j].x += xshift;
 914                                 rects[j].y += yshift;
 915                                 rects[j].width += Math.abs(xshift);
 916                                 rects[j].height += Math.abs(yshift);
 917                             }
 918                         }
 919                     }
 920                 }
 921             };
 922         }
 923     }
 924 
 925     @SuppressWarnings("serial") // Superclass is not serializable across versions
 926     private class SynthScrollableTabButton extends SynthArrowButton implements
 927             UIResource {
 928         public SynthScrollableTabButton(int direction) {
 929             super(direction);
 930             setName("TabbedPane.button");
 931         }
 932     }
 933 }