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