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