Source for javax.swing.plaf.basic.BasicTabbedPaneUI

   1: /* BasicTabbedPaneUI.java --
   2:    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import gnu.classpath.NotImplementedException;
  42: 
  43: import java.awt.Color;
  44: import java.awt.Component;
  45: import java.awt.Container;
  46: import java.awt.Dimension;
  47: import java.awt.Font;
  48: import java.awt.FontMetrics;
  49: import java.awt.Graphics;
  50: import java.awt.Insets;
  51: import java.awt.LayoutManager;
  52: import java.awt.Point;
  53: import java.awt.Rectangle;
  54: import java.awt.event.FocusAdapter;
  55: import java.awt.event.FocusEvent;
  56: import java.awt.event.FocusListener;
  57: import java.awt.event.MouseAdapter;
  58: import java.awt.event.MouseEvent;
  59: import java.awt.event.MouseListener;
  60: import java.beans.PropertyChangeEvent;
  61: import java.beans.PropertyChangeListener;
  62: 
  63: import javax.swing.Icon;
  64: import javax.swing.JComponent;
  65: import javax.swing.JPanel;
  66: import javax.swing.JTabbedPane;
  67: import javax.swing.JViewport;
  68: import javax.swing.KeyStroke;
  69: import javax.swing.LookAndFeel;
  70: import javax.swing.SwingConstants;
  71: import javax.swing.SwingUtilities;
  72: import javax.swing.UIManager;
  73: import javax.swing.event.ChangeEvent;
  74: import javax.swing.event.ChangeListener;
  75: import javax.swing.plaf.ComponentUI;
  76: import javax.swing.plaf.PanelUI;
  77: import javax.swing.plaf.TabbedPaneUI;
  78: import javax.swing.plaf.UIResource;
  79: import javax.swing.text.View;
  80: 
  81: /**
  82:  * This is the Basic Look and Feel's UI delegate for JTabbedPane.
  83:  */
  84: public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants
  85: {
  86:   /**
  87:    * A helper class that handles focus.
  88:    *
  89:    * @specnote Apparently this class was intended to be protected,
  90:    *           but was made public by a compiler bug and is now
  91:    *           public for compatibility.
  92:    */
  93:   public class FocusHandler extends FocusAdapter
  94:   {
  95:     /**
  96:      * This method is called when the component gains focus.
  97:      *
  98:      * @param e The FocusEvent.
  99:      */
 100:     public void focusGained(FocusEvent e)
 101:     {
 102:       // FIXME: Implement.
 103:     }
 104: 
 105:     /**
 106:      * This method is called when the component loses focus.
 107:      *
 108:      * @param e The FocusEvent.
 109:      */
 110:     public void focusLost(FocusEvent e)
 111:     {
 112:       // FIXME: Implement.
 113:     }
 114:   }
 115: 
 116:   /**
 117:    * A helper class for determining if mouse presses occur inside tabs and
 118:    * sets the index appropriately. In SCROLL_TAB_MODE, this class also
 119:    * handles the mouse clicks on the scrolling buttons.
 120:    *
 121:    * @specnote Apparently this class was intended to be protected,
 122:    *           but was made public by a compiler bug and is now
 123:    *           public for compatibility.
 124:    */
 125:   public class MouseHandler extends MouseAdapter
 126:   {
 127:     /**
 128:      * This method is called when the mouse is pressed. The index cannot
 129:      * change to a tab that is  not enabled.
 130:      *
 131:      * @param e The MouseEvent.
 132:      */
 133:     public void mousePressed(MouseEvent e)
 134:     {
 135:       if (tabPane.isEnabled())
 136:         {
 137:           int index = tabForCoordinate(tabPane, e.getX(), e.getY());
 138:           if (index >= 0 && tabPane.isEnabledAt(index))
 139:             {
 140:               tabPane.setSelectedIndex(index);
 141:             }
 142:         }
 143:     }
 144: 
 145:     /**
 146:      * Receives notification when the mouse pointer has entered the tabbed
 147:      * pane.
 148:      *
 149:      * @param ev the mouse event
 150:      */
 151:     public void mouseEntered(MouseEvent ev)
 152:     {
 153:       int tabIndex = tabForCoordinate(tabPane, ev.getX(), ev.getY());
 154:       setRolloverTab(tabIndex);
 155:     }
 156: 
 157:     /**
 158:      * Receives notification when the mouse pointer has exited the tabbed
 159:      * pane.
 160:      *
 161:      * @param ev the mouse event
 162:      */
 163:     public void mouseExited(MouseEvent ev)
 164:     {
 165:       setRolloverTab(-1);
 166:     }
 167: 
 168:     /**
 169:      * Receives notification when the mouse pointer has moved over the tabbed
 170:      * pane.
 171:      *
 172:      * @param ev the mouse event
 173:      */
 174:     public void mouseMoved(MouseEvent ev)
 175:     {
 176:       int tabIndex = tabForCoordinate(tabPane, ev.getX(), ev.getY());
 177:       setRolloverTab(tabIndex);
 178:     }
 179:   }
 180: 
 181:   /**
 182:    * This class handles PropertyChangeEvents fired from the JTabbedPane.
 183:    *
 184:    * @specnote Apparently this class was intended to be protected,
 185:    *           but was made public by a compiler bug and is now
 186:    *           public for compatibility.
 187:    */
 188:   public class PropertyChangeHandler implements PropertyChangeListener
 189:   {
 190:     /**
 191:      * This method is called whenever one of the properties of the JTabbedPane
 192:      * changes.
 193:      *
 194:      * @param e The PropertyChangeEvent.
 195:      */
 196:     public void propertyChange(PropertyChangeEvent e)
 197:     {
 198:       if (e.getPropertyName().equals("tabLayoutPolicy"))
 199:         {
 200:           layoutManager = createLayoutManager();
 201:           
 202:           tabPane.setLayout(layoutManager);
 203:         }
 204:       else if (e.getPropertyName().equals("tabPlacement")
 205:           && tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
 206:         {
 207:           incrButton = createIncreaseButton();
 208:           decrButton = createDecreaseButton();
 209:         }
 210:       tabPane.revalidate();
 211:       tabPane.repaint();
 212:     }
 213:   }
 214: 
 215:   /**
 216:    * A LayoutManager responsible for placing all the tabs and the visible
 217:    * component inside the JTabbedPane. This class is only used for
 218:    * WRAP_TAB_LAYOUT.
 219:    *
 220:    * @specnote Apparently this class was intended to be protected,
 221:    *           but was made public by a compiler bug and is now
 222:    *           public for compatibility.
 223:    */
 224:   public class TabbedPaneLayout implements LayoutManager
 225:   {
 226:     /**
 227:      * This method is called when a component is added to the JTabbedPane.
 228:      *
 229:      * @param name The name of the component.
 230:      * @param comp The component being added.
 231:      */
 232:     public void addLayoutComponent(String name, Component comp)
 233:     {
 234:       // Do nothing.
 235:     }
 236: 
 237:     /**
 238:      * This method is called when the rectangles need to be calculated. It
 239:      * also fixes the size of the visible component.
 240:      */
 241:     public void calculateLayoutInfo()
 242:     {
 243:       int count = tabPane.getTabCount();
 244:       assureRectsCreated(count);
 245:       calculateTabRects(tabPane.getTabPlacement(), count);
 246:       tabRunsDirty = false;
 247:     }
 248: 
 249:     /**
 250:      * This method calculates the size of the the JTabbedPane.
 251:      *
 252:      * @param minimum Whether the JTabbedPane will try to be as small as it
 253:      *        can.
 254:      *
 255:      * @return The desired size of the JTabbedPane.
 256:      */
 257:     protected Dimension calculateSize(boolean minimum)
 258:     {
 259:       int tabPlacement = tabPane.getTabPlacement();
 260: 
 261:       int width = 0;
 262:       int height = 0;
 263:       Component c;
 264:       Dimension dims;
 265: 
 266:       // Find out the minimum/preferred size to display the largest child
 267:       // of the tabbed pane.
 268:       for (int i = 0; i < tabPane.getTabCount(); i++)
 269:         {
 270:           c = tabPane.getComponentAt(i);
 271:           if (c == null)
 272:             continue;
 273:           dims = minimum ? c.getMinimumSize() : c.getPreferredSize(); 
 274:           if (dims != null)
 275:             {
 276:               height = Math.max(height, dims.height);
 277:               width = Math.max(width, dims.width);
 278:             }
 279:         }
 280: 
 281:       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
 282:       if (tabPlacement == SwingConstants.TOP
 283:           || tabPlacement == SwingConstants.BOTTOM)
 284:         {
 285:           int min = calculateMaxTabWidth(tabPlacement);
 286:           width = Math.max(min, width);
 287:           int tabAreaHeight = preferredTabAreaHeight(tabPlacement,
 288:                                                      width - tabAreaInsets.left
 289:                                                      -tabAreaInsets.right);
 290:           height += tabAreaHeight;
 291:         }
 292:       else
 293:         {
 294:           int min = calculateMaxTabHeight(tabPlacement);
 295:           height = Math.max(min, height);
 296:           int tabAreaWidth = preferredTabAreaWidth(tabPlacement,
 297:                                                    height - tabAreaInsets.top
 298:                                                    - tabAreaInsets.bottom);
 299:           width += tabAreaWidth;
 300:         }
 301: 
 302:       Insets tabPaneInsets = tabPane.getInsets();
 303:       return new Dimension(width + tabPaneInsets.left + tabPaneInsets.right,
 304:                            height + tabPaneInsets.top + tabPaneInsets.bottom);
 305:     }
 306: 
 307:     // if tab placement is LEFT OR RIGHT, they share width.
 308:     // if tab placement is TOP OR BOTTOM, they share height
 309:     // PRE STEP: finds the default sizes for the labels as well as their locations.
 310:     // AND where they will be placed within the run system.
 311:     // 1. calls normalizeTab Runs.
 312:     // 2. calls rotate tab runs.
 313:     // 3. pads the tab runs.
 314:     // 4. pads the selected tab.
 315: 
 316:     /**
 317:      * This method is called to calculate the tab rectangles.  This method
 318:      * will calculate the size and position of all  rectangles (taking into
 319:      * account which ones should be in which tab run). It will pad them and
 320:      * normalize them  as necessary.
 321:      *
 322:      * @param tabPlacement The JTabbedPane's tab placement.
 323:      * @param tabCount The run the current selection is in.
 324:      */
 325:     protected void calculateTabRects(int tabPlacement, int tabCount)
 326:     {
 327:       Insets insets = tabPane.getInsets();
 328:       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
 329:       Dimension size = tabPane.getSize();
 330:       
 331:       // The coordinates of the upper left corner of the tab area.
 332:       int x;
 333:       int y;
 334:       // The location at which the runs must be broken.
 335:       int breakAt;
 336: 
 337:       // Calculate the bounds for the tab area.
 338:       switch (tabPlacement)
 339:       {
 340:         case LEFT:
 341:           maxTabWidth = calculateMaxTabWidth(tabPlacement);
 342:           x = insets.left + tabAreaInsets.left;
 343:           y = insets.top + tabAreaInsets.top;
 344:           breakAt = size.height - (insets.bottom + tabAreaInsets.bottom);
 345:           break;
 346:         case RIGHT:
 347:           maxTabWidth = calculateMaxTabWidth(tabPlacement);
 348:           x = size.width - (insets.right + tabAreaInsets.right) - maxTabWidth;
 349:           y = insets.top + tabAreaInsets.top;
 350:           breakAt = size.height - (insets.bottom + tabAreaInsets.bottom);
 351:           break;
 352:         case BOTTOM:
 353:           maxTabHeight = calculateMaxTabHeight(tabPlacement);
 354:           x = insets.left + tabAreaInsets.left;
 355:           y = size.height - (insets.bottom + tabAreaInsets.bottom)
 356:               - maxTabHeight;
 357:           breakAt = size.width - (insets.right + tabAreaInsets.right);
 358:           break;
 359:         case TOP:
 360:         default:
 361:           maxTabHeight = calculateMaxTabHeight(tabPlacement);
 362:           x = insets.left + tabAreaInsets.left;
 363:           y = insets.top + tabAreaInsets.top;
 364:           breakAt = size.width - (insets.right + tabAreaInsets.right);
 365:           break;
 366:       }
 367: 
 368:       if (tabCount == 0)
 369:         return;
 370: 
 371:       FontMetrics fm = getFontMetrics();
 372:       runCount = 0;
 373:       selectedRun = -1;
 374:       int selectedIndex = tabPane.getSelectedIndex();
 375: 
 376:       Rectangle rect;
 377: 
 378:       // Go through all the tabs and build the tab runs.
 379:       if (tabPlacement == SwingConstants.TOP
 380:           || tabPlacement == SwingConstants.BOTTOM)
 381:         {
 382:           for (int i = 0; i < tabCount; i++)
 383:             {
 384:               rect = rects[i];
 385:               if (i > 0)
 386:                 {
 387:                   rect.x = rects[i - 1].x + rects[i - 1].width;
 388:                 }
 389:               else
 390:                 {
 391:                   tabRuns[0] = 0;
 392:                   runCount = 1;
 393:                   maxTabWidth = 0;
 394:                   rect.x = x;
 395:                 }
 396:               rect.width = calculateTabWidth(tabPlacement, i, fm);
 397:               maxTabWidth = Math.max(maxTabWidth, rect.width);
 398: 
 399:               if (rect.x != 2 + insets.left && rect.x + rect.width > breakAt)
 400:                 {
 401:                   if (runCount > tabRuns.length - 1)
 402:                     expandTabRunsArray();
 403:                   tabRuns[runCount] = i;
 404:                   runCount++;
 405:                   rect.x = x;
 406:                 }
 407: 
 408:               rect.y = y;
 409:               rect.height = maxTabHeight;
 410:               if (i == selectedIndex)
 411:                 selectedRun = runCount - 1;
 412:                 
 413:             }
 414:         }
 415:       else
 416:         {
 417:           for (int i = 0; i < tabCount; i++)
 418:             {
 419:               rect = rects[i];
 420:               if (i > 0)
 421:                 {
 422:                   rect.y = rects[i - 1].y + rects[i - 1].height;
 423:                 }
 424:               else
 425:                 {
 426:                   tabRuns[0] = 0;
 427:                   runCount = 1;
 428:                   maxTabHeight = 0;
 429:                   rect.y = y;
 430:                 }
 431:               rect.height = calculateTabHeight(tabPlacement, i,
 432:                                                fm.getHeight());
 433:               maxTabHeight = Math.max(maxTabHeight, rect.height);
 434: 
 435:               if (rect.y != 2 + insets.top && rect.y + rect.height > breakAt)
 436:                 {
 437:                   if (runCount > tabRuns.length - 1)
 438:                     expandTabRunsArray();
 439:                   tabRuns[runCount] = i;
 440:                   runCount++;
 441:                   rect.y = y;
 442:                 }
 443: 
 444:               rect.x = x;
 445:               rect.width = maxTabWidth;
 446: 
 447:               if (i == selectedIndex)
 448:                 selectedRun = runCount - 1;
 449:             }
 450:         }
 451: 
 452:       if (runCount > 1)
 453:         {
 454:           int start;
 455:           if  (tabPlacement == SwingConstants.TOP
 456:               || tabPlacement == SwingConstants.BOTTOM)
 457:             start = y;
 458:           else
 459:             start = x;
 460:           normalizeTabRuns(tabPlacement, tabCount, start, breakAt);
 461:           selectedRun = getRunForTab(tabCount, selectedIndex);
 462:           if (shouldRotateTabRuns(tabPlacement))
 463:             {
 464:               rotateTabRuns(tabPlacement, selectedRun);
 465:             }
 466:         }
 467: 
 468:       // Pad the runs.
 469:       int tabRunOverlay = getTabRunOverlay(tabPlacement);
 470:       for (int i = runCount - 1; i >= 0; --i)
 471:         {
 472:           int start = tabRuns[i];
 473:           int nextIndex;
 474:           if (i == runCount - 1)
 475:             nextIndex = 0;
 476:           else
 477:             nextIndex = i + 1;
 478:           int next = tabRuns[nextIndex];
 479:           int end = (next != 0 ? next - 1 : tabCount - 1);
 480:           if (tabPlacement == SwingConstants.TOP
 481:               || tabPlacement == SwingConstants.BOTTOM)
 482:             {
 483:               for (int j = start; j <= end; ++j)
 484:                 {
 485:                   rect = rects[j];
 486:                   rect.y = y;
 487:                   rect.x += getTabRunIndent(tabPlacement, i);
 488:                 }
 489:               if (shouldPadTabRun(tabPlacement, i))
 490:                 {
 491:                   padTabRun(tabPlacement, start, end, breakAt);
 492:                 }
 493:               if (tabPlacement == BOTTOM)
 494:                 y -= (maxTabHeight - tabRunOverlay);
 495:               else
 496:                 y += (maxTabHeight - tabRunOverlay);
 497:             }
 498:           else
 499:             {
 500:               for (int j = start; j <= end; ++j)
 501:                 {
 502:                   rect = rects[j];
 503:                   rect.x = x;
 504:                   rect.y += getTabRunIndent(tabPlacement, i);
 505:                 }
 506:               if (shouldPadTabRun(tabPlacement, i))
 507:                 {
 508:                   padTabRun(tabPlacement, start, end, breakAt);
 509:                 }
 510:               if (tabPlacement == RIGHT)
 511:                 x -= (maxTabWidth - tabRunOverlay);
 512:               else
 513:                 x += (maxTabWidth - tabRunOverlay);
 514:               
 515:             }
 516:         }
 517:       padSelectedTab(tabPlacement, selectedIndex);
 518:     }
 519: 
 520:     /**
 521:      * This method is called when the JTabbedPane is laid out in
 522:      * WRAP_TAB_LAYOUT. It calls calculateLayoutInfo to  find the positions
 523:      * of all its components.
 524:      *
 525:      * @param parent The Container to lay out.
 526:      */
 527:     public void layoutContainer(Container parent)
 528:     {
 529:       calculateLayoutInfo();
 530: 
 531:       int tabPlacement = tabPane.getTabPlacement();
 532:       Insets insets = tabPane.getInsets();
 533:       int childCount = tabPane.getComponentCount();
 534:       if (childCount > 0)
 535:         {
 536:           int compX;
 537:           int compY;
 538:           int tabAreaWidth = 0;
 539:           int tabAreaHeight = 0;
 540:           switch (tabPlacement)
 541:           {
 542:             case LEFT:
 543:               tabAreaWidth = calculateTabAreaWidth(tabPlacement, runCount,
 544:                                                    maxTabWidth);
 545:               compX = tabAreaWidth + insets.left + contentBorderInsets.left;
 546:               compY = insets.top + contentBorderInsets.top;
 547:               break;
 548:             case RIGHT:
 549:               tabAreaWidth = calculateTabAreaWidth(tabPlacement, runCount,
 550:                                                    maxTabWidth);
 551:               compX = insets.left + contentBorderInsets.left;
 552:               compY = insets.top + contentBorderInsets.top;
 553:               break;
 554:             case BOTTOM: 
 555:               tabAreaHeight = calculateTabAreaHeight(tabPlacement, runCount,
 556:                                                      maxTabHeight);
 557:               compX = insets.left + contentBorderInsets.left;
 558:               compY = insets.top + contentBorderInsets.top;
 559:               break;
 560:             case TOP:
 561:             default:
 562:               tabAreaHeight = calculateTabAreaHeight(tabPlacement, runCount,
 563:                                                      maxTabHeight);
 564:               compX = insets.left + contentBorderInsets.left;
 565:               compY = tabAreaHeight + insets.top + contentBorderInsets.top;
 566:           }
 567:           Rectangle bounds = tabPane.getBounds();
 568:           int compWidth = bounds.width - tabAreaWidth - insets.left
 569:                           - insets.right - contentBorderInsets.left
 570:                           - contentBorderInsets.right;
 571:           int compHeight = bounds.height - tabAreaHeight - insets.top
 572:                            - insets.bottom - contentBorderInsets.top
 573:                            - contentBorderInsets.bottom;
 574: 
 575: 
 576:           for (int i = 0; i < childCount; ++i)
 577:             {
 578:               Component c = tabPane.getComponent(i);
 579:               c.setBounds(compX, compY, compWidth, compHeight);
 580:             }
 581:         }
 582:     }
 583: 
 584:     /**
 585:      * This method returns the minimum layout size for the given container.
 586:      *
 587:      * @param parent The container that is being sized.
 588:      *
 589:      * @return The minimum size.
 590:      */
 591:     public Dimension minimumLayoutSize(Container parent)
 592:     {
 593:       return calculateSize(false);
 594:     }
 595: 
 596:     // If there is more free space in an adjacent run AND the tab in the run can fit in the 
 597:     // adjacent run, move it. This method is not perfect, it is merely an approximation.
 598:     // If you play around with Sun's JTabbedPane, you'll see that 
 599:     // it does do some pretty strange things with regards to not moving tabs 
 600:     // that should be moved. 
 601:     // start = the x position where the tabs will begin
 602:     // max = the maximum position of where the tabs can go to (tabAreaInsets.left + the width of the tab area)
 603: 
 604:     /**
 605:      * This method tries to "even out" the number of tabs in each run based on
 606:      * their widths.
 607:      *
 608:      * @param tabPlacement The JTabbedPane's tab placement.
 609:      * @param tabCount The number of tabs.
 610:      * @param start The x position where the tabs will begin.
 611:      * @param max The maximum x position where the tab can run to.
 612:      */
 613:     protected void normalizeTabRuns(int tabPlacement, int tabCount, int start,
 614:                                     int max)
 615:     {
 616:       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
 617:       if (tabPlacement == SwingUtilities.TOP
 618:           || tabPlacement == SwingUtilities.BOTTOM)
 619:         {
 620:           // We should only do this for runCount - 1, cause we can only shift that many times between
 621:           // runs.
 622:           for (int i = 1; i < runCount; i++)
 623:             {
 624:               Rectangle currRun = rects[lastTabInRun(tabCount, i)];
 625:               Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))];
 626:               int spaceInCurr = currRun.x + currRun.width;
 627:               int spaceInNext = nextRun.x + nextRun.width;
 628: 
 629:               int diffNow = spaceInCurr - spaceInNext;
 630:               int diffLater = (spaceInCurr - currRun.width)
 631:               - (spaceInNext + currRun.width);
 632:               while (Math.abs(diffLater) < Math.abs(diffNow)
 633:                   && spaceInNext + currRun.width < max)
 634:                 {
 635:                   tabRuns[i]--;
 636:                   spaceInNext += currRun.width;
 637:                   spaceInCurr -= currRun.width;
 638:                   currRun = rects[lastTabInRun(tabCount, i)];
 639:                   diffNow = spaceInCurr - spaceInNext;
 640:                   diffLater = (spaceInCurr - currRun.width)
 641:                   - (spaceInNext + currRun.width);
 642:                 }
 643: 
 644:               // Fix the bounds.
 645:               int first = lastTabInRun(tabCount, i) + 1;
 646:               int last = lastTabInRun(tabCount, getNextTabRun(i));
 647:               int currX = tabAreaInsets.left;
 648:               for (int j = first; j <= last; j++)
 649:                 {
 650:                   rects[j].x = currX;
 651:                   currX += rects[j].width;
 652:                 }
 653:             }
 654:         }
 655:       else
 656:         {
 657:           for (int i = 1; i < runCount; i++)
 658:             {
 659:               Rectangle currRun = rects[lastTabInRun(tabCount, i)];
 660:               Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))];
 661:               int spaceInCurr = currRun.y + currRun.height;
 662:               int spaceInNext = nextRun.y + nextRun.height;
 663: 
 664:               int diffNow = spaceInCurr - spaceInNext;
 665:               int diffLater = (spaceInCurr - currRun.height)
 666:               - (spaceInNext + currRun.height);
 667:               while (Math.abs(diffLater) < Math.abs(diffNow)
 668:                   && spaceInNext + currRun.height < max)
 669:                 {
 670:                   tabRuns[i]--;
 671:                   spaceInNext += currRun.height;
 672:                   spaceInCurr -= currRun.height;
 673:                   currRun = rects[lastTabInRun(tabCount, i)];
 674:                   diffNow = spaceInCurr - spaceInNext;
 675:                   diffLater = (spaceInCurr - currRun.height)
 676:                   - (spaceInNext + currRun.height);
 677:                 }
 678: 
 679:               int first = lastTabInRun(tabCount, i) + 1;
 680:               int last = lastTabInRun(tabCount, getNextTabRun(i));
 681:               int currY = tabAreaInsets.top;
 682:               for (int j = first; j <= last; j++)
 683:                 {
 684:                   rects[j].y = currY;
 685:                   currY += rects[j].height;
 686:                 }
 687:             }
 688:         }
 689:     }
 690: 
 691:     /**
 692:      * This method pads the tab at the selected index by the  selected tab pad
 693:      * insets (so that it looks larger).
 694:      *
 695:      * @param tabPlacement The placement of the tabs.
 696:      * @param selectedIndex The selected index.
 697:      */
 698:     protected void padSelectedTab(int tabPlacement, int selectedIndex)
 699:     {
 700:       Insets insets = getSelectedTabPadInsets(tabPlacement);
 701:       rects[selectedIndex].x -= insets.left;
 702:       rects[selectedIndex].y -= insets.top;
 703:       rects[selectedIndex].width += insets.left + insets.right;
 704:       rects[selectedIndex].height += insets.top + insets.bottom;
 705:     }
 706: 
 707:     // If the tabs on the run don't fill the width of the window, make it fit now.
 708:     // start = starting index of the run
 709:     // end = last index of the run
 710:     // max = tabAreaInsets.left + width (or equivalent)
 711:     // assert start <= end.
 712: 
 713:     /**
 714:      * This method makes each tab in the run larger so that the  tabs expand
 715:      * to fill the runs width/height (depending on tabPlacement).
 716:      *
 717:      * @param tabPlacement The placement of the tabs.
 718:      * @param start The index of the first tab.
 719:      * @param end The last index of the tab
 720:      * @param max The amount of space in the run (width for TOP and BOTTOM
 721:      *        tabPlacement).
 722:      */
 723:     protected void padTabRun(int tabPlacement, int start, int end, int max)
 724:     {
 725:       if (tabPlacement == SwingConstants.TOP
 726:           || tabPlacement == SwingConstants.BOTTOM)
 727:         {
 728:           int runWidth = rects[end].x + rects[end].width;
 729:           int spaceRemaining = max - runWidth;
 730:           int numTabs = end - start + 1;
 731: 
 732:           // now divvy up the space.
 733:           int spaceAllocated = spaceRemaining / numTabs;
 734:           int currX = rects[start].x;
 735:           for (int i = start; i <= end; i++)
 736:             {
 737:               rects[i].x = currX;
 738:               rects[i].width += spaceAllocated;
 739:               currX += rects[i].width;
 740:               // This is used because since the spaceAllocated 
 741:               // variable is an int, it rounds down. Sometimes,
 742:               // we don't fill an entire row, so we make it do
 743:               // so now.
 744:               if (i == end && rects[i].x + rects[i].width != max)
 745:                 rects[i].width = max - rects[i].x;
 746:             }
 747:         }
 748:       else
 749:         {
 750:           int runHeight = rects[end].y + rects[end].height;
 751:           int spaceRemaining = max - runHeight;
 752:           int numTabs = end - start + 1;
 753: 
 754:           int spaceAllocated = spaceRemaining / numTabs;
 755:           int currY = rects[start].y;
 756:           for (int i = start; i <= end; i++)
 757:             {
 758:               rects[i].y = currY;
 759:               rects[i].height += spaceAllocated;
 760:               currY += rects[i].height;
 761:               if (i == end && rects[i].y + rects[i].height != max)
 762:                 rects[i].height = max - rects[i].y;
 763:             }
 764:         }
 765:     }
 766: 
 767:     /**
 768:      * This method returns the preferred layout size for the given container.
 769:      *
 770:      * @param parent The container to size.
 771:      *
 772:      * @return The preferred layout size.
 773:      */
 774:     public Dimension preferredLayoutSize(Container parent)
 775:     {
 776:       return calculateSize(false);
 777:     }
 778: 
 779:     /**
 780:      * This method returns the preferred tab height given a tabPlacement and
 781:      * width.
 782:      *
 783:      * @param tabPlacement The JTabbedPane's tab placement.
 784:      * @param width The expected width.
 785:      *
 786:      * @return The preferred tab area height.
 787:      */
 788:     protected int preferredTabAreaHeight(int tabPlacement, int width)
 789:     {
 790:       if (tabPane.getTabCount() == 0)
 791:         return calculateTabAreaHeight(tabPlacement, 0, 0);
 792: 
 793:       int runs = 0;
 794:       int runWidth = 0;
 795:       int tabWidth = 0;
 796: 
 797:       FontMetrics fm = getFontMetrics();
 798: 
 799:       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
 800:       Insets insets = tabPane.getInsets();
 801: 
 802:       // Only interested in width, this is a messed up rectangle now.
 803:       width -= tabAreaInsets.left + tabAreaInsets.right + insets.left
 804:       + insets.right;
 805: 
 806:       // The reason why we can't use runCount:
 807:       // This method is only called to calculate the size request
 808:       // for the tabbedPane. However, this size request is dependent on 
 809:       // our desired width. We need to find out what the height would
 810:       // be IF we got our desired width.
 811:       for (int i = 0; i < tabPane.getTabCount(); i++)
 812:         {
 813:           tabWidth = calculateTabWidth(tabPlacement, i, fm);
 814:           if (runWidth + tabWidth > width)
 815:             {
 816:               runWidth = tabWidth;
 817:               runs++;
 818:             }
 819:           else
 820:             runWidth += tabWidth;
 821:         }
 822:       runs++;
 823: 
 824:       int maxTabHeight = calculateMaxTabHeight(tabPlacement);
 825:       int tabAreaHeight = calculateTabAreaHeight(tabPlacement, runs,
 826:                                                  maxTabHeight);
 827:       return tabAreaHeight;
 828:     }
 829: 
 830:     /**
 831:      * This method calculates the preferred tab area width given a tab
 832:      * placement and height.
 833:      *
 834:      * @param tabPlacement The JTabbedPane's tab placement.
 835:      * @param height The expected height.
 836:      *
 837:      * @return The preferred tab area width.
 838:      */
 839:     protected int preferredTabAreaWidth(int tabPlacement, int height)
 840:     {
 841:       if (tabPane.getTabCount() == 0)
 842:         return calculateTabAreaHeight(tabPlacement, 0, 0);
 843: 
 844:       int runs = 0;
 845:       int runHeight = 0;
 846:       int tabHeight = 0;
 847: 
 848:       FontMetrics fm = getFontMetrics();
 849: 
 850:       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
 851:       Insets insets = tabPane.getInsets();
 852: 
 853:       height -= tabAreaInsets.top + tabAreaInsets.bottom + insets.top
 854:       + insets.bottom;
 855:       int fontHeight = fm.getHeight();
 856: 
 857:       for (int i = 0; i < tabPane.getTabCount(); i++)
 858:         {
 859:           tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
 860:           if (runHeight + tabHeight > height)
 861:             {
 862:               runHeight = tabHeight;
 863:               runs++;
 864:             }
 865:           else
 866:             runHeight += tabHeight;
 867:         }
 868:       runs++;
 869: 
 870:       int maxTabWidth = calculateMaxTabWidth(tabPlacement);
 871:       int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, maxTabWidth);
 872:       return tabAreaWidth;
 873:     }
 874: 
 875:     /**
 876:      * This method rotates the places each run in the correct place  the
 877:      * tabRuns array. See the comment for tabRuns for how the runs are placed
 878:      * in the array.
 879:      *
 880:      * @param tabPlacement The JTabbedPane's tab placement.
 881:      * @param selectedRun The run the current selection is in.
 882:      */
 883:     protected void rotateTabRuns(int tabPlacement, int selectedRun)
 884:     {
 885:       if (runCount == 1 || selectedRun == 1 || selectedRun == -1)
 886:         return;
 887:       int[] newTabRuns = new int[tabRuns.length];
 888:       int currentRun = selectedRun;
 889:       int i = 1;
 890:       do
 891:         {
 892:           newTabRuns[i] = tabRuns[currentRun];
 893:           currentRun = getNextTabRun(currentRun);
 894:           i++;
 895:         }
 896:       while (i < runCount);
 897:       if (runCount > 1)
 898:         newTabRuns[0] = tabRuns[currentRun];
 899: 
 900:       tabRuns = newTabRuns;
 901:       BasicTabbedPaneUI.this.selectedRun = 1;
 902:     }
 903: 
 904:     /**
 905:      * This method is called when a component is removed  from the
 906:      * JTabbedPane.
 907:      *
 908:      * @param comp The component removed.
 909:      */
 910:     public void removeLayoutComponent(Component comp)
 911:     {
 912:       // Do nothing.
 913:     }
 914:   }
 915: 
 916:   /**
 917:    * This class acts as the LayoutManager for the JTabbedPane in
 918:    * SCROLL_TAB_MODE.
 919:    */
 920:   private class TabbedPaneScrollLayout extends TabbedPaneLayout
 921:   {
 922:     /**
 923:      * This method returns the preferred layout size for the given container.
 924:      *
 925:      * @param parent The container to calculate a size for.
 926:      *
 927:      * @return The preferred layout size.
 928:      */
 929:     public Dimension preferredLayoutSize(Container parent)
 930:     {
 931:       return super.calculateSize(true);
 932:     }
 933: 
 934:     /**
 935:      * This method returns the minimum layout size for the given container.
 936:      *
 937:      * @param parent The container to calculate a size for.
 938:      *
 939:      * @return The minimum layout size.
 940:      */
 941:     public Dimension minimumLayoutSize(Container parent)
 942:     {
 943:       return super.calculateSize(true);
 944:     }
 945: 
 946:     /**
 947:      * This method calculates the tab area height given  a desired width.
 948:      *
 949:      * @param tabPlacement The JTabbedPane's tab placement.
 950:      * @param width The expected width.
 951:      *
 952:      * @return The tab area height given the width.
 953:      */
 954:     protected int preferredTabAreaHeight(int tabPlacement, int width)
 955:     {
 956:       if (tabPane.getTabCount() == 0)
 957:         return calculateTabAreaHeight(tabPlacement, 0, 0);
 958: 
 959:       int runs = 1;
 960: 
 961:       int maxTabHeight = calculateMaxTabHeight(tabPlacement);
 962:       int tabAreaHeight = calculateTabAreaHeight(tabPlacement, runs,
 963:                                                  maxTabHeight);
 964:       return tabAreaHeight;
 965:     }
 966: 
 967:     /**
 968:      * This method calculates the tab area width given a desired height.
 969:      *
 970:      * @param tabPlacement The JTabbedPane's tab placement.
 971:      * @param height The expected height.
 972:      *
 973:      * @return The tab area width given the height.
 974:      */
 975:     protected int preferredTabAreaWidth(int tabPlacement, int height)
 976:     {
 977:       if (tabPane.getTabCount() == 0)
 978:         return calculateTabAreaHeight(tabPlacement, 0, 0);
 979: 
 980:       int runs = 1;
 981: 
 982:       int maxTabWidth = calculateMaxTabWidth(tabPlacement);
 983:       int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, maxTabWidth);
 984:       return tabAreaWidth;
 985:     }
 986: 
 987:     /**
 988:      * This method is called to calculate the tab rectangles.  This method
 989:      * will calculate the size and position of all  rectangles (taking into
 990:      * account which ones should be in which tab run). It will pad them and
 991:      * normalize them  as necessary.
 992:      *
 993:      * @param tabPlacement The JTabbedPane's tab placement.
 994:      * @param tabCount The number of tabs.
 995:      */
 996:     protected void calculateTabRects(int tabPlacement, int tabCount)
 997:     {
 998:       if (tabCount == 0)
 999:         return;
1000: 
1001:       FontMetrics fm = getFontMetrics();
1002:       SwingUtilities.calculateInnerArea(tabPane, calcRect);
1003:       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1004:       Insets insets = tabPane.getInsets();
1005:       int runs = 1;
1006:       int start = 0;
1007:       int top = 0;
1008:       if (tabPlacement == SwingConstants.TOP
1009:           || tabPlacement == SwingConstants.BOTTOM)
1010:         {
1011:           int maxHeight = calculateMaxTabHeight(tabPlacement);
1012:           calcRect.width -= tabAreaInsets.left + tabAreaInsets.right;
1013:           start = tabAreaInsets.left + insets.left;
1014:           int width = 0;
1015:           int runWidth = start;
1016:           top = insets.top + tabAreaInsets.top;
1017:           for (int i = 0; i < tabCount; i++)
1018:             {
1019:               width = calculateTabWidth(tabPlacement, i, fm);
1020: 
1021:               rects[i] = new Rectangle(runWidth, top, width, maxHeight);
1022:               runWidth += width;
1023:             }
1024:           tabAreaRect.width = tabPane.getWidth() - insets.left - insets.right;
1025:           tabAreaRect.height = runs * maxTabHeight
1026:           - (runs - 1) * tabRunOverlay
1027:           + tabAreaInsets.top + tabAreaInsets.bottom;
1028:           contentRect.width = tabAreaRect.width;
1029:           contentRect.height = tabPane.getHeight() - insets.top
1030:           - insets.bottom - tabAreaRect.height;
1031:           contentRect.x = insets.left;
1032:           tabAreaRect.x = insets.left;
1033:           if (tabPlacement == SwingConstants.BOTTOM)
1034:             {
1035:               contentRect.y = insets.top;
1036:               tabAreaRect.y = contentRect.y + contentRect.height;
1037:             }
1038:           else
1039:             {
1040:               tabAreaRect.y = insets.top;
1041:               contentRect.y = tabAreaRect.y + tabAreaRect.height;
1042:             }
1043:         }
1044:       else
1045:         {
1046:           int maxWidth = calculateMaxTabWidth(tabPlacement);
1047: 
1048:           calcRect.height -= tabAreaInsets.top + tabAreaInsets.bottom;
1049:           int height = 0;
1050:           start = tabAreaInsets.top + insets.top;
1051:           int runHeight = start;
1052:           int fontHeight = fm.getHeight();
1053:           top = insets.left + tabAreaInsets.left;
1054:           for (int i = 0; i < tabCount; i++)
1055:             {
1056:               height = calculateTabHeight(tabPlacement, i, fontHeight);
1057:               rects[i] = new Rectangle(top, runHeight, maxWidth, height);
1058:               runHeight += height;
1059:             }
1060:           tabAreaRect.width = runs * maxTabWidth - (runs - 1) * tabRunOverlay
1061:           + tabAreaInsets.left + tabAreaInsets.right;
1062:           tabAreaRect.height = tabPane.getHeight() - insets.top
1063:           - insets.bottom;
1064:           tabAreaRect.y = insets.top;
1065:           contentRect.width = tabPane.getWidth() - insets.left - insets.right
1066:           - tabAreaRect.width;
1067:           contentRect.height = tabAreaRect.height;
1068:           contentRect.y = insets.top;
1069:           if (tabPlacement == SwingConstants.LEFT)
1070:             {
1071:               tabAreaRect.x = insets.left;
1072:               contentRect.x = tabAreaRect.x + tabAreaRect.width;
1073:             }
1074:           else
1075:             {
1076:               contentRect.x = insets.left;
1077:               tabAreaRect.x = contentRect.x + contentRect.width;
1078:             }
1079:         }
1080:       runCount = runs;
1081:       if (runCount > tabRuns.length)
1082:         expandTabRunsArray();
1083: 
1084:       padSelectedTab(tabPlacement, tabPane.getSelectedIndex());
1085:     }
1086: 
1087:     /**
1088:      * This method is called when the JTabbedPane is laid out in
1089:      * SCROLL_TAB_LAYOUT. It finds the position for all components in the
1090:      * JTabbedPane.
1091:      *
1092:      * @param pane The JTabbedPane to be laid out.
1093:      */
1094:     public void layoutContainer(Container pane)
1095:     {
1096:       super.layoutContainer(pane);
1097:       int tabCount = tabPane.getTabCount();
1098:       Point p = null;
1099:       if (tabCount == 0)
1100:         return;
1101:       int tabPlacement = tabPane.getTabPlacement();
1102:       incrButton.setVisible(false);
1103:       decrButton.setVisible(false);
1104:       if (tabPlacement == SwingConstants.TOP
1105:           || tabPlacement == SwingConstants.BOTTOM)
1106:         {
1107:           if (tabAreaRect.x + tabAreaRect.width < rects[tabCount - 1].x
1108:               + rects[tabCount - 1].width)
1109:             {
1110:               Dimension incrDims = incrButton.getPreferredSize();
1111:               Dimension decrDims = decrButton.getPreferredSize();
1112: 
1113:               decrButton.setBounds(tabAreaRect.x + tabAreaRect.width
1114:                                    - incrDims.width - decrDims.width,
1115:                                    tabAreaRect.y, decrDims.width,
1116:                                    tabAreaRect.height);
1117:               incrButton.setBounds(tabAreaRect.x + tabAreaRect.width
1118:                                    - incrDims.width, tabAreaRect.y,
1119:                                    decrDims.width, tabAreaRect.height);
1120: 
1121:               tabAreaRect.width -= decrDims.width + incrDims.width;
1122:               incrButton.setVisible(true);
1123:               decrButton.setVisible(true);
1124:             }
1125:         }
1126: 
1127:       if (tabPlacement == SwingConstants.LEFT
1128:           || tabPlacement == SwingConstants.RIGHT)
1129:         {
1130:           if (tabAreaRect.y + tabAreaRect.height < rects[tabCount - 1].y
1131:               + rects[tabCount - 1].height)
1132:             {
1133:               Dimension incrDims = incrButton.getPreferredSize();
1134:               Dimension decrDims = decrButton.getPreferredSize();
1135: 
1136:               decrButton.setBounds(tabAreaRect.x,
1137:                                    tabAreaRect.y + tabAreaRect.height
1138:                                    - incrDims.height - decrDims.height,
1139:                                    tabAreaRect.width, decrDims.height);
1140:               incrButton.setBounds(tabAreaRect.x,
1141:                                    tabAreaRect.y + tabAreaRect.height
1142:                                    - incrDims.height, tabAreaRect.width,
1143:                                    incrDims.height);
1144: 
1145:               tabAreaRect.height -= decrDims.height + incrDims.height;
1146:               incrButton.setVisible(true);
1147:               decrButton.setVisible(true);
1148:             }
1149:         }
1150:       viewport.setBounds(tabAreaRect.x, tabAreaRect.y, tabAreaRect.width,
1151:                          tabAreaRect.height);
1152:       int tabC = tabPane.getTabCount() - 1;
1153:       if (tabCount > 0)
1154:         {
1155:           int w = Math.max(rects[tabC].width + rects[tabC].x, tabAreaRect.width);
1156:           int h = Math.max(rects[tabC].height, tabAreaRect.height);
1157:           p = findPointForIndex(currentScrollLocation);
1158:           
1159:           // we want to cover that entire space so that borders that run under
1160:           // the tab area don't show up when we move the viewport around.
1161:           panel.setSize(w + p.x, h + p.y);
1162:         }
1163:       viewport.setViewPosition(p);
1164:       viewport.repaint();
1165:     }
1166:   }
1167: 
1168:   /**
1169:    * This class handles ChangeEvents from the JTabbedPane.
1170:    *
1171:    * @specnote Apparently this class was intended to be protected,
1172:    *           but was made public by a compiler bug and is now
1173:    *           public for compatibility.
1174:    */
1175:   public class TabSelectionHandler implements ChangeListener
1176:   {
1177:     /**
1178:      * This method is called whenever a ChangeEvent is fired from the
1179:      * JTabbedPane.
1180:      *
1181:      * @param e The ChangeEvent fired.
1182:      */
1183:     public void stateChanged(ChangeEvent e)
1184:     {
1185:       selectedRun = getRunForTab(tabPane.getTabCount(),
1186:                                  tabPane.getSelectedIndex());
1187:       tabPane.revalidate();
1188:       tabPane.repaint();
1189:     }
1190:   }
1191: 
1192:   /**
1193:    * This helper class is a JPanel that fits inside the ScrollViewport. This
1194:    * panel's sole job is to paint the tab rectangles inside the  viewport so
1195:    * that it's clipped correctly.
1196:    */
1197:   private class ScrollingPanel extends JPanel
1198:   {
1199:     /**
1200:      * This is a private UI class for our panel.
1201:      */
1202:     private class ScrollingPanelUI extends BasicPanelUI
1203:     {
1204:       /**
1205:        * This method overrides the default paint method. It paints the tab
1206:        * rectangles for the JTabbedPane in the panel.
1207:        *
1208:        * @param g The Graphics object to paint with.
1209:        * @param c The JComponent to paint.
1210:        */
1211:       public void paint(Graphics g, JComponent c)
1212:       {
1213:         paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1214:       }
1215:     }
1216: 
1217:     /**
1218:      * This method overrides the updateUI method. It makes the default UI for
1219:      * this ScrollingPanel to be  a ScrollingPanelUI.
1220:      */
1221:     public void updateUI()
1222:     {
1223:       setUI((PanelUI) new ScrollingPanelUI());
1224:     }
1225:   }
1226: 
1227:   /**
1228:    * This is a helper class that paints the panel that paints tabs. This
1229:    * custom JViewport is used so that the tabs painted in the panel will be
1230:    * clipped. This class implements UIResource so tabs are not added when
1231:    * this objects of this class are added to the  JTabbedPane.
1232:    */
1233:   private class ScrollingViewport extends JViewport implements UIResource
1234:   {
1235:     // TODO: Maybe remove this inner class.
1236:   }
1237: 
1238:   /**
1239:    * This is a helper class that implements UIResource so it is not added as a
1240:    * tab when an object of this class is added to the JTabbedPane.
1241:    */
1242:   private class ScrollingButton extends BasicArrowButton implements UIResource
1243:   {
1244:     /**
1245:      * Creates a ScrollingButton given the direction.
1246:      *
1247:      * @param dir The direction to point in.
1248:      */
1249:     public ScrollingButton(int dir)
1250:     {
1251:       super(dir);
1252:     }
1253:   }
1254: 
1255:   /** The button that increments the current scroll location.
1256:    * This is package-private to avoid an accessor method.  */
1257:   transient ScrollingButton incrButton;
1258: 
1259:   /** The button that decrements the current scroll location.
1260:    * This is package-private to avoid an accessor method.  */
1261:   transient ScrollingButton decrButton;
1262: 
1263:   /** The viewport used to display the tabs.
1264:    * This is package-private to avoid an accessor method.  */
1265:   transient ScrollingViewport viewport;
1266: 
1267:   /** The panel inside the viewport that paints the tabs.
1268:    * This is package-private to avoid an accessor method.  */
1269:   transient ScrollingPanel panel;
1270: 
1271:   /** The starting visible tab in the run in SCROLL_TAB_MODE.
1272:    * This is package-private to avoid an accessor method.  */
1273:   transient int currentScrollLocation;
1274: 
1275:   /** A reusable rectangle. */
1276:   protected Rectangle calcRect;
1277: 
1278:   /** An array of Rectangles keeping track of the tabs' area and position. */
1279:   protected Rectangle[] rects;
1280: 
1281:   /** The insets around the content area. */
1282:   protected Insets contentBorderInsets;
1283: 
1284:   /** The extra insets around the selected tab. */
1285:   protected Insets selectedTabPadInsets;
1286: 
1287:   /** The insets around the tab area. */
1288:   protected Insets tabAreaInsets;
1289: 
1290:   /** The insets around each and every tab. */
1291:   protected Insets tabInsets;
1292: 
1293:   /**
1294:    * The outer bottom and right edge color for both the tab and content
1295:    * border.
1296:    */
1297:   protected Color darkShadow;
1298: 
1299:   /** The color of the focus outline on the selected tab. */
1300:   protected Color focus;
1301: 
1302:   /** FIXME: find a use for this. */
1303:   protected Color highlight;
1304: 
1305:   /** The top and left edge color for both the tab and content border. */
1306:   protected Color lightHighlight;
1307: 
1308:   /** The inner bottom and right edge color for the tab and content border. */
1309:   protected Color shadow;
1310: 
1311:   /** The maximum tab height. */
1312:   protected int maxTabHeight;
1313: 
1314:   /** The maximum tab width. */
1315:   protected int maxTabWidth;
1316: 
1317:   /** The number of runs in the JTabbedPane. */
1318:   protected int runCount;
1319: 
1320:   /** The index of the run that the selected index is in. */
1321:   protected int selectedRun;
1322: 
1323:   /** The amount of space each run overlaps the previous by. */
1324:   protected int tabRunOverlay;
1325: 
1326:   /** The gap between text and label */
1327:   protected int textIconGap;
1328: 
1329:   // Keeps track of tab runs.
1330:   // The organization of this array is as follows (lots of experimentation to
1331:   // figure this out)
1332:   // index 0 = furthest away from the component area (aka outer run)
1333:   // index 1 = closest to component area (aka selected run)
1334:   // index > 1 = listed in order leading from selected run to outer run.
1335:   // each int in the array is the tab index + 1 (counting starts at 1)
1336:   // for the last tab in the run. (same as the rects array)
1337: 
1338:   /** This array keeps track of which tabs are in which run. See above. */
1339:   protected int[] tabRuns;
1340: 
1341:   /**
1342:    * Indicates if the layout of the tab runs is ok or not. This is package
1343:    * private to avoid a synthetic accessor method.
1344:    */
1345:   boolean tabRunsDirty;
1346: 
1347:   /**
1348:    * This is the keystroke for moving down.
1349:    *
1350:    * @deprecated 1.3
1351:    */
1352:   protected KeyStroke downKey;
1353: 
1354:   /**
1355:    * This is the keystroke for moving left.
1356:    *
1357:    * @deprecated 1.3
1358:    */
1359:   protected KeyStroke leftKey;
1360: 
1361:   /**
1362:    * This is the keystroke for moving right.
1363:    *
1364:    * @deprecated 1.3
1365:    */
1366:   protected KeyStroke rightKey;
1367: 
1368:   /**
1369:    * This is the keystroke for moving up.
1370:    *
1371:    * @deprecated 1.3
1372:    */
1373:   protected KeyStroke upKey;
1374: 
1375:   /** The listener that listens for focus events. */
1376:   protected FocusListener focusListener;
1377: 
1378:   /** The listener that listens for mouse events. */
1379:   protected MouseListener mouseListener;
1380: 
1381:   /** The listener that listens for property change events. */
1382:   protected PropertyChangeListener propertyChangeListener;
1383: 
1384:   /** The listener that listens for change events. */
1385:   protected ChangeListener tabChangeListener;
1386: 
1387:   /** The tab pane that this UI paints. */
1388:   protected JTabbedPane tabPane;
1389: 
1390:   /** The current layout manager for the tabPane.
1391:    * This is package-private to avoid an accessor method.  */
1392:   transient LayoutManager layoutManager;
1393: 
1394:   /** The rectangle that describes the tab area's position and size.
1395:    * This is package-private to avoid an accessor method.  */
1396:   transient Rectangle tabAreaRect;
1397: 
1398:   /** The rectangle that describes the content area's position and
1399:    * size.  This is package-private to avoid an accessor method.  */
1400:   transient Rectangle contentRect;
1401: 
1402:   /**
1403:    * The index over which the mouse is currently moving.
1404:    */
1405:   private int rolloverTab;
1406: 
1407:   /**
1408:    * Determines if tabs are painted opaque or not. This can be adjusted using
1409:    * the UIManager property 'TabbedPane.tabsOpaque'.
1410:    */
1411:   private boolean tabsOpaque;
1412: 
1413:   /**
1414:    * Creates a new BasicTabbedPaneUI object.
1415:    */
1416:   public BasicTabbedPaneUI()
1417:   {
1418:     super();
1419:     rects = new Rectangle[0];
1420:     tabRuns = new int[10];
1421:   }
1422: 
1423:   /**
1424:    * This method creates a ScrollingButton that  points in the appropriate
1425:    * direction for an increasing button.
1426:    * This is package-private to avoid an accessor method.
1427:    *
1428:    * @return The increase ScrollingButton.
1429:    */
1430:   ScrollingButton createIncreaseButton()
1431:   {
1432:     if (incrButton == null)
1433:       incrButton = new ScrollingButton(SwingConstants.NORTH);
1434:     if (tabPane.getTabPlacement() == SwingConstants.TOP
1435:         || tabPane.getTabPlacement() == SwingConstants.BOTTOM)
1436:       incrButton.setDirection(SwingConstants.EAST);
1437:     else
1438:       incrButton.setDirection(SwingConstants.SOUTH);
1439:     return incrButton;
1440:   }
1441: 
1442:   /**
1443:    * This method creates a ScrollingButton that points in the appropriate
1444:    * direction for a decreasing button.
1445:    * This is package-private to avoid an accessor method.
1446:    *
1447:    * @return The decrease ScrollingButton.
1448:    */
1449:   ScrollingButton createDecreaseButton()
1450:   {
1451:     if (decrButton == null)
1452:       decrButton = new ScrollingButton(SwingConstants.SOUTH);
1453:     if (tabPane.getTabPlacement() == SwingConstants.TOP
1454:         || tabPane.getTabPlacement() == SwingConstants.BOTTOM)
1455:       decrButton.setDirection(SwingConstants.WEST);
1456:     else
1457:       decrButton.setDirection(SwingConstants.NORTH);
1458:     return decrButton;
1459:   }
1460: 
1461:   /**
1462:    * This method finds the point to set the view  position at given the index
1463:    * of a tab. The tab will be the first visible tab in the run.
1464:    * This is package-private to avoid an accessor method.
1465:    *
1466:    * @param index The index of the first visible tab.
1467:    *
1468:    * @return The position of the first visible tab.
1469:    */
1470:   Point findPointForIndex(int index)
1471:   {
1472:     int tabPlacement = tabPane.getTabPlacement();
1473:     int selectedIndex = tabPane.getSelectedIndex();
1474:     Insets insets = getSelectedTabPadInsets(tabPlacement);
1475:     int w = 0;
1476:     int h = 0;
1477: 
1478:     if (tabPlacement == TOP || tabPlacement == BOTTOM)
1479:       {
1480:         if (index > 0)
1481:           {
1482:             w += rects[index - 1].x + rects[index - 1].width;
1483:             if (index > selectedIndex)
1484:               w -= insets.left + insets.right;
1485:           }
1486:       }
1487: 
1488:     else
1489:       {
1490:         if (index > 0)
1491:           {
1492:             h += rects[index - 1].y + rects[index - 1].height;
1493:             if (index > selectedIndex)
1494:               h -= insets.top + insets.bottom;
1495:           }
1496:       }
1497: 
1498:     Point p = new Point(w, h);
1499:     return p;
1500:   }
1501: 
1502:   /**
1503:    * This method creates a new BasicTabbedPaneUI.
1504:    *
1505:    * @param c The JComponent to create a UI for.
1506:    *
1507:    * @return A new BasicTabbedPaneUI.
1508:    */
1509:   public static ComponentUI createUI(JComponent c)
1510:   {
1511:     return new BasicTabbedPaneUI();
1512:   }
1513: 
1514:   /**
1515:    * This method installs the UI for the given JComponent.
1516:    *
1517:    * @param c The JComponent to install the UI for.
1518:    */
1519:   public void installUI(JComponent c)
1520:   {
1521:     super.installUI(c);
1522:     if (c instanceof JTabbedPane)
1523:       {
1524:         tabPane = (JTabbedPane) c;
1525:         
1526:         installComponents();
1527:         installDefaults();
1528:         installListeners();
1529:         installKeyboardActions();
1530:         
1531:         layoutManager = createLayoutManager();
1532:         tabPane.setLayout(layoutManager);
1533:       }
1534:   }
1535: 
1536:   /**
1537:    * This method uninstalls the UI for the  given JComponent.
1538:    *
1539:    * @param c The JComponent to uninstall the UI for.
1540:    */
1541:   public void uninstallUI(JComponent c)
1542:   {
1543:     layoutManager = null;
1544: 
1545:     uninstallKeyboardActions();
1546:     uninstallListeners();
1547:     uninstallDefaults();
1548:     uninstallComponents();
1549: 
1550:     tabPane = null;
1551:   }
1552: 
1553:   /**
1554:    * This method creates the appropriate layout manager for the JTabbedPane's
1555:    * current tab layout policy. If the tab layout policy is
1556:    * SCROLL_TAB_LAYOUT, then all the associated components that need to be
1557:    * created will be done so now.
1558:    *
1559:    * @return A layout manager given the tab layout policy.
1560:    */
1561:   protected LayoutManager createLayoutManager()
1562:   {
1563:     if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT)
1564:       return new TabbedPaneLayout();
1565:     else
1566:       {
1567:         incrButton = createIncreaseButton();
1568:         decrButton = createDecreaseButton();
1569:         viewport = new ScrollingViewport();
1570:         viewport.setLayout(null);
1571:         panel = new ScrollingPanel();
1572:         viewport.setView(panel);
1573:         tabPane.add(incrButton);
1574:         tabPane.add(decrButton);
1575:         tabPane.add(viewport);
1576:         currentScrollLocation = 0;
1577:         decrButton.setEnabled(false);
1578:         panel.addMouseListener(mouseListener);
1579:         incrButton.addMouseListener(mouseListener);
1580:         decrButton.addMouseListener(mouseListener);
1581:         viewport.setBackground(Color.LIGHT_GRAY);
1582: 
1583:         return new TabbedPaneScrollLayout();
1584:       }
1585:   }
1586: 
1587:   /**
1588:    * This method installs components for this JTabbedPane.
1589:    */
1590:   protected void installComponents()
1591:   {
1592:     // Nothing to be done.
1593:   }
1594: 
1595:   /**
1596:    * This method uninstalls components for this JTabbedPane.
1597:    */
1598:   protected void uninstallComponents()
1599:   {
1600:     // Nothing to be done.
1601:   }
1602: 
1603:   /**
1604:    * This method installs defaults for the Look and Feel.
1605:    */
1606:   protected void installDefaults()
1607:   {
1608:     LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
1609:                                      "TabbedPane.foreground",
1610:                                      "TabbedPane.font");
1611:     tabPane.setOpaque(false);
1612: 
1613:     highlight = UIManager.getColor("TabbedPane.highlight");
1614:     lightHighlight = UIManager.getColor("TabbedPane.lightHighlight");
1615: 
1616:     shadow = UIManager.getColor("TabbedPane.shadow");
1617:     darkShadow = UIManager.getColor("TabbedPane.darkShadow");
1618: 
1619:     focus = UIManager.getColor("TabbedPane.focus");
1620: 
1621:     textIconGap = UIManager.getInt("TabbedPane.textIconGap");
1622:     tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
1623: 
1624:     tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
1625:     selectedTabPadInsets = UIManager.getInsets("TabbedPane.tabbedPaneTabPadInsets");
1626:     tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
1627:     contentBorderInsets = UIManager.getInsets("TabbedPane.tabbedPaneContentBorderInsets");
1628:     tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
1629: 
1630:     calcRect = new Rectangle();
1631:     tabRuns = new int[10];
1632:     tabAreaRect = new Rectangle();
1633:     contentRect = new Rectangle();
1634:   }
1635: 
1636:   /**
1637:    * This method uninstalls defaults for the Look and Feel.
1638:    */
1639:   protected void uninstallDefaults()
1640:   {
1641:     calcRect = null;
1642:     tabAreaRect = null;
1643:     contentRect = null;
1644:     tabRuns = null;
1645: 
1646:     contentBorderInsets = null;
1647:     tabAreaInsets = null;
1648:     selectedTabPadInsets = null;
1649:     tabInsets = null;
1650: 
1651:     focus = null;
1652:     darkShadow = null;
1653:     shadow = null;
1654:     lightHighlight = null;
1655:     highlight = null;
1656: 
1657:     // Install UI colors and fonts.
1658:     LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
1659:                                      "TabbedPane.foreground",
1660:                                      "TabbedPane.font");
1661:   }
1662: 
1663:   /**
1664:    * This method creates and installs the listeners for this UI.
1665:    */
1666:   protected void installListeners()
1667:   {
1668:     mouseListener = createMouseListener();
1669:     tabChangeListener = createChangeListener();
1670:     propertyChangeListener = createPropertyChangeListener();
1671:     focusListener = createFocusListener();
1672: 
1673:     tabPane.addMouseListener(mouseListener);
1674:     tabPane.addChangeListener(tabChangeListener);
1675:     tabPane.addPropertyChangeListener(propertyChangeListener);
1676:     tabPane.addFocusListener(focusListener);
1677:   }
1678: 
1679:   /**
1680:    * This method removes and nulls the listeners for this UI.
1681:    */
1682:   protected void uninstallListeners()
1683:   {
1684:     tabPane.removeFocusListener(focusListener);
1685:     tabPane.removePropertyChangeListener(propertyChangeListener);
1686:     tabPane.removeChangeListener(tabChangeListener);
1687:     tabPane.removeMouseListener(mouseListener);
1688: 
1689:     focusListener = null;
1690:     propertyChangeListener = null;
1691:     tabChangeListener = null;
1692:     mouseListener = null;
1693:   }
1694: 
1695:   /**
1696:    * This method creates a new MouseListener.
1697:    *
1698:    * @return A new MouseListener.
1699:    */
1700:   protected MouseListener createMouseListener()
1701:   {
1702:     return new MouseHandler();
1703:   }
1704: 
1705:   /**
1706:    * This method creates a new FocusListener.
1707:    *
1708:    * @return A new FocusListener.
1709:    */
1710:   protected FocusListener createFocusListener()
1711:   {
1712:     return new FocusHandler();
1713:   }
1714: 
1715:   /**
1716:    * This method creates a new ChangeListener.
1717:    *
1718:    * @return A new ChangeListener.
1719:    */
1720:   protected ChangeListener createChangeListener()
1721:   {
1722:     return new TabSelectionHandler();
1723:   }
1724: 
1725:   /**
1726:    * This method creates a new PropertyChangeListener.
1727:    *
1728:    * @return A new PropertyChangeListener.
1729:    */
1730:   protected PropertyChangeListener createPropertyChangeListener()
1731:   {
1732:     return new PropertyChangeHandler();
1733:   }
1734: 
1735:   /**
1736:    * This method installs keyboard actions for the JTabbedPane.
1737:    */
1738:   protected void installKeyboardActions()
1739:     throws NotImplementedException
1740:   {
1741:     // FIXME: Implement.
1742:   }
1743: 
1744:   /**
1745:    * This method uninstalls keyboard actions for the JTabbedPane.
1746:    */
1747:   protected void uninstallKeyboardActions()
1748:     throws NotImplementedException
1749:   {
1750:     // FIXME: Implement.
1751:   }
1752: 
1753:   /**
1754:    * This method returns the minimum size of the JTabbedPane.
1755:    *
1756:    * @param c The JComponent to find a size for.
1757:    *
1758:    * @return The minimum size.
1759:    */
1760:   public Dimension getMinimumSize(JComponent c)
1761:   {
1762:     return layoutManager.minimumLayoutSize(tabPane);
1763:   }
1764: 
1765:   /**
1766:    * This method returns the maximum size of the JTabbedPane.
1767:    *
1768:    * @param c The JComponent to find a size for.
1769:    *
1770:    * @return The maximum size.
1771:    */
1772:   public Dimension getMaximumSize(JComponent c)
1773:   {
1774:     return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
1775:   }
1776: 
1777:   /**
1778:    * This method paints the JTabbedPane.
1779:    *
1780:    * @param g The Graphics object to paint with.
1781:    * @param c The JComponent to paint.
1782:    */
1783:   public void paint(Graphics g, JComponent c)
1784:   {
1785:     if (!tabPane.isValid())
1786:       tabPane.validate();
1787: 
1788:     if (tabPane.getTabCount() == 0)
1789:       return;
1790:     if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT)
1791:       paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1792:     paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1793:   }
1794: 
1795:   /**
1796:    * This method paints the tab area. This includes painting the rectangles
1797:    * that make up the tabs.
1798:    *
1799:    * @param g The Graphics object to paint with.
1800:    * @param tabPlacement The JTabbedPane's tab placement.
1801:    * @param selectedIndex The selected index.
1802:    */
1803:   protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex)
1804:   {
1805:     Rectangle ir = new Rectangle();
1806:     Rectangle tr = new Rectangle();
1807: 
1808:     boolean isScroll = tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
1809: 
1810:     // Please note: the ordering of the painting is important. 
1811:     // we WANT to paint the outermost run first and then work our way in.
1812:     int tabCount = tabPane.getTabCount();
1813:     for (int i = runCount - 1; i >= 0; --i)
1814:       {
1815:         int start = tabRuns[i];
1816:         int next;
1817:         if (i == runCount - 1)
1818:           next = tabRuns[0];
1819:         else
1820:           next = tabRuns[i + 1];
1821:         int end = (next != 0 ? next - 1 : tabCount - 1);
1822:         for (int j = start; j <= end; ++j)
1823:           {
1824:             if (j != selectedIndex)
1825:               {
1826:                 paintTab(g, tabPlacement, rects, j, ir, tr);
1827:               }
1828:           }
1829:       }
1830: 
1831:     // Paint selected tab in front of every other tab.
1832:     if (selectedIndex >= 0)
1833:       paintTab(g, tabPlacement, rects, selectedIndex, ir, tr);
1834:   }
1835: 
1836:   /**
1837:    * This method paints an individual tab.
1838:    *
1839:    * @param g The Graphics object to paint with.
1840:    * @param tabPlacement The JTabbedPane's tab placement.
1841:    * @param rects The array of rectangles that keep the size and position of
1842:    *        the tabs.
1843:    * @param tabIndex The tab index to paint.
1844:    * @param iconRect The rectangle to use for the icon.
1845:    * @param textRect The rectangle to use for the text.
1846:    */
1847:   protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects,
1848:                           int tabIndex, Rectangle iconRect, Rectangle textRect)
1849:   {
1850:     Rectangle rect = rects[tabIndex];
1851:     boolean isSelected = tabIndex == tabPane.getSelectedIndex();
1852:     // Paint background if necessary.
1853:     if (tabsOpaque || tabPane.isOpaque())
1854:       {
1855:         paintTabBackground(g, tabPlacement, tabIndex, rect.x, rect.y,
1856:                            rect.width, rect.height, isSelected);
1857:       }
1858: 
1859:     // Paint border.
1860:     paintTabBorder(g, tabPlacement, tabIndex, rect.x, rect.y, rect.width,
1861:                    rect.height, isSelected);
1862: 
1863: 
1864:     // Layout label.
1865:     FontMetrics fm = getFontMetrics();
1866:     Icon icon = getIconForTab(tabIndex);
1867:     String title = tabPane.getTitleAt(tabIndex);
1868:     layoutLabel(tabPlacement, fm, tabIndex, title, icon, rect, iconRect,
1869:                 textRect, isSelected);
1870:     // Paint the text.
1871:     paintText(g, tabPlacement, tabPane.getFont(), fm, tabIndex, title,
1872:               textRect, isSelected);
1873:     // Paint icon if necessary.
1874:     paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
1875:     // Paint focus indicator.
1876:     paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect, textRect,
1877:                         isSelected);
1878:   }
1879: 
1880:   /**
1881:    * This method lays out the tab and finds the location to paint the  icon
1882:    * and text.
1883:    *
1884:    * @param tabPlacement The JTabbedPane's tab placement.
1885:    * @param metrics The font metrics for the font to paint with.
1886:    * @param tabIndex The tab index to paint.
1887:    * @param title The string painted.
1888:    * @param icon The icon painted.
1889:    * @param tabRect The tab bounds.
1890:    * @param iconRect The calculated icon bounds.
1891:    * @param textRect The calculated text bounds.
1892:    * @param isSelected Whether this tab is selected.
1893:    */
1894:   protected void layoutLabel(int tabPlacement, FontMetrics metrics,
1895:                              int tabIndex, String title, Icon icon,
1896:                              Rectangle tabRect, Rectangle iconRect,
1897:                              Rectangle textRect, boolean isSelected)
1898:   {
1899:     SwingUtilities.layoutCompoundLabel(metrics, title, icon,
1900:                                        SwingConstants.CENTER,
1901:                                        SwingConstants.CENTER,
1902:                                        SwingConstants.CENTER,
1903:                                        SwingConstants.RIGHT, tabRect,
1904:                                        iconRect, textRect, textIconGap);
1905: 
1906:     int shiftX = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
1907:     int shiftY = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
1908: 
1909:     iconRect.x += shiftX;
1910:     iconRect.y += shiftY;
1911: 
1912:     textRect.x += shiftX;
1913:     textRect.y += shiftY;
1914:   }
1915: 
1916:   /**
1917:    * This method paints the icon.
1918:    *
1919:    * @param g The Graphics object to paint.
1920:    * @param tabPlacement The JTabbedPane's tab placement.
1921:    * @param tabIndex The tab index to paint.
1922:    * @param icon The icon to paint.
1923:    * @param iconRect The bounds of the icon.
1924:    * @param isSelected Whether this tab is selected.
1925:    */
1926:   protected void paintIcon(Graphics g, int tabPlacement, int tabIndex,
1927:                            Icon icon, Rectangle iconRect, boolean isSelected)
1928:   {
1929:     if (icon != null)
1930:       icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
1931:   }
1932: 
1933:   /**
1934:    * This method paints the text for the given tab.
1935:    *
1936:    * @param g The Graphics object to paint with.
1937:    * @param tabPlacement The JTabbedPane's tab placement.
1938:    * @param font The font to paint with.
1939:    * @param metrics The fontmetrics of the given font.
1940:    * @param tabIndex The tab index.
1941:    * @param title The string to paint.
1942:    * @param textRect The bounds of the string.
1943:    * @param isSelected Whether this tab is selected.
1944:    */
1945:   protected void paintText(Graphics g, int tabPlacement, Font font,
1946:                            FontMetrics metrics, int tabIndex, String title,
1947:                            Rectangle textRect, boolean isSelected)
1948:   {
1949:     g.setFont(font);
1950:     View textView = getTextViewForTab(tabIndex);
1951:     if (textView != null)
1952:       {
1953:         textView.paint(g, textRect);
1954:         return;
1955:       }
1956: 
1957:     int ascent = metrics.getAscent();
1958: 
1959:     int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1960:     if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex))
1961:       {
1962:         Color fg = tabPane.getForegroundAt(tabIndex);
1963:         if (isSelected && (fg instanceof UIResource))
1964:           {
1965:             Color selectionForeground =
1966:               UIManager.getColor("TabbedPane.selectionForeground");
1967:             if (selectionForeground != null)
1968:               fg = selectionForeground;
1969:           }
1970:         g.setColor(fg);
1971: 
1972:         if (mnemIndex != -1)
1973:           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1974:                                                        textRect.x,
1975:                                                        textRect.y + ascent);
1976:         else
1977:           g.drawString(title, textRect.x, textRect.y + ascent);
1978:       }
1979:     else
1980:       {
1981:         Color bg = tabPane.getBackgroundAt(tabIndex);
1982:         g.setColor(bg.brighter());
1983:         if (mnemIndex != -1)
1984:           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1985:                                                        textRect.x, textRect.y
1986:                                                        + ascent);
1987:         else
1988:           g.drawString(title, textRect.x, textRect.y + ascent);
1989: 
1990:         g.setColor(bg.darker());
1991:         if (mnemIndex != -1)
1992:           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1993:                                                        textRect.x + 1,
1994:                                                        textRect.y + 1
1995:                                                        + ascent);
1996:         else
1997:           g.drawString(title, textRect.x + 1, textRect.y + 1 + ascent);
1998:       }
1999:   }
2000: 
2001:   /**
2002:    * This method returns how much the label for the tab should shift in the X
2003:    * direction.
2004:    *
2005:    * @param tabPlacement The JTabbedPane's tab placement.
2006:    * @param tabIndex The tab index being painted.
2007:    * @param isSelected Whether this tab is selected.
2008:    *
2009:    * @return The amount the label should shift by in the X direction.
2010:    */
2011:   protected int getTabLabelShiftX(int tabPlacement, int tabIndex,
2012:                                   boolean isSelected)
2013:   {
2014:     // No reason to shift.
2015:     return 0;
2016:   }
2017: 
2018:   /**
2019:    * This method returns how much the label for the tab should shift in the Y
2020:    * direction.
2021:    *
2022:    * @param tabPlacement The JTabbedPane's tab placement.
2023:    * @param tabIndex The tab index being painted.
2024:    * @param isSelected Whether this tab is selected.
2025:    *
2026:    * @return The amount the label should shift by in the Y direction.
2027:    */
2028:   protected int getTabLabelShiftY(int tabPlacement, int tabIndex,
2029:                                   boolean isSelected)
2030:   {
2031:     // No reason to shift.
2032:     return 0;
2033:   }
2034: 
2035:   /**
2036:    * This method paints the focus rectangle around the selected tab.
2037:    *
2038:    * @param g The Graphics object to paint with.
2039:    * @param tabPlacement The JTabbedPane's tab placement.
2040:    * @param rects The array of rectangles keeping track of size and position.
2041:    * @param tabIndex The tab index.
2042:    * @param iconRect The icon bounds.
2043:    * @param textRect The text bounds.
2044:    * @param isSelected Whether this tab is selected.
2045:    */
2046:   protected void paintFocusIndicator(Graphics g, int tabPlacement,
2047:                                      Rectangle[] rects, int tabIndex,
2048:                                      Rectangle iconRect, Rectangle textRect,
2049:                                      boolean isSelected)
2050:   {
2051:     if (tabPane.hasFocus() && isSelected)
2052:       {
2053:         Rectangle rect = rects[tabIndex];
2054:         // The focus rectangle.
2055:         int x;
2056:         int y;
2057:         int w;
2058:         int h;
2059: 
2060:         g.setColor(focus);
2061:         switch (tabPlacement)
2062:         {
2063:         case LEFT:
2064:           x = rect.x + 3;
2065:           y = rect.y + 3;
2066:           w = rect.width - 5;
2067:           h = rect.height - 6;
2068:           break;
2069:         case RIGHT:
2070:           x = rect.x + 2;
2071:           y = rect.y + 3;
2072:           w = rect.width - 6;
2073:           h = rect.height - 5;
2074:           break;
2075:         case BOTTOM:
2076:           x = rect.x + 3;
2077:           y = rect.y + 2;
2078:           w = rect.width - 6;
2079:           h = rect.height - 5;
2080:           break;
2081:         case TOP:
2082:         default:
2083:           x = rect.x + 3;
2084:         y = rect.y + 3;
2085:         w = rect.width - 6;
2086:         h = rect.height - 5;
2087:         }
2088:         BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
2089:       }
2090:   }
2091: 
2092:   /**
2093:    * This method paints the border for an individual tab.
2094:    *
2095:    * @param g The Graphics object to paint with.
2096:    * @param tabPlacement The JTabbedPane's tab placement.
2097:    * @param tabIndex The tab index.
2098:    * @param x The x position of the tab.
2099:    * @param y The y position of the tab.
2100:    * @param w The width of the tab.
2101:    * @param h The height of the tab.
2102:    * @param isSelected Whether the tab is selected.
2103:    */
2104:   protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex,
2105:                                 int x, int y, int w, int h, boolean isSelected)
2106:   {
2107:     Color saved = g.getColor();
2108: 
2109:     if (! isSelected || tabPlacement != SwingConstants.TOP)
2110:       {
2111:         g.setColor(shadow);
2112:         g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1);
2113:         g.setColor(darkShadow);
2114:         g.drawLine(x, y + h, x + w, y + h);
2115:       }
2116: 
2117:     if (! isSelected || tabPlacement != SwingConstants.LEFT)
2118:       {
2119:         g.setColor(darkShadow);
2120:         g.drawLine(x + w, y, x + w, y + h);
2121:         g.setColor(shadow);
2122:         g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
2123:       }
2124: 
2125:     if (! isSelected || tabPlacement != SwingConstants.RIGHT)
2126:       {
2127:         g.setColor(lightHighlight);
2128:         g.drawLine(x, y, x, y + h);
2129:       }
2130: 
2131:     if (! isSelected || tabPlacement != SwingConstants.BOTTOM)
2132:       {
2133:         g.setColor(lightHighlight);
2134:         g.drawLine(x, y, x + w, y);
2135:       }
2136: 
2137:     g.setColor(saved);
2138:   }
2139: 
2140:   /**
2141:    * This method paints the background for an individual tab.
2142:    *
2143:    * @param g The Graphics object to paint with.
2144:    * @param tabPlacement The JTabbedPane's tab placement.
2145:    * @param tabIndex The tab index.
2146:    * @param x The x position of the tab.
2147:    * @param y The y position of the tab.
2148:    * @param w The width of the tab.
2149:    * @param h The height of the tab.
2150:    * @param isSelected Whether the tab is selected.
2151:    */
2152:   protected void paintTabBackground(Graphics g, int tabPlacement,
2153:                                     int tabIndex, int x, int y, int w, int h,
2154:                                     boolean isSelected)
2155:   {
2156:     Color saved = g.getColor();
2157:     if (isSelected)
2158:       g.setColor(Color.LIGHT_GRAY);
2159:     else
2160:       {
2161:         Color bg = tabPane.getBackgroundAt(tabIndex);
2162:         if (bg == null)
2163:           bg = Color.GRAY;
2164:         g.setColor(bg);
2165:       }
2166: 
2167:     g.fillRect(x, y, w, h);
2168: 
2169:     g.setColor(saved);
2170:   }
2171: 
2172:   /**
2173:    * This method paints the border around the content area.
2174:    *
2175:    * @param g The Graphics object to paint with.
2176:    * @param tabPlacement The JTabbedPane's tab placement.
2177:    * @param selectedIndex The index of the selected tab.
2178:    */
2179:   protected void paintContentBorder(Graphics g, int tabPlacement,
2180:                                     int selectedIndex)
2181:   {
2182:     int width = tabPane.getWidth();
2183:     int height = tabPane.getHeight();
2184:     Insets insets = tabPane.getInsets();
2185:     Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2186: 
2187:     // Calculate coordinates of content area.
2188:     int x = insets.left;
2189:     int y = insets.top;
2190:     int w = width - insets.left - insets.right;
2191:     int h = height - insets.top - insets.bottom;
2192: 
2193:     switch (tabPlacement)
2194:     {
2195:     case LEFT:
2196:       x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2197:       w -= (x - insets.left);
2198:       break;
2199:     case RIGHT:
2200:       w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2201:       break;
2202:     case BOTTOM:
2203:       h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2204:       break;
2205:     case TOP:
2206:     default:
2207:       y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2208:       h -= (y - insets.top);
2209:     }
2210: 
2211:     // Fill background if necessary.
2212:     if (tabPane.isOpaque())
2213:       {
2214:         Color bg = UIManager.getColor("TabbedPane.contentAreaColor");
2215:         g.setColor(bg);
2216:         g.fillRect(x, y, w, h);
2217:       }
2218: 
2219:     // Paint border.
2220:     paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2221:     paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2222:     paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2223:     paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2224:   }
2225: 
2226:   /**
2227:    * This method paints the top edge of the content border.
2228:    *
2229:    * @param g The Graphics object to paint with.
2230:    * @param tabPlacement The JTabbedPane's tab placement.
2231:    * @param selectedIndex The selected tab index.
2232:    * @param x The x coordinate for the content area.
2233:    * @param y The y coordinate for the content area.
2234:    * @param w The width of the content area.
2235:    * @param h The height of the content area.
2236:    */
2237:   protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
2238:                                            int selectedIndex, int x, int y,
2239:                                            int w, int h)
2240:   {
2241:     Color saved = g.getColor();
2242:     g.setColor(lightHighlight);
2243: 
2244:     int startgap = rects[selectedIndex].x;
2245:     int endgap = rects[selectedIndex].x + rects[selectedIndex].width;
2246: 
2247:     int diff = 0;
2248: 
2249:     if (tabPlacement == SwingConstants.TOP)
2250:       {
2251:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2252:           {
2253:             Point p = findPointForIndex(currentScrollLocation);
2254:             diff = p.x;
2255:           }
2256: 
2257:         g.drawLine(x, y, startgap - diff, y);
2258:         g.drawLine(endgap - diff, y, x + w, y);
2259:       }
2260:     else
2261:       g.drawLine(x, y, x + w, y);
2262: 
2263:     g.setColor(saved);
2264:   }
2265: 
2266:   /**
2267:    * This method paints the left edge of the content border.
2268:    *
2269:    * @param g The Graphics object to paint with.
2270:    * @param tabPlacement The JTabbedPane's tab placement.
2271:    * @param selectedIndex The selected tab index.
2272:    * @param x The x coordinate for the content area.
2273:    * @param y The y coordinate for the content area.
2274:    * @param w The width of the content area.
2275:    * @param h The height of the content area.
2276:    */
2277:   protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
2278:                                             int selectedIndex, int x, int y,
2279:                                             int w, int h)
2280:   {
2281:     Color saved = g.getColor();
2282:     g.setColor(lightHighlight);
2283: 
2284:     int startgap = rects[selectedIndex].y;
2285:     int endgap = rects[selectedIndex].y + rects[selectedIndex].height;
2286: 
2287:     int diff = 0;
2288: 
2289:     if (tabPlacement == SwingConstants.LEFT)
2290:       {
2291:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2292:           {
2293:             Point p = findPointForIndex(currentScrollLocation);
2294:             diff = p.y;
2295:           }
2296: 
2297:         g.drawLine(x, y, x, startgap - diff);
2298:         g.drawLine(x, endgap - diff, x, y + h);
2299:       }
2300:     else
2301:       g.drawLine(x, y, x, y + h);
2302: 
2303:     g.setColor(saved);
2304:   }
2305: 
2306:   /**
2307:    * This method paints the bottom edge of the content border.
2308:    *
2309:    * @param g The Graphics object to paint with.
2310:    * @param tabPlacement The JTabbedPane's tab placement.
2311:    * @param selectedIndex The selected tab index.
2312:    * @param x The x coordinate for the content area.
2313:    * @param y The y coordinate for the content area.
2314:    * @param w The width of the content area.
2315:    * @param h The height of the content area.
2316:    */
2317:   protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
2318:                                               int selectedIndex, int x, int y,
2319:                                               int w, int h)
2320:   {
2321:     Color saved = g.getColor();
2322: 
2323:     int startgap = rects[selectedIndex].x;
2324:     int endgap = rects[selectedIndex].x + rects[selectedIndex].width;
2325: 
2326:     int diff = 0;
2327: 
2328:     if (tabPlacement == SwingConstants.BOTTOM)
2329:       {
2330:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2331:           {
2332:             Point p = findPointForIndex(currentScrollLocation);
2333:             diff = p.x;
2334:           }
2335: 
2336:         g.setColor(shadow);
2337:         g.drawLine(x + 1, y + h - 1, startgap - diff, y + h - 1);
2338:         g.drawLine(endgap - diff, y + h - 1, x + w - 1, y + h - 1);
2339: 
2340:         g.setColor(darkShadow);
2341:         g.drawLine(x, y + h, startgap - diff, y + h);
2342:         g.drawLine(endgap - diff, y + h, x + w, y + h);
2343:       }
2344:     else
2345:       {
2346:         g.setColor(shadow);
2347:         g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1);
2348:         g.setColor(darkShadow);
2349:         g.drawLine(x, y + h, x + w, y + h);
2350:       }
2351: 
2352:     g.setColor(saved);
2353:   }
2354: 
2355:   /**
2356:    * This method paints the right edge of the content border.
2357:    *
2358:    * @param g The Graphics object to paint with.
2359:    * @param tabPlacement The JTabbedPane's tab placement.
2360:    * @param selectedIndex The selected tab index.
2361:    * @param x The x coordinate for the content area.
2362:    * @param y The y coordinate for the content area.
2363:    * @param w The width of the content area.
2364:    * @param h The height of the content area.
2365:    */
2366:   protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
2367:                                              int selectedIndex, int x, int y,
2368:                                              int w, int h)
2369:   {
2370:     Color saved = g.getColor();
2371:     int startgap = rects[selectedIndex].y;
2372:     int endgap = rects[selectedIndex].y + rects[selectedIndex].height;
2373: 
2374:     int diff = 0;
2375: 
2376:     if (tabPlacement == SwingConstants.RIGHT)
2377:       {
2378:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2379:           {
2380:             Point p = findPointForIndex(currentScrollLocation);
2381:             diff = p.y;
2382:           }
2383: 
2384:         g.setColor(shadow);
2385:         g.drawLine(x + w - 1, y + 1, x + w - 1, startgap - diff);
2386:         g.drawLine(x + w - 1, endgap - diff, x + w - 1, y + h - 1);
2387: 
2388:         g.setColor(darkShadow);
2389:         g.drawLine(x + w, y, x + w, startgap - diff);
2390:         g.drawLine(x + w, endgap - diff, x + w, y + h);
2391:       }
2392:     else
2393:       {
2394:         g.setColor(shadow);
2395:         g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
2396:         g.setColor(darkShadow);
2397:         g.drawLine(x + w, y, x + w, y + h);
2398:       }
2399: 
2400:     g.setColor(saved);
2401:   }
2402: 
2403:   /**
2404:    * This method returns the tab bounds for the given index.
2405:    *
2406:    * @param pane The JTabbedPane.
2407:    * @param i The index to look for.
2408:    *
2409:    * @return The bounds of the tab with the given index.
2410:    */
2411:   public Rectangle getTabBounds(JTabbedPane pane, int i)
2412:   {
2413:     return rects[i];
2414:   }
2415: 
2416:   /**
2417:    * This method returns the number of runs.
2418:    *
2419:    * @param pane The JTabbedPane.
2420:    *
2421:    * @return The number of runs.
2422:    */
2423:   public int getTabRunCount(JTabbedPane pane)
2424:   {
2425:     return runCount;
2426:   }
2427: 
2428:   /**
2429:    * This method returns the tab index given a coordinate.
2430:    *
2431:    * @param pane The JTabbedPane.
2432:    * @param x The x coordinate.
2433:    * @param y The y coordinate.
2434:    *
2435:    * @return The tab index that the coordinate lands in.
2436:    */
2437:   public int tabForCoordinate(JTabbedPane pane, int x, int y)
2438:   {
2439:     if (! tabPane.isValid())
2440:       tabPane.validate();
2441: 
2442:     int tabCount = tabPane.getTabCount();
2443:     int index = -1;
2444:     for (int i = 0; i < tabCount; ++i)
2445:       {
2446:         if (rects[i].contains(x, y))
2447:           {
2448:             index = i;
2449:             break;
2450:           }
2451:       }
2452: 
2453:     // FIXME: Handle scrollable tab layout.
2454: 
2455:     return index;
2456:   }
2457: 
2458:   /**
2459:    * This method returns the tab bounds in the given rectangle.
2460:    *
2461:    * @param tabIndex The index to get bounds for.
2462:    * @param dest The rectangle to store bounds in.
2463:    *
2464:    * @return The rectangle passed in.
2465:    */
2466:   protected Rectangle getTabBounds(int tabIndex, Rectangle dest)
2467:   {
2468:     dest.setBounds(getTabBounds(tabPane, tabIndex));
2469:     return dest;
2470:   }
2471: 
2472:   /**
2473:    * This method returns the component that is shown in  the content area.
2474:    *
2475:    * @return The component that is shown in the content area.
2476:    */
2477:   protected Component getVisibleComponent()
2478:   {
2479:     return tabPane.getComponentAt(tabPane.getSelectedIndex());
2480:   }
2481: 
2482:   /**
2483:    * This method sets the visible component.
2484:    *
2485:    * @param component The component to be set visible.
2486:    */
2487:   protected void setVisibleComponent(Component component)
2488:   {
2489:     component.setVisible(true);
2490:     tabPane.setSelectedComponent(component);
2491:   }
2492: 
2493:   /**
2494:    * This method assures that enough rectangles are created given the
2495:    * tabCount. The old array is copied to the  new one.
2496:    *
2497:    * @param tabCount The number of tabs.
2498:    */
2499:   protected void assureRectsCreated(int tabCount)
2500:   {
2501:     if (rects.length < tabCount)
2502:       {
2503:         Rectangle[] old = rects;
2504:         rects = new Rectangle[tabCount];
2505:         System.arraycopy(old, 0, rects, 0, old.length);
2506:         for (int i = old.length; i < rects.length; i++)
2507:           rects[i] = new Rectangle();
2508:       }
2509:   }
2510: 
2511:   /**
2512:    * This method expands the tabRuns array to give it more room. The old array
2513:    * is copied to the new one.
2514:    */
2515:   protected void expandTabRunsArray()
2516:   {
2517:     // This method adds another 10 index positions to the tabRuns array.
2518:     if (tabRuns == null)
2519:       tabRuns = new int[10];
2520:     else
2521:       {
2522:         int[] newRuns = new int[tabRuns.length + 10];
2523:         System.arraycopy(tabRuns, 0, newRuns, 0, tabRuns.length);
2524:         tabRuns = newRuns;
2525:       }
2526:   }
2527: 
2528:   /**
2529:    * This method returns which run a particular tab belongs to.
2530:    *
2531:    * @param tabCount The number of tabs.
2532:    * @param tabIndex The tab to find.
2533:    *
2534:    * @return The tabRuns index that it belongs to.
2535:    */
2536:   protected int getRunForTab(int tabCount, int tabIndex)
2537:   {
2538:     if (runCount == 1 && tabIndex < tabCount && tabIndex >= 0)
2539:       return 1;
2540:     for (int i = 0; i < runCount; i++)
2541:       {
2542:         int first = lastTabInRun(tabCount, getPreviousTabRun(i)) + 1;
2543:         if (first == tabCount)
2544:           first = 0;
2545:         int last = lastTabInRun(tabCount, i);
2546:         if (last >= tabIndex && first <= tabIndex)
2547:           return i;
2548:       }
2549:     return -1;
2550:   }
2551: 
2552:   /**
2553:    * This method returns the index of the last tab in  a run.
2554:    *
2555:    * @param tabCount The number of tabs.
2556:    * @param run The run to check.
2557:    *
2558:    * @return The last tab in the given run.
2559:    */
2560:   protected int lastTabInRun(int tabCount, int run)
2561:   {
2562:     int lastTab;
2563:     if (runCount == 1)
2564:       lastTab = tabCount - 1;
2565:     else
2566:       {
2567:         int nextRun;
2568:         if (run == runCount - 1)
2569:           nextRun = 0;
2570:         else
2571:           nextRun = run + 1;
2572: 
2573:         if (tabRuns[nextRun] == 0)
2574:           lastTab = tabCount - 1;
2575:         else
2576:           lastTab = tabRuns[nextRun] - 1;
2577:       }
2578:     return lastTab;
2579:   }
2580: 
2581:   /**
2582:    * This method returns the tab run overlay.
2583:    *
2584:    * @param tabPlacement The JTabbedPane's tab placement.
2585:    *
2586:    * @return The tab run overlay.
2587:    */
2588:   protected int getTabRunOverlay(int tabPlacement)
2589:   {
2590:     return tabRunOverlay;
2591:   }
2592: 
2593:   /**
2594:    * This method returns the tab run indent. It is used in WRAP_TAB_LAYOUT and
2595:    * makes each tab run start indented by a certain amount.
2596:    *
2597:    * @param tabPlacement The JTabbedPane's tab placement.
2598:    * @param run The run to get indent for.
2599:    *
2600:    * @return The amount a run should be indented.
2601:    */
2602:   protected int getTabRunIndent(int tabPlacement, int run)
2603:   {
2604:     return 0;
2605:   }
2606: 
2607:   /**
2608:    * This method returns whether a tab run should be padded.
2609:    *
2610:    * @param tabPlacement The JTabbedPane's tab placement.
2611:    * @param run The run to check.
2612:    *
2613:    * @return Whether the given run should be padded.
2614:    */
2615:   protected boolean shouldPadTabRun(int tabPlacement, int run)
2616:   {
2617:     return true;
2618:   }
2619: 
2620:   /**
2621:    * This method returns whether the tab runs should be rotated.
2622:    *
2623:    * @param tabPlacement The JTabbedPane's tab placement.
2624:    *
2625:    * @return Whether runs should be rotated.
2626:    */
2627:   protected boolean shouldRotateTabRuns(int tabPlacement)
2628:   {
2629:     return true;
2630:   }
2631: 
2632:   /**
2633:    * This method returns an icon for the tab. If the tab is disabled, it
2634:    * should return the disabledIcon. If it is enabled, then it should return
2635:    * the default icon.
2636:    *
2637:    * @param tabIndex The tab index to get an icon for.
2638:    *
2639:    * @return The icon for the tab index.
2640:    */
2641:   protected Icon getIconForTab(int tabIndex)
2642:   {
2643:     if (tabPane.isEnabledAt(tabIndex))
2644:       return tabPane.getIconAt(tabIndex);
2645:     else
2646:       return tabPane.getDisabledIconAt(tabIndex);
2647:   }
2648: 
2649:   /**
2650:    * This method returns a view that can paint the text for the label.
2651:    *
2652:    * @param tabIndex The tab index to get a view for.
2653:    *
2654:    * @return The view for the tab index.
2655:    */
2656:   protected View getTextViewForTab(int tabIndex)
2657:   {
2658:     return null;
2659:   }
2660: 
2661:   /**
2662:    * This method returns the tab height, including insets, for the given index
2663:    * and fontheight.
2664:    *
2665:    * @param tabPlacement The JTabbedPane's tab placement.
2666:    * @param tabIndex The index of the tab to calculate.
2667:    * @param fontHeight The font height.
2668:    *
2669:    * @return This tab's height.
2670:    */
2671:   protected int calculateTabHeight(int tabPlacement, int tabIndex,
2672:                                    int fontHeight)
2673:   {
2674:     // FIXME: Handle HTML somehow.
2675: 
2676:     int height = fontHeight;
2677:     Icon icon = getIconForTab(tabIndex);
2678:     Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
2679:     if (icon != null)
2680:       height = Math.max(height, icon.getIconHeight());
2681:     height += tabInsets.top + tabInsets.bottom + 2;
2682:     return height;
2683:   }
2684: 
2685:   /**
2686:    * This method returns the max tab height.
2687:    *
2688:    * @param tabPlacement The JTabbedPane's tab placement.
2689:    *
2690:    * @return The maximum tab height.
2691:    */
2692:   protected int calculateMaxTabHeight(int tabPlacement)
2693:   {
2694:     maxTabHeight = 0;
2695: 
2696:     FontMetrics fm = getFontMetrics();
2697:     int fontHeight = fm.getHeight();
2698: 
2699:     for (int i = 0; i < tabPane.getTabCount(); i++)
2700:       maxTabHeight = Math.max(calculateTabHeight(tabPlacement, i, fontHeight),
2701:                               maxTabHeight);
2702: 
2703:     return maxTabHeight;
2704:   }
2705: 
2706:   /**
2707:    * This method calculates the tab width, including insets, for the given tab
2708:    * index and font metrics.
2709:    *
2710:    * @param tabPlacement The JTabbedPane's tab placement.
2711:    * @param tabIndex The tab index to calculate for.
2712:    * @param metrics The font's metrics.
2713:    *
2714:    * @return The tab width for the given index.
2715:    */
2716:   protected int calculateTabWidth(int tabPlacement, int tabIndex,
2717:                                   FontMetrics metrics)
2718:   {
2719:     Icon icon = getIconForTab(tabIndex);
2720:     Insets insets = getTabInsets(tabPlacement, tabIndex);
2721: 
2722:     int width = 0;
2723:     if (icon != null)
2724:       {
2725:         Rectangle vr = new Rectangle();
2726:         Rectangle ir = new Rectangle();
2727:         Rectangle tr = new Rectangle();
2728:         layoutLabel(tabPlacement, getFontMetrics(), tabIndex,
2729:                     tabPane.getTitleAt(tabIndex), icon, vr, ir, tr,
2730:                     tabIndex == tabPane.getSelectedIndex());
2731:         width = tr.union(ir).width;
2732:       }
2733:     else
2734:       width = metrics.stringWidth(tabPane.getTitleAt(tabIndex));
2735: 
2736:     width += insets.left + insets.right;
2737:     return width;
2738:   }
2739: 
2740:   /**
2741:    * This method calculates the max tab width.
2742:    *
2743:    * @param tabPlacement The JTabbedPane's tab placement.
2744:    *
2745:    * @return The maximum tab width.
2746:    */
2747:   protected int calculateMaxTabWidth(int tabPlacement)
2748:   {
2749:     maxTabWidth = 0;
2750: 
2751:     FontMetrics fm = getFontMetrics();
2752: 
2753:     for (int i = 0; i < tabPane.getTabCount(); i++)
2754:       maxTabWidth = Math.max(calculateTabWidth(tabPlacement, i, fm),
2755:                              maxTabWidth);
2756: 
2757:     return maxTabWidth;
2758:   }
2759: 
2760:   /**
2761:    * This method calculates the tab area height, including insets, for the
2762:    * given amount of runs and tab height.
2763:    *
2764:    * @param tabPlacement The JTabbedPane's tab placement.
2765:    * @param horizRunCount The number of runs.
2766:    * @param maxTabHeight The max tab height.
2767:    *
2768:    * @return The tab area height.
2769:    */
2770:   protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount,
2771:                                        int maxTabHeight)
2772:   {
2773:     Insets insets = getTabAreaInsets(tabPlacement);
2774:     int tabAreaHeight = horizRunCount * maxTabHeight
2775:                         - (horizRunCount - 1) * tabRunOverlay;
2776: 
2777:     tabAreaHeight += insets.top + insets.bottom;
2778: 
2779:     return tabAreaHeight;
2780:   }
2781: 
2782:   /**
2783:    * This method calculates the tab area width, including insets, for the
2784:    * given amount of runs and tab width.
2785:    *
2786:    * @param tabPlacement The JTabbedPane's tab placement.
2787:    * @param vertRunCount The number of runs.
2788:    * @param maxTabWidth The max tab width.
2789:    *
2790:    * @return The tab area width.
2791:    */
2792:   protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount,
2793:                                       int maxTabWidth)
2794:   {
2795:     Insets insets = getTabAreaInsets(tabPlacement);
2796:     int tabAreaWidth = vertRunCount * maxTabWidth
2797:                        - (vertRunCount - 1) * tabRunOverlay;
2798: 
2799:     tabAreaWidth += insets.left + insets.right;
2800: 
2801:     return tabAreaWidth;
2802:   }
2803: 
2804:   /**
2805:    * This method returns the tab insets appropriately rotated.
2806:    *
2807:    * @param tabPlacement The JTabbedPane's tab placement.
2808:    * @param tabIndex The tab index.
2809:    *
2810:    * @return The tab insets for the given index.
2811:    */
2812:   protected Insets getTabInsets(int tabPlacement, int tabIndex)
2813:   {
2814:     return tabInsets;
2815:   }
2816: 
2817:   /**
2818:    * This method returns the selected tab pad insets appropriately rotated.
2819:    *
2820:    * @param tabPlacement The JTabbedPane's tab placement.
2821:    *
2822:    * @return The selected tab pad insets.
2823:    */
2824:   protected Insets getSelectedTabPadInsets(int tabPlacement)
2825:   {
2826:     Insets target = new Insets(0, 0, 0, 0);
2827:     rotateInsets(selectedTabPadInsets, target, tabPlacement);
2828:     return target;
2829:   }
2830: 
2831:   /**
2832:    * This method returns the tab area insets appropriately rotated.
2833:    *
2834:    * @param tabPlacement The JTabbedPane's tab placement.
2835:    *
2836:    * @return The tab area insets.
2837:    */
2838:   protected Insets getTabAreaInsets(int tabPlacement)
2839:   {
2840:     Insets target = new Insets(0, 0, 0, 0);
2841:     rotateInsets(tabAreaInsets, target, tabPlacement);
2842:     return target;
2843:   }
2844: 
2845:   /**
2846:    * This method returns the content border insets appropriately rotated.
2847:    *
2848:    * @param tabPlacement The JTabbedPane's tab placement.
2849:    *
2850:    * @return The content border insets.
2851:    */
2852:   protected Insets getContentBorderInsets(int tabPlacement)
2853:   {
2854:     Insets target = new Insets(0, 0, 0, 0);
2855:     rotateInsets(contentBorderInsets, target, tabPlacement);
2856:     return target;
2857:   }
2858: 
2859:   /**
2860:    * This method returns the fontmetrics for the font of the JTabbedPane.
2861:    *
2862:    * @return The font metrics for the JTabbedPane.
2863:    */
2864:   protected FontMetrics getFontMetrics()
2865:   {
2866:     FontMetrics fm = tabPane.getFontMetrics(tabPane.getFont());
2867:     return fm;
2868:   }
2869: 
2870:   /**
2871:    * This method navigates from the selected tab into the given direction. As
2872:    * a result, a new tab will be selected (if possible).
2873:    *
2874:    * @param direction The direction to navigate in.
2875:    */
2876:   protected void navigateSelectedTab(int direction)
2877:   {
2878:     int tabPlacement = tabPane.getTabPlacement();
2879:     if (tabPlacement == SwingConstants.TOP
2880:         || tabPlacement == SwingConstants.BOTTOM)
2881:       {
2882:         if (direction == SwingConstants.WEST)
2883:           selectPreviousTabInRun(tabPane.getSelectedIndex());
2884:         else if (direction == SwingConstants.EAST)
2885:           selectNextTabInRun(tabPane.getSelectedIndex());
2886: 
2887:         else
2888:           {
2889:             int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(),
2890:                                          tabPane.getSelectedIndex(),
2891:                                          (tabPlacement == SwingConstants.RIGHT)
2892:                                          ? true : false);
2893:             selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(),
2894:                                  offset);
2895:           }
2896:       }
2897:     if (tabPlacement == SwingConstants.LEFT
2898:         || tabPlacement == SwingConstants.RIGHT)
2899:       {
2900:         if (direction == SwingConstants.NORTH)
2901:           selectPreviousTabInRun(tabPane.getSelectedIndex());
2902:         else if (direction == SwingConstants.SOUTH)
2903:           selectNextTabInRun(tabPane.getSelectedIndex());
2904:         else
2905:           {
2906:             int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(),
2907:                                          tabPane.getSelectedIndex(),
2908:                                          (tabPlacement == SwingConstants.RIGHT)
2909:                                          ? true : false);
2910:             selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(),
2911:                                  offset);
2912:           }
2913:       }
2914:   }
2915: 
2916:   /**
2917:    * This method selects the next tab in the run.
2918:    *
2919:    * @param current The current selected index.
2920:    */
2921:   protected void selectNextTabInRun(int current)
2922:   {
2923:     tabPane.setSelectedIndex(getNextTabIndexInRun(tabPane.getTabCount(),
2924:                                                   current));
2925:   }
2926: 
2927:   /**
2928:    * This method selects the previous tab in the run.
2929:    *
2930:    * @param current The current selected index.
2931:    */
2932:   protected void selectPreviousTabInRun(int current)
2933:   {
2934:     tabPane.setSelectedIndex(getPreviousTabIndexInRun(tabPane.getTabCount(),
2935:                                                       current));
2936:   }
2937: 
2938:   /**
2939:    * This method selects the next tab (regardless of runs).
2940:    *
2941:    * @param current The current selected index.
2942:    */
2943:   protected void selectNextTab(int current)
2944:   {
2945:     tabPane.setSelectedIndex(getNextTabIndex(current));
2946:   }
2947: 
2948:   /**
2949:    * This method selects the previous tab (regardless of runs).
2950:    *
2951:    * @param current The current selected index.
2952:    */
2953:   protected void selectPreviousTab(int current)
2954:   {
2955:     tabPane.setSelectedIndex(getPreviousTabIndex(current));
2956:   }
2957: 
2958:   /**
2959:    * This method selects the correct tab given an offset from the current tab
2960:    * index. If the tab placement is TOP or BOTTOM, the offset will be in the
2961:    * y direction, otherwise, it will be in the x direction. A new coordinate
2962:    * will be found by adding the offset to the current location of the tab.
2963:    * The tab that the new location will be selected.
2964:    *
2965:    * @param tabPlacement The JTabbedPane's tab placement.
2966:    * @param tabIndex The tab to start from.
2967:    * @param offset The coordinate offset.
2968:    */
2969:   protected void selectAdjacentRunTab(int tabPlacement, int tabIndex,
2970:                                       int offset)
2971:   {
2972:     int x = rects[tabIndex].x + rects[tabIndex].width / 2;
2973:     int y = rects[tabIndex].y + rects[tabIndex].height / 2;
2974: 
2975:     switch (tabPlacement)
2976:     {
2977:     case SwingConstants.TOP:
2978:     case SwingConstants.BOTTOM:
2979:       y += offset;
2980:       break;
2981:     case SwingConstants.RIGHT:
2982:     case SwingConstants.LEFT:
2983:       x += offset;
2984:       break;
2985:     }
2986: 
2987:     int index = tabForCoordinate(tabPane, x, y);
2988:     if (index != -1)
2989:       tabPane.setSelectedIndex(index);
2990:   }
2991: 
2992:   // This method is called when you press up/down to cycle through tab runs.
2993:   // it returns the distance (between the two runs' x/y position.
2994:   // where one run is the current selected run and the other run is the run in the
2995:   // direction of the scroll (dictated by the forward flag)
2996:   // the offset is an absolute value of the difference
2997: 
2998:   /**
2999:    * This method calculates the offset distance for use in
3000:    * selectAdjacentRunTab. The offset returned will be a difference in the y
3001:    * coordinate between the run in  the desired direction and the current run
3002:    * (for tabPlacement in TOP or BOTTOM). Use x coordinate for LEFT and
3003:    * RIGHT.
3004:    *
3005:    * @param tabPlacement The JTabbedPane's tab placement.
3006:    * @param tabCount The number of tabs.
3007:    * @param tabIndex The starting index.
3008:    * @param forward If forward, the run in the desired direction will be the
3009:    *        next run.
3010:    *
3011:    * @return The offset between the two runs.
3012:    */
3013:   protected int getTabRunOffset(int tabPlacement, int tabCount, int tabIndex,
3014:                                 boolean forward)
3015:   {
3016:     int currRun = getRunForTab(tabCount, tabIndex);
3017:     int offset;
3018:     int nextRun = (forward) ? getNextTabRun(currRun) : getPreviousTabRun(currRun);
3019:     if (tabPlacement == SwingConstants.TOP
3020:         || tabPlacement == SwingConstants.BOTTOM)
3021:       offset = rects[lastTabInRun(tabCount, nextRun)].y
3022:                - rects[lastTabInRun(tabCount, currRun)].y;
3023:     else
3024:       offset = rects[lastTabInRun(tabCount, nextRun)].x
3025:                - rects[lastTabInRun(tabCount, currRun)].x;
3026:     return offset;
3027:   }
3028: 
3029:   /**
3030:    * This method returns the previous tab index.
3031:    *
3032:    * @param base The index to start from.
3033:    *
3034:    * @return The previous tab index.
3035:    */
3036:   protected int getPreviousTabIndex(int base)
3037:   {
3038:     base--;
3039:     if (base < 0)
3040:       return tabPane.getTabCount() - 1;
3041:     return base;
3042:   }
3043: 
3044:   /**
3045:    * This method returns the next tab index.
3046:    *
3047:    * @param base The index to start from.
3048:    *
3049:    * @return The next tab index.
3050:    */
3051:   protected int getNextTabIndex(int base)
3052:   {
3053:     base++;
3054:     if (base == tabPane.getTabCount())
3055:       return 0;
3056:     return base;
3057:   }
3058: 
3059:   /**
3060:    * This method returns the next tab index in the run. If the next index is
3061:    * out of this run, it will return the starting tab index for the run.
3062:    *
3063:    * @param tabCount The number of tabs.
3064:    * @param base The index to start from.
3065:    *
3066:    * @return The next tab index in the run.
3067:    */
3068:   protected int getNextTabIndexInRun(int tabCount, int base)
3069:   {
3070:     int index = getNextTabIndex(base);
3071:     int run = getRunForTab(tabCount, base);
3072:     if (index == lastTabInRun(tabCount, run) + 1)
3073:       index = lastTabInRun(tabCount, getPreviousTabRun(run)) + 1;
3074:     return getNextTabIndex(base);
3075:   }
3076: 
3077:   /**
3078:    * This method returns the previous tab index in the run. If the previous
3079:    * index is out of this run, it will return the last index for the run.
3080:    *
3081:    * @param tabCount The number of tabs.
3082:    * @param base The index to start from.
3083:    *
3084:    * @return The previous tab index in the run.
3085:    */
3086:   protected int getPreviousTabIndexInRun(int tabCount, int base)
3087:   {
3088:     int index = getPreviousTabIndex(base);
3089:     int run = getRunForTab(tabCount, base);
3090:     if (index == lastTabInRun(tabCount, getPreviousTabRun(run)))
3091:       index = lastTabInRun(tabCount, run);
3092:     return getPreviousTabIndex(base);
3093:   }
3094: 
3095:   /**
3096:    * This method returns the index of the previous run.
3097:    *
3098:    * @param baseRun The run to start from.
3099:    *
3100:    * @return The index of the previous run.
3101:    */
3102:   protected int getPreviousTabRun(int baseRun)
3103:   {
3104:     if (getTabRunCount(tabPane) == 1)
3105:       return 1;
3106: 
3107:     int prevRun = --baseRun;
3108:     if (prevRun < 0)
3109:       prevRun = getTabRunCount(tabPane) - 1;
3110:     return prevRun;
3111:   }
3112: 
3113:   /**
3114:    * This method returns the index of the next run.
3115:    *
3116:    * @param baseRun The run to start from.
3117:    *
3118:    * @return The index of the next run.
3119:    */
3120:   protected int getNextTabRun(int baseRun)
3121:   {
3122:     if (getTabRunCount(tabPane) == 1)
3123:       return 1;
3124: 
3125:     int nextRun = ++baseRun;
3126:     if (nextRun == getTabRunCount(tabPane))
3127:       nextRun = 0;
3128:     return nextRun;
3129:   }
3130: 
3131:   /**
3132:    * This method rotates the insets given a direction to rotate them in.
3133:    * Target placement should be one of TOP, LEFT, BOTTOM, RIGHT. The  rotated
3134:    * insets will be stored in targetInsets. Passing in TOP as  the direction
3135:    * does nothing. Passing in LEFT switches top and left, right and bottom.
3136:    * Passing in BOTTOM switches top and bottom. Passing in RIGHT switches top
3137:    * for left, left for bottom, bottom for right, and right for top.
3138:    *
3139:    * @param topInsets The reference insets.
3140:    * @param targetInsets An Insets object to store the new insets.
3141:    * @param targetPlacement The rotation direction.
3142:    */
3143:   protected static void rotateInsets(Insets topInsets, Insets targetInsets,
3144:                                      int targetPlacement)
3145:   {
3146:     // Sun's version will happily throw an NPE if params are null,
3147:     // so I won't check it either.
3148:     switch (targetPlacement)
3149:     {
3150:     case SwingConstants.TOP:
3151:       targetInsets.top = topInsets.top;
3152:       targetInsets.left = topInsets.left;
3153:       targetInsets.right = topInsets.right;
3154:       targetInsets.bottom = topInsets.bottom;
3155:       break;
3156:     case SwingConstants.LEFT:
3157:       targetInsets.left = topInsets.top;
3158:       targetInsets.top = topInsets.left;
3159:       targetInsets.right = topInsets.bottom;
3160:       targetInsets.bottom = topInsets.right;
3161:       break;
3162:     case SwingConstants.BOTTOM:
3163:       targetInsets.top = topInsets.bottom;
3164:       targetInsets.bottom = topInsets.top;
3165:       targetInsets.left = topInsets.left;
3166:       targetInsets.right = topInsets.right;
3167:       break;
3168:     case SwingConstants.RIGHT:
3169:       targetInsets.top = topInsets.left;
3170:       targetInsets.left = topInsets.bottom;
3171:       targetInsets.bottom = topInsets.right;
3172:       targetInsets.right = topInsets.top;
3173:       break;
3174:     }
3175:   }
3176: 
3177:   /**
3178:    * Sets the tab which should be highlighted when in rollover mode. And
3179:    * <code>index</code> of <code>-1</code> means that the rollover tab
3180:    * is deselected (i.e. the mouse is outside of the tabarea).
3181:    *
3182:    * @param index the index of the tab that is under the mouse, <code>-1</code>
3183:    *        for no tab
3184:    *
3185:    * @since 1.5
3186:    */
3187:   protected void setRolloverTab(int index)
3188:   {
3189:     rolloverTab = index;
3190:   }
3191: 
3192:   /**
3193:    * Retunrs the index of the tab over which the mouse is currently moving,
3194:    * or <code>-1</code> for no tab.
3195:    *
3196:    * @return the index of the tab over which the mouse is currently moving,
3197:    *         or <code>-1</code> for no tab
3198:    *
3199:    * @since 1.5
3200:    */
3201:   protected int getRolloverTab()
3202:   {
3203:     return rolloverTab;
3204:   }
3205: }