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.revalidate();
 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:       assureRectsCreated(tabPane.getTabCount());
 245:       contentRect = SwingUtilities.calculateInnerArea(tabPane, contentRect);
 246: 
 247:       calculateTabRects(tabPane.getTabPlacement(), tabPane.getTabCount());
 248: 
 249:       if (tabPane.getSelectedIndex() != -1)
 250:         {
 251:           Component visible = getVisibleComponent();
 252:           Insets insets = getContentBorderInsets(tabPane.getTabPlacement());
 253:           if (visible != null)
 254:             visible.setBounds(contentRect.x + insets.left,
 255:                               contentRect.y + insets.top,
 256:                               contentRect.width - insets.left - insets.right,
 257:                               contentRect.height - insets.top - insets.bottom);
 258:         }
 259:     }
 260: 
 261:     /**
 262:      * This method calculates the size of the the JTabbedPane.
 263:      *
 264:      * @param minimum Whether the JTabbedPane will try to be as small as it
 265:      *        can.
 266:      *
 267:      * @return The desired size of the JTabbedPane.
 268:      */
 269:     protected Dimension calculateSize(boolean minimum)
 270:     {
 271:       int tabPlacement = tabPane.getTabPlacement();
 272:       int width = 0;
 273:       int height = 0;
 274: 
 275:       int componentHeight = 0;
 276:       int componentWidth = 0;
 277:       Component c;
 278:       Dimension dims;
 279:       for (int i = 0; i < tabPane.getTabCount(); i++)
 280:         {
 281:           c = tabPane.getComponentAt(i);
 282:           if (c == null)
 283:             continue;
 284:           calcRect = c.getBounds();
 285:           dims = c.getPreferredSize();
 286:           if (dims != null)
 287:             {
 288:               componentHeight = Math.max(componentHeight, dims.height);
 289:               componentWidth = Math.max(componentWidth, dims.width);
 290:             }
 291:         }
 292:       if (tabPlacement == SwingConstants.TOP
 293:           || tabPlacement == SwingConstants.BOTTOM)
 294:         {
 295:           int min = calculateMaxTabWidth(tabPlacement);
 296:           width = Math.max(min, componentWidth);
 297:           
 298:           int tabAreaHeight = preferredTabAreaHeight(tabPlacement, width);
 299:           height = tabAreaHeight + componentHeight;
 300:         }
 301:       else
 302:         {
 303:           int min = calculateMaxTabHeight(tabPlacement);
 304:           height = Math.max(min, componentHeight);
 305:           
 306:           int tabAreaWidth = preferredTabAreaWidth(tabPlacement, height);
 307:           width = tabAreaWidth + componentWidth;
 308:         }
 309: 
 310:       return new Dimension(width, height);
 311:     }
 312: 
 313:     // if tab placement is LEFT OR RIGHT, they share width.
 314:     // if tab placement is TOP OR BOTTOM, they share height
 315:     // PRE STEP: finds the default sizes for the labels as well as their locations.
 316:     // AND where they will be placed within the run system.
 317:     // 1. calls normalizeTab Runs.
 318:     // 2. calls rotate tab runs.
 319:     // 3. pads the tab runs.
 320:     // 4. pads the selected tab.
 321: 
 322:     /**
 323:      * This method is called to calculate the tab rectangles.  This method
 324:      * will calculate the size and position of all  rectangles (taking into
 325:      * account which ones should be in which tab run). It will pad them and
 326:      * normalize them  as necessary.
 327:      *
 328:      * @param tabPlacement The JTabbedPane's tab placement.
 329:      * @param tabCount The run the current selection is in.
 330:      */
 331:     protected void calculateTabRects(int tabPlacement, int tabCount)
 332:     {
 333:       if (tabCount == 0)
 334:         return;
 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: 
 948:       FontMetrics fm = getFontMetrics();
 949:       SwingUtilities.calculateInnerArea(tabPane, calcRect);
 950:       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
 951:       Insets insets = tabPane.getInsets();
 952:       int runs = 1;
 953:       int start = 0;
 954:       int top = 0;
 955:       if (tabPlacement == SwingConstants.TOP
 956:           || tabPlacement == SwingConstants.BOTTOM)
 957:         {
 958:           int maxHeight = calculateMaxTabHeight(tabPlacement);
 959:           calcRect.width -= tabAreaInsets.left + tabAreaInsets.right;
 960:           start = tabAreaInsets.left + insets.left;
 961:           int width = 0;
 962:           int runWidth = start;
 963:           top = insets.top + tabAreaInsets.top;
 964:           for (int i = 0; i < tabCount; i++)
 965:             {
 966:               width = calculateTabWidth(tabPlacement, i, fm);
 967: 
 968:               rects[i] = new Rectangle(runWidth, top, width, maxHeight);
 969:               runWidth += width;
 970:             }
 971:           tabAreaRect.width = tabPane.getWidth() - insets.left - insets.right;
 972:           tabAreaRect.height = runs * maxTabHeight
 973:           - (runs - 1) * tabRunOverlay
 974:           + tabAreaInsets.top + tabAreaInsets.bottom;
 975:           contentRect.width = tabAreaRect.width;
 976:           contentRect.height = tabPane.getHeight() - insets.top
 977:           - insets.bottom - tabAreaRect.height;
 978:           contentRect.x = insets.left;
 979:           tabAreaRect.x = insets.left;
 980:           if (tabPlacement == SwingConstants.BOTTOM)
 981:             {
 982:               contentRect.y = insets.top;
 983:               tabAreaRect.y = contentRect.y + contentRect.height;
 984:             }
 985:           else
 986:             {
 987:               tabAreaRect.y = insets.top;
 988:               contentRect.y = tabAreaRect.y + tabAreaRect.height;
 989:             }
 990:         }
 991:       else
 992:         {
 993:           int maxWidth = calculateMaxTabWidth(tabPlacement);
 994: 
 995:           calcRect.height -= tabAreaInsets.top + tabAreaInsets.bottom;
 996:           int height = 0;
 997:           start = tabAreaInsets.top + insets.top;
 998:           int runHeight = start;
 999:           int fontHeight = fm.getHeight();
1000:           top = insets.left + tabAreaInsets.left;
1001:           for (int i = 0; i < tabCount; i++)
1002:             {
1003:               height = calculateTabHeight(tabPlacement, i, fontHeight);
1004:               rects[i] = new Rectangle(top, runHeight, maxWidth, height);
1005:               runHeight += height;
1006:             }
1007:           tabAreaRect.width = runs * maxTabWidth - (runs - 1) * tabRunOverlay
1008:           + tabAreaInsets.left + tabAreaInsets.right;
1009:           tabAreaRect.height = tabPane.getHeight() - insets.top
1010:           - insets.bottom;
1011:           tabAreaRect.y = insets.top;
1012:           contentRect.width = tabPane.getWidth() - insets.left - insets.right
1013:           - tabAreaRect.width;
1014:           contentRect.height = tabAreaRect.height;
1015:           contentRect.y = insets.top;
1016:           if (tabPlacement == SwingConstants.LEFT)
1017:             {
1018:               tabAreaRect.x = insets.left;
1019:               contentRect.x = tabAreaRect.x + tabAreaRect.width;
1020:             }
1021:           else
1022:             {
1023:               contentRect.x = insets.left;
1024:               tabAreaRect.x = contentRect.x + contentRect.width;
1025:             }
1026:         }
1027:       runCount = runs;
1028: 
1029:       padSelectedTab(tabPlacement, tabPane.getSelectedIndex());
1030:     }
1031: 
1032:     /**
1033:      * This method is called when the JTabbedPane is laid out in
1034:      * SCROLL_TAB_LAYOUT. It finds the position for all components in the
1035:      * JTabbedPane.
1036:      *
1037:      * @param pane The JTabbedPane to be laid out.
1038:      */
1039:     public void layoutContainer(Container pane)
1040:     {
1041:       super.layoutContainer(pane);
1042:       int tabCount = tabPane.getTabCount();
1043:       Point p = null;
1044:       if (tabCount == 0)
1045:         return;
1046:       int tabPlacement = tabPane.getTabPlacement();
1047:       incrButton.setVisible(false);
1048:       decrButton.setVisible(false);
1049:       if (tabPlacement == SwingConstants.TOP
1050:           || tabPlacement == SwingConstants.BOTTOM)
1051:         {
1052:           if (tabAreaRect.x + tabAreaRect.width < rects[tabCount - 1].x
1053:               + rects[tabCount - 1].width)
1054:             {
1055:               Dimension incrDims = incrButton.getPreferredSize();
1056:               Dimension decrDims = decrButton.getPreferredSize();
1057: 
1058:               decrButton.setBounds(tabAreaRect.x + tabAreaRect.width
1059:                                    - incrDims.width - decrDims.width,
1060:                                    tabAreaRect.y, decrDims.width,
1061:                                    tabAreaRect.height);
1062:               incrButton.setBounds(tabAreaRect.x + tabAreaRect.width
1063:                                    - incrDims.width, tabAreaRect.y,
1064:                                    decrDims.width, tabAreaRect.height);
1065: 
1066:               tabAreaRect.width -= decrDims.width + incrDims.width;
1067:               incrButton.setVisible(true);
1068:               decrButton.setVisible(true);
1069:             }
1070:         }
1071: 
1072:       if (tabPlacement == SwingConstants.LEFT
1073:           || tabPlacement == SwingConstants.RIGHT)
1074:         {
1075:           if (tabAreaRect.y + tabAreaRect.height < rects[tabCount - 1].y
1076:               + rects[tabCount - 1].height)
1077:             {
1078:               Dimension incrDims = incrButton.getPreferredSize();
1079:               Dimension decrDims = decrButton.getPreferredSize();
1080: 
1081:               decrButton.setBounds(tabAreaRect.x,
1082:                                    tabAreaRect.y + tabAreaRect.height
1083:                                    - incrDims.height - decrDims.height,
1084:                                    tabAreaRect.width, decrDims.height);
1085:               incrButton.setBounds(tabAreaRect.x,
1086:                                    tabAreaRect.y + tabAreaRect.height
1087:                                    - incrDims.height, tabAreaRect.width,
1088:                                    incrDims.height);
1089: 
1090:               tabAreaRect.height -= decrDims.height + incrDims.height;
1091:               incrButton.setVisible(true);
1092:               decrButton.setVisible(true);
1093:             }
1094:         }
1095:       viewport.setBounds(tabAreaRect.x, tabAreaRect.y, tabAreaRect.width,
1096:                          tabAreaRect.height);
1097:       int tabC = tabPane.getTabCount() - 1;
1098:       if (tabCount > 0)
1099:         {
1100:           int w = Math.max(rects[tabC].width + rects[tabC].x, tabAreaRect.width);
1101:           int h = Math.max(rects[tabC].height, tabAreaRect.height);
1102:           p = findPointForIndex(currentScrollLocation);
1103:           
1104:           // we want to cover that entire space so that borders that run under
1105:           // the tab area don't show up when we move the viewport around.
1106:           panel.setSize(w + p.x, h + p.y);
1107:         }
1108:       viewport.setViewPosition(p);
1109:       viewport.repaint();
1110:     }
1111:   }
1112: 
1113:   /**
1114:    * This class handles ChangeEvents from the JTabbedPane.
1115:    *
1116:    * @specnote Apparently this class was intended to be protected,
1117:    *           but was made public by a compiler bug and is now
1118:    *           public for compatibility.
1119:    */
1120:   public class TabSelectionHandler implements ChangeListener
1121:   {
1122:     /**
1123:      * This method is called whenever a ChangeEvent is fired from the
1124:      * JTabbedPane.
1125:      *
1126:      * @param e The ChangeEvent fired.
1127:      */
1128:     public void stateChanged(ChangeEvent e)
1129:     {
1130:       selectedRun = getRunForTab(tabPane.getTabCount(),
1131:                                  tabPane.getSelectedIndex());
1132:       tabPane.revalidate();
1133:       tabPane.repaint();
1134:     }
1135:   }
1136: 
1137:   /**
1138:    * This helper class is a JPanel that fits inside the ScrollViewport. This
1139:    * panel's sole job is to paint the tab rectangles inside the  viewport so
1140:    * that it's clipped correctly.
1141:    */
1142:   private class ScrollingPanel extends JPanel
1143:   {
1144:     /**
1145:      * This is a private UI class for our panel.
1146:      */
1147:     private class ScrollingPanelUI extends BasicPanelUI
1148:     {
1149:       /**
1150:        * This method overrides the default paint method. It paints the tab
1151:        * rectangles for the JTabbedPane in the panel.
1152:        *
1153:        * @param g The Graphics object to paint with.
1154:        * @param c The JComponent to paint.
1155:        */
1156:       public void paint(Graphics g, JComponent c)
1157:       {
1158:         paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1159:       }
1160:     }
1161: 
1162:     /**
1163:      * This method overrides the updateUI method. It makes the default UI for
1164:      * this ScrollingPanel to be  a ScrollingPanelUI.
1165:      */
1166:     public void updateUI()
1167:     {
1168:       setUI((PanelUI) new ScrollingPanelUI());
1169:     }
1170:   }
1171: 
1172:   /**
1173:    * This is a helper class that paints the panel that paints tabs. This
1174:    * custom JViewport is used so that the tabs painted in the panel will be
1175:    * clipped. This class implements UIResource so tabs are not added when
1176:    * this objects of this class are added to the  JTabbedPane.
1177:    */
1178:   private class ScrollingViewport extends JViewport implements UIResource
1179:   {
1180:     // TODO: Maybe remove this inner class.
1181:   }
1182: 
1183:   /**
1184:    * This is a helper class that implements UIResource so it is not added as a
1185:    * tab when an object of this class is added to the JTabbedPane.
1186:    */
1187:   private class ScrollingButton extends BasicArrowButton implements UIResource
1188:   {
1189:     /**
1190:      * Creates a ScrollingButton given the direction.
1191:      *
1192:      * @param dir The direction to point in.
1193:      */
1194:     public ScrollingButton(int dir)
1195:     {
1196:       super(dir);
1197:     }
1198:   }
1199: 
1200:   /** The button that increments the current scroll location.
1201:    * This is package-private to avoid an accessor method.  */
1202:   transient ScrollingButton incrButton;
1203: 
1204:   /** The button that decrements the current scroll location.
1205:    * This is package-private to avoid an accessor method.  */
1206:   transient ScrollingButton decrButton;
1207: 
1208:   /** The viewport used to display the tabs.
1209:    * This is package-private to avoid an accessor method.  */
1210:   transient ScrollingViewport viewport;
1211: 
1212:   /** The panel inside the viewport that paints the tabs.
1213:    * This is package-private to avoid an accessor method.  */
1214:   transient ScrollingPanel panel;
1215: 
1216:   /** The starting visible tab in the run in SCROLL_TAB_MODE.
1217:    * This is package-private to avoid an accessor method.  */
1218:   transient int currentScrollLocation;
1219: 
1220:   /** A reusable rectangle. */
1221:   protected Rectangle calcRect;
1222: 
1223:   /** An array of Rectangles keeping track of the tabs' area and position. */
1224:   protected Rectangle[] rects;
1225: 
1226:   /** The insets around the content area. */
1227:   protected Insets contentBorderInsets;
1228: 
1229:   /** The extra insets around the selected tab. */
1230:   protected Insets selectedTabPadInsets;
1231: 
1232:   /** The insets around the tab area. */
1233:   protected Insets tabAreaInsets;
1234: 
1235:   /** The insets around each and every tab. */
1236:   protected Insets tabInsets;
1237: 
1238:   /**
1239:    * The outer bottom and right edge color for both the tab and content
1240:    * border.
1241:    */
1242:   protected Color darkShadow;
1243: 
1244:   /** The color of the focus outline on the selected tab. */
1245:   protected Color focus;
1246: 
1247:   /** FIXME: find a use for this. */
1248:   protected Color highlight;
1249: 
1250:   /** The top and left edge color for both the tab and content border. */
1251:   protected Color lightHighlight;
1252: 
1253:   /** The inner bottom and right edge color for the tab and content border. */
1254:   protected Color shadow;
1255: 
1256:   /** The maximum tab height. */
1257:   protected int maxTabHeight;
1258: 
1259:   /** The maximum tab width. */
1260:   protected int maxTabWidth;
1261: 
1262:   /** The number of runs in the JTabbedPane. */
1263:   protected int runCount;
1264: 
1265:   /** The index of the run that the selected index is in. */
1266:   protected int selectedRun;
1267: 
1268:   /** The amount of space each run overlaps the previous by. */
1269:   protected int tabRunOverlay;
1270: 
1271:   /** The gap between text and label */
1272:   protected int textIconGap;
1273: 
1274:   // Keeps track of tab runs.
1275:   // The organization of this array is as follows (lots of experimentation to
1276:   // figure this out)
1277:   // index 0 = furthest away from the component area (aka outer run)
1278:   // index 1 = closest to component area (aka selected run)
1279:   // index > 1 = listed in order leading from selected run to outer run.
1280:   // each int in the array is the tab index + 1 (counting starts at 1)
1281:   // for the last tab in the run. (same as the rects array)
1282: 
1283:   /** This array keeps track of which tabs are in which run. See above. */
1284:   protected int[] tabRuns;
1285: 
1286:   /**
1287:    * This is the keystroke for moving down.
1288:    *
1289:    * @deprecated 1.3
1290:    */
1291:   protected KeyStroke downKey;
1292: 
1293:   /**
1294:    * This is the keystroke for moving left.
1295:    *
1296:    * @deprecated 1.3
1297:    */
1298:   protected KeyStroke leftKey;
1299: 
1300:   /**
1301:    * This is the keystroke for moving right.
1302:    *
1303:    * @deprecated 1.3
1304:    */
1305:   protected KeyStroke rightKey;
1306: 
1307:   /**
1308:    * This is the keystroke for moving up.
1309:    *
1310:    * @deprecated 1.3
1311:    */
1312:   protected KeyStroke upKey;
1313: 
1314:   /** The listener that listens for focus events. */
1315:   protected FocusListener focusListener;
1316: 
1317:   /** The listener that listens for mouse events. */
1318:   protected MouseListener mouseListener;
1319: 
1320:   /** The listener that listens for property change events. */
1321:   protected PropertyChangeListener propertyChangeListener;
1322: 
1323:   /** The listener that listens for change events. */
1324:   protected ChangeListener tabChangeListener;
1325: 
1326:   /** The tab pane that this UI paints. */
1327:   protected JTabbedPane tabPane;
1328: 
1329:   /** The current layout manager for the tabPane.
1330:    * This is package-private to avoid an accessor method.  */
1331:   transient LayoutManager layoutManager;
1332: 
1333:   /** The rectangle that describes the tab area's position and size.
1334:    * This is package-private to avoid an accessor method.  */
1335:   transient Rectangle tabAreaRect;
1336: 
1337:   /** The rectangle that describes the content area's position and
1338:    * size.  This is package-private to avoid an accessor method.  */
1339:   transient Rectangle contentRect;
1340: 
1341:   /**
1342:    * Creates a new BasicTabbedPaneUI object.
1343:    */
1344:   public BasicTabbedPaneUI()
1345:   {
1346:     super();
1347:     rects = new Rectangle[0];
1348:     tabRuns = new int[10];
1349:   }
1350: 
1351:   /**
1352:    * This method creates a ScrollingButton that  points in the appropriate
1353:    * direction for an increasing button.
1354:    * This is package-private to avoid an accessor method.
1355:    *
1356:    * @return The increase ScrollingButton.
1357:    */
1358:   ScrollingButton createIncreaseButton()
1359:   {
1360:     if (incrButton == null)
1361:       incrButton = new ScrollingButton(SwingConstants.NORTH);
1362:     if (tabPane.getTabPlacement() == SwingConstants.TOP
1363:         || tabPane.getTabPlacement() == SwingConstants.BOTTOM)
1364:       incrButton.setDirection(SwingConstants.EAST);
1365:     else
1366:       incrButton.setDirection(SwingConstants.SOUTH);
1367:     return incrButton;
1368:   }
1369: 
1370:   /**
1371:    * This method creates a ScrollingButton that points in the appropriate
1372:    * direction for a decreasing button.
1373:    * This is package-private to avoid an accessor method.
1374:    *
1375:    * @return The decrease ScrollingButton.
1376:    */
1377:   ScrollingButton createDecreaseButton()
1378:   {
1379:     if (decrButton == null)
1380:       decrButton = new ScrollingButton(SwingConstants.SOUTH);
1381:     if (tabPane.getTabPlacement() == SwingConstants.TOP
1382:         || tabPane.getTabPlacement() == SwingConstants.BOTTOM)
1383:       decrButton.setDirection(SwingConstants.WEST);
1384:     else
1385:       decrButton.setDirection(SwingConstants.NORTH);
1386:     return decrButton;
1387:   }
1388: 
1389:   /**
1390:    * This method finds the point to set the view  position at given the index
1391:    * of a tab. The tab will be the first visible tab in the run.
1392:    * This is package-private to avoid an accessor method.
1393:    *
1394:    * @param index The index of the first visible tab.
1395:    *
1396:    * @return The position of the first visible tab.
1397:    */
1398:   Point findPointForIndex(int index)
1399:   {
1400:     int tabPlacement = tabPane.getTabPlacement();
1401:     int selectedIndex = tabPane.getSelectedIndex();
1402:     Insets insets = getSelectedTabPadInsets(tabPlacement);
1403:     int w = 0;
1404:     int h = 0;
1405: 
1406:     if (tabPlacement == TOP || tabPlacement == BOTTOM)
1407:       {
1408:         if (index > 0)
1409:           {
1410:             w += rects[index - 1].x + rects[index - 1].width;
1411:             if (index > selectedIndex)
1412:               w -= insets.left + insets.right;
1413:           }
1414:       }
1415: 
1416:     else
1417:       {
1418:         if (index > 0)
1419:           {
1420:             h += rects[index - 1].y + rects[index - 1].height;
1421:             if (index > selectedIndex)
1422:               h -= insets.top + insets.bottom;
1423:           }
1424:       }
1425: 
1426:     Point p = new Point(w, h);
1427:     return p;
1428:   }
1429: 
1430:   /**
1431:    * This method creates a new BasicTabbedPaneUI.
1432:    *
1433:    * @param c The JComponent to create a UI for.
1434:    *
1435:    * @return A new BasicTabbedPaneUI.
1436:    */
1437:   public static ComponentUI createUI(JComponent c)
1438:   {
1439:     return new BasicTabbedPaneUI();
1440:   }
1441: 
1442:   /**
1443:    * This method installs the UI for the given JComponent.
1444:    *
1445:    * @param c The JComponent to install the UI for.
1446:    */
1447:   public void installUI(JComponent c)
1448:   {
1449:     super.installUI(c);
1450:     if (c instanceof JTabbedPane)
1451:       {
1452:         tabPane = (JTabbedPane) c;
1453:         
1454:         installComponents();
1455:         installDefaults();
1456:         installListeners();
1457:         installKeyboardActions();
1458:         
1459:         layoutManager = createLayoutManager();
1460:         tabPane.setLayout(layoutManager);
1461:       }
1462:   }
1463: 
1464:   /**
1465:    * This method uninstalls the UI for the  given JComponent.
1466:    *
1467:    * @param c The JComponent to uninstall the UI for.
1468:    */
1469:   public void uninstallUI(JComponent c)
1470:   {
1471:     layoutManager = null;
1472: 
1473:     uninstallKeyboardActions();
1474:     uninstallListeners();
1475:     uninstallDefaults();
1476:     uninstallComponents();
1477: 
1478:     tabPane = null;
1479:   }
1480: 
1481:   /**
1482:    * This method creates the appropriate layout manager for the JTabbedPane's
1483:    * current tab layout policy. If the tab layout policy is
1484:    * SCROLL_TAB_LAYOUT, then all the associated components that need to be
1485:    * created will be done so now.
1486:    *
1487:    * @return A layout manager given the tab layout policy.
1488:    */
1489:   protected LayoutManager createLayoutManager()
1490:   {
1491:     if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT)
1492:       return new TabbedPaneLayout();
1493:     else
1494:       {
1495:         incrButton = createIncreaseButton();
1496:         decrButton = createDecreaseButton();
1497:         viewport = new ScrollingViewport();
1498:         viewport.setLayout(null);
1499:         panel = new ScrollingPanel();
1500:         viewport.setView(panel);
1501:         tabPane.add(incrButton);
1502:         tabPane.add(decrButton);
1503:         tabPane.add(viewport);
1504:         currentScrollLocation = 0;
1505:         decrButton.setEnabled(false);
1506:         panel.addMouseListener(mouseListener);
1507:         incrButton.addMouseListener(mouseListener);
1508:         decrButton.addMouseListener(mouseListener);
1509:         viewport.setBackground(Color.LIGHT_GRAY);
1510: 
1511:         return new TabbedPaneScrollLayout();
1512:       }
1513:   }
1514: 
1515:   /**
1516:    * This method installs components for this JTabbedPane.
1517:    */
1518:   protected void installComponents()
1519:   {
1520:     // Nothing to be done.
1521:   }
1522: 
1523:   /**
1524:    * This method uninstalls components for this JTabbedPane.
1525:    */
1526:   protected void uninstallComponents()
1527:   {
1528:     // Nothing to be done.
1529:   }
1530: 
1531:   /**
1532:    * This method installs defaults for the Look and Feel.
1533:    */
1534:   protected void installDefaults()
1535:   {
1536:     LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
1537:                                      "TabbedPane.foreground",
1538:                                      "TabbedPane.font");
1539:     tabPane.setOpaque(false);
1540: 
1541:     highlight = UIManager.getColor("TabbedPane.highlight");
1542:     lightHighlight = UIManager.getColor("TabbedPane.lightHighlight");
1543: 
1544:     shadow = UIManager.getColor("TabbedPane.shadow");
1545:     darkShadow = UIManager.getColor("TabbedPane.darkShadow");
1546: 
1547:     focus = UIManager.getColor("TabbedPane.focus");
1548: 
1549:     textIconGap = UIManager.getInt("TabbedPane.textIconGap");
1550:     tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
1551: 
1552:     tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
1553:     selectedTabPadInsets = UIManager.getInsets("TabbedPane.tabbedPaneTabPadInsets");
1554:     tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
1555:     contentBorderInsets = UIManager.getInsets("TabbedPane.tabbedPaneContentBorderInsets");
1556: 
1557:     calcRect = new Rectangle();
1558:     tabRuns = new int[10];
1559:     tabAreaRect = new Rectangle();
1560:     contentRect = new Rectangle();
1561:   }
1562: 
1563:   /**
1564:    * This method uninstalls defaults for the Look and Feel.
1565:    */
1566:   protected void uninstallDefaults()
1567:   {
1568:     calcRect = null;
1569:     tabAreaRect = null;
1570:     contentRect = null;
1571:     tabRuns = null;
1572: 
1573:     contentBorderInsets = null;
1574:     tabAreaInsets = null;
1575:     selectedTabPadInsets = null;
1576:     tabInsets = null;
1577: 
1578:     focus = null;
1579:     darkShadow = null;
1580:     shadow = null;
1581:     lightHighlight = null;
1582:     highlight = null;
1583: 
1584:     tabPane.setBackground(null);
1585:     tabPane.setForeground(null);
1586:     tabPane.setFont(null);
1587:   }
1588: 
1589:   /**
1590:    * This method creates and installs the listeners for this UI.
1591:    */
1592:   protected void installListeners()
1593:   {
1594:     mouseListener = createMouseListener();
1595:     tabChangeListener = createChangeListener();
1596:     propertyChangeListener = createPropertyChangeListener();
1597:     focusListener = createFocusListener();
1598: 
1599:     tabPane.addMouseListener(mouseListener);
1600:     tabPane.addChangeListener(tabChangeListener);
1601:     tabPane.addPropertyChangeListener(propertyChangeListener);
1602:     tabPane.addFocusListener(focusListener);
1603:   }
1604: 
1605:   /**
1606:    * This method removes and nulls the listeners for this UI.
1607:    */
1608:   protected void uninstallListeners()
1609:   {
1610:     tabPane.removeFocusListener(focusListener);
1611:     tabPane.removePropertyChangeListener(propertyChangeListener);
1612:     tabPane.removeChangeListener(tabChangeListener);
1613:     tabPane.removeMouseListener(mouseListener);
1614: 
1615:     focusListener = null;
1616:     propertyChangeListener = null;
1617:     tabChangeListener = null;
1618:     mouseListener = null;
1619:   }
1620: 
1621:   /**
1622:    * This method creates a new MouseListener.
1623:    *
1624:    * @return A new MouseListener.
1625:    */
1626:   protected MouseListener createMouseListener()
1627:   {
1628:     return new MouseHandler();
1629:   }
1630: 
1631:   /**
1632:    * This method creates a new FocusListener.
1633:    *
1634:    * @return A new FocusListener.
1635:    */
1636:   protected FocusListener createFocusListener()
1637:   {
1638:     return new FocusHandler();
1639:   }
1640: 
1641:   /**
1642:    * This method creates a new ChangeListener.
1643:    *
1644:    * @return A new ChangeListener.
1645:    */
1646:   protected ChangeListener createChangeListener()
1647:   {
1648:     return new TabSelectionHandler();
1649:   }
1650: 
1651:   /**
1652:    * This method creates a new PropertyChangeListener.
1653:    *
1654:    * @return A new PropertyChangeListener.
1655:    */
1656:   protected PropertyChangeListener createPropertyChangeListener()
1657:   {
1658:     return new PropertyChangeHandler();
1659:   }
1660: 
1661:   /**
1662:    * This method installs keyboard actions for the JTabbedPane.
1663:    */
1664:   protected void installKeyboardActions()
1665:   {
1666:     // FIXME: Implement.
1667:   }
1668: 
1669:   /**
1670:    * This method uninstalls keyboard actions for the JTabbedPane.
1671:    */
1672:   protected void uninstallKeyboardActions()
1673:   {
1674:     // FIXME: Implement.
1675:   }
1676: 
1677:   /**
1678:    * This method returns the minimum size of the JTabbedPane.
1679:    *
1680:    * @param c The JComponent to find a size for.
1681:    *
1682:    * @return The minimum size.
1683:    */
1684:   public Dimension getMinimumSize(JComponent c)
1685:   {
1686:     return layoutManager.minimumLayoutSize(tabPane);
1687:   }
1688: 
1689:   /**
1690:    * This method returns the maximum size of the JTabbedPane.
1691:    *
1692:    * @param c The JComponent to find a size for.
1693:    *
1694:    * @return The maximum size.
1695:    */
1696:   public Dimension getMaximumSize(JComponent c)
1697:   {
1698:     return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
1699:   }
1700: 
1701:   /**
1702:    * This method paints the JTabbedPane.
1703:    *
1704:    * @param g The Graphics object to paint with.
1705:    * @param c The JComponent to paint.
1706:    */
1707:   public void paint(Graphics g, JComponent c)
1708:   {
1709:     if (tabPane.getTabCount() == 0)
1710:       return;
1711:     if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT)
1712:       paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1713:     paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1714:   }
1715: 
1716:   /**
1717:    * This method paints the tab area. This includes painting the rectangles
1718:    * that make up the tabs.
1719:    *
1720:    * @param g The Graphics object to paint with.
1721:    * @param tabPlacement The JTabbedPane's tab placement.
1722:    * @param selectedIndex The selected index.
1723:    */
1724:   protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex)
1725:   {
1726:     Rectangle ir = new Rectangle();
1727:     Rectangle tr = new Rectangle();
1728: 
1729:     boolean isScroll = tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
1730: 
1731:     // Please note: the ordering of the painting is important. 
1732:     // we WANT to paint the outermost run first and then work our way in.
1733:     int tabCount = tabPane.getTabCount();
1734:     int currRun = 1;
1735:     
1736:     if (tabCount > runCount)
1737:       runCount = tabCount;
1738:     
1739:     if (tabCount < 1)
1740:       return;
1741:     
1742:     if (runCount > 1)
1743:       currRun = 0;    
1744:     for (int i = 0; i < runCount; i++)
1745:       {
1746:         int first = lastTabInRun(tabCount, getPreviousTabRun(currRun)) + 1;
1747:         if (isScroll)
1748:           first = currentScrollLocation;
1749:         else if (first == tabCount)
1750:           first = 0;
1751:         int last = lastTabInRun(tabCount, currRun);
1752:         if (isScroll)
1753:           {
1754:             for (int k = first; k < tabCount; k++)
1755:               {
1756:                 if (rects[k].x + rects[k].width - rects[first].x > viewport
1757:                     .getWidth())
1758:                   {
1759:                     last = k;
1760:                     break;
1761:                   }
1762:               }
1763:           }
1764: 
1765:         for (int j = first; j <= last; j++)
1766:           {
1767:             if (j != selectedIndex || isScroll)
1768:               paintTab(g, tabPlacement, rects, j, ir, tr);
1769:           }
1770:         currRun = getPreviousTabRun(currRun);
1771:       }
1772:     if (! isScroll)
1773:       paintTab(g, tabPlacement, rects, selectedIndex, ir, tr);
1774:   }
1775: 
1776:   /**
1777:    * This method paints an individual tab.
1778:    *
1779:    * @param g The Graphics object to paint with.
1780:    * @param tabPlacement The JTabbedPane's tab placement.
1781:    * @param rects The array of rectangles that keep the size and position of
1782:    *        the tabs.
1783:    * @param tabIndex The tab index to paint.
1784:    * @param iconRect The rectangle to use for the icon.
1785:    * @param textRect The rectangle to use for the text.
1786:    */
1787:   protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects,
1788:                           int tabIndex, Rectangle iconRect, Rectangle textRect)
1789:   {
1790:     FontMetrics fm = getFontMetrics();
1791:     Icon icon = getIconForTab(tabIndex);
1792:     String title = tabPane.getTitleAt(tabIndex);
1793:     boolean isSelected = tabIndex == tabPane.getSelectedIndex();
1794:     calcRect = getTabBounds(tabPane, tabIndex);
1795: 
1796:     int x = calcRect.x;
1797:     int y = calcRect.y;
1798:     int w = calcRect.width;
1799:     int h = calcRect.height;
1800:     if (getRunForTab(tabPane.getTabCount(), tabIndex) == 1)
1801:       {
1802:         Insets insets = getTabAreaInsets(tabPlacement);
1803:         switch (tabPlacement)
1804:         {
1805:         case TOP:
1806:           h += insets.bottom;
1807:           break;
1808:         case LEFT:
1809:           w += insets.right;
1810:           break;
1811:         case BOTTOM:
1812:           y -= insets.top;
1813:           h += insets.top;
1814:           break;
1815:         case RIGHT:
1816:           x -= insets.left;
1817:           w += insets.left;
1818:           break;
1819:         }
1820:       }
1821: 
1822:     layoutLabel(tabPlacement, fm, tabIndex, title, icon, calcRect, iconRect,
1823:                 textRect, isSelected);
1824:     paintTabBackground(g, tabPlacement, tabIndex, x, y, w, h, isSelected);
1825:     paintTabBorder(g, tabPlacement, tabIndex, x, y, w, h, isSelected);
1826: 
1827:     // FIXME: Paint little folding corner and jagged edge clipped tab.
1828:     if (icon != null)
1829:       paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
1830:     if (title != null && ! title.equals(""))
1831:       paintText(g, tabPlacement, tabPane.getFont(), fm, tabIndex, title,
1832:                 textRect, isSelected);
1833:   }
1834: 
1835:   /**
1836:    * This method lays out the tab and finds the location to paint the  icon
1837:    * and text.
1838:    *
1839:    * @param tabPlacement The JTabbedPane's tab placement.
1840:    * @param metrics The font metrics for the font to paint with.
1841:    * @param tabIndex The tab index to paint.
1842:    * @param title The string painted.
1843:    * @param icon The icon painted.
1844:    * @param tabRect The tab bounds.
1845:    * @param iconRect The calculated icon bounds.
1846:    * @param textRect The calculated text bounds.
1847:    * @param isSelected Whether this tab is selected.
1848:    */
1849:   protected void layoutLabel(int tabPlacement, FontMetrics metrics,
1850:                              int tabIndex, String title, Icon icon,
1851:                              Rectangle tabRect, Rectangle iconRect,
1852:                              Rectangle textRect, boolean isSelected)
1853:   {
1854:     SwingUtilities.layoutCompoundLabel(metrics, title, icon,
1855:                                        SwingConstants.CENTER,
1856:                                        SwingConstants.CENTER,
1857:                                        SwingConstants.CENTER,
1858:                                        SwingConstants.RIGHT, tabRect,
1859:                                        iconRect, textRect, textIconGap);
1860: 
1861:     int shiftX = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
1862:     int shiftY = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
1863: 
1864:     iconRect.x += shiftX;
1865:     iconRect.y += shiftY;
1866: 
1867:     textRect.x += shiftX;
1868:     textRect.y += shiftY;
1869:   }
1870: 
1871:   /**
1872:    * This method paints the icon.
1873:    *
1874:    * @param g The Graphics object to paint.
1875:    * @param tabPlacement The JTabbedPane's tab placement.
1876:    * @param tabIndex The tab index to paint.
1877:    * @param icon The icon to paint.
1878:    * @param iconRect The bounds of the icon.
1879:    * @param isSelected Whether this tab is selected.
1880:    */
1881:   protected void paintIcon(Graphics g, int tabPlacement, int tabIndex,
1882:                            Icon icon, Rectangle iconRect, boolean isSelected)
1883:   {
1884:     if (icon != null)
1885:       icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
1886:   }
1887: 
1888:   /**
1889:    * This method paints the text for the given tab.
1890:    *
1891:    * @param g The Graphics object to paint with.
1892:    * @param tabPlacement The JTabbedPane's tab placement.
1893:    * @param font The font to paint with.
1894:    * @param metrics The fontmetrics of the given font.
1895:    * @param tabIndex The tab index.
1896:    * @param title The string to paint.
1897:    * @param textRect The bounds of the string.
1898:    * @param isSelected Whether this tab is selected.
1899:    */
1900:   protected void paintText(Graphics g, int tabPlacement, Font font,
1901:                            FontMetrics metrics, int tabIndex, String title,
1902:                            Rectangle textRect, boolean isSelected)
1903:   {
1904:     View textView = getTextViewForTab(tabIndex);
1905:     if (textView != null)
1906:       {
1907:         textView.paint(g, textRect);
1908:         return;
1909:       }
1910: 
1911:     Color fg = tabPane.getForegroundAt(tabIndex);
1912:     if (fg == null)
1913:       fg = tabPane.getForeground();
1914:     Color bg = tabPane.getBackgroundAt(tabIndex);
1915:     if (bg == null)
1916:       bg = tabPane.getBackground();
1917: 
1918:     Color saved_color = g.getColor();
1919:     Font f = g.getFont();
1920:     g.setFont(font);
1921: 
1922:     if (tabPane.isEnabledAt(tabIndex))
1923:       {
1924:         g.setColor(fg);
1925: 
1926:         int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1927: 
1928:         if (mnemIndex != -1)
1929:           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1930:                                                        textRect.x,
1931:                                                        textRect.y
1932:                                                        + metrics.getAscent());
1933:         else
1934:           g.drawString(title, textRect.x, textRect.y + metrics.getAscent());
1935:       }
1936:     else
1937:       {
1938:         g.setColor(bg.brighter());
1939: 
1940:         int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1941: 
1942:         if (mnemIndex != -1)
1943:           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1944:                                                        textRect.x, textRect.y);
1945:         else
1946:           g.drawString(title, textRect.x, textRect.y);
1947: 
1948:         g.setColor(bg.darker());
1949:         if (mnemIndex != -1)
1950:           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1951:                                                        textRect.x + 1,
1952:                                                        textRect.y + 1);
1953:         else
1954:           g.drawString(title, textRect.x + 1, textRect.y + 1);
1955:       }
1956: 
1957:     g.setColor(saved_color);
1958:     g.setFont(f);
1959:   }
1960: 
1961:   /**
1962:    * This method returns how much the label for the tab should shift in the X
1963:    * direction.
1964:    *
1965:    * @param tabPlacement The JTabbedPane's tab placement.
1966:    * @param tabIndex The tab index being painted.
1967:    * @param isSelected Whether this tab is selected.
1968:    *
1969:    * @return The amount the label should shift by in the X direction.
1970:    */
1971:   protected int getTabLabelShiftX(int tabPlacement, int tabIndex,
1972:                                   boolean isSelected)
1973:   {
1974:     // No reason to shift.
1975:     return 0;
1976:   }
1977: 
1978:   /**
1979:    * This method returns how much the label for the tab should shift in the Y
1980:    * direction.
1981:    *
1982:    * @param tabPlacement The JTabbedPane's tab placement.
1983:    * @param tabIndex The tab index being painted.
1984:    * @param isSelected Whether this tab is selected.
1985:    *
1986:    * @return The amount the label should shift by in the Y direction.
1987:    */
1988:   protected int getTabLabelShiftY(int tabPlacement, int tabIndex,
1989:                                   boolean isSelected)
1990:   {
1991:     // No reason to shift.
1992:     return 0;
1993:   }
1994: 
1995:   /**
1996:    * This method paints the focus rectangle around the selected tab.
1997:    *
1998:    * @param g The Graphics object to paint with.
1999:    * @param tabPlacement The JTabbedPane's tab placement.
2000:    * @param rects The array of rectangles keeping track of size and position.
2001:    * @param tabIndex The tab index.
2002:    * @param iconRect The icon bounds.
2003:    * @param textRect The text bounds.
2004:    * @param isSelected Whether this tab is selected.
2005:    */
2006:   protected void paintFocusIndicator(Graphics g, int tabPlacement,
2007:                                      Rectangle[] rects, int tabIndex,
2008:                                      Rectangle iconRect, Rectangle textRect,
2009:                                      boolean isSelected)
2010:   {
2011:     Color saved = g.getColor();
2012:     calcRect = iconRect.union(textRect);
2013: 
2014:     g.setColor(focus);
2015: 
2016:     g.drawRect(calcRect.x, calcRect.y, calcRect.width, calcRect.height);
2017: 
2018:     g.setColor(saved);
2019:   }
2020: 
2021:   /**
2022:    * This method paints the border for an individual tab.
2023:    *
2024:    * @param g The Graphics object to paint with.
2025:    * @param tabPlacement The JTabbedPane's tab placement.
2026:    * @param tabIndex The tab index.
2027:    * @param x The x position of the tab.
2028:    * @param y The y position of the tab.
2029:    * @param w The width of the tab.
2030:    * @param h The height of the tab.
2031:    * @param isSelected Whether the tab is selected.
2032:    */
2033:   protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex,
2034:                                 int x, int y, int w, int h, boolean isSelected)
2035:   {
2036:     Color saved = g.getColor();
2037: 
2038:     if (! isSelected || tabPlacement != SwingConstants.TOP)
2039:       {
2040:         g.setColor(shadow);
2041:         g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1);
2042:         g.setColor(darkShadow);
2043:         g.drawLine(x, y + h, x + w, y + h);
2044:       }
2045: 
2046:     if (! isSelected || tabPlacement != SwingConstants.LEFT)
2047:       {
2048:         g.setColor(darkShadow);
2049:         g.drawLine(x + w, y, x + w, y + h);
2050:         g.setColor(shadow);
2051:         g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
2052:       }
2053: 
2054:     if (! isSelected || tabPlacement != SwingConstants.RIGHT)
2055:       {
2056:         g.setColor(lightHighlight);
2057:         g.drawLine(x, y, x, y + h);
2058:       }
2059: 
2060:     if (! isSelected || tabPlacement != SwingConstants.BOTTOM)
2061:       {
2062:         g.setColor(lightHighlight);
2063:         g.drawLine(x, y, x + w, y);
2064:       }
2065: 
2066:     g.setColor(saved);
2067:   }
2068: 
2069:   /**
2070:    * This method paints the background for an individual tab.
2071:    *
2072:    * @param g The Graphics object to paint with.
2073:    * @param tabPlacement The JTabbedPane's tab placement.
2074:    * @param tabIndex The tab index.
2075:    * @param x The x position of the tab.
2076:    * @param y The y position of the tab.
2077:    * @param w The width of the tab.
2078:    * @param h The height of the tab.
2079:    * @param isSelected Whether the tab is selected.
2080:    */
2081:   protected void paintTabBackground(Graphics g, int tabPlacement,
2082:                                     int tabIndex, int x, int y, int w, int h,
2083:                                     boolean isSelected)
2084:   {
2085:     Color saved = g.getColor();
2086:     if (isSelected)
2087:       g.setColor(Color.LIGHT_GRAY);
2088:     else
2089:       {
2090:         Color bg = tabPane.getBackgroundAt(tabIndex);
2091:         if (bg == null)
2092:           bg = Color.GRAY;
2093:         g.setColor(bg);
2094:       }
2095: 
2096:     g.fillRect(x, y, w, h);
2097: 
2098:     g.setColor(saved);
2099:   }
2100: 
2101:   /**
2102:    * This method paints the border around the content area.
2103:    *
2104:    * @param g The Graphics object to paint with.
2105:    * @param tabPlacement The JTabbedPane's tab placement.
2106:    * @param selectedIndex The index of the selected tab.
2107:    */
2108:   protected void paintContentBorder(Graphics g, int tabPlacement,
2109:                                     int selectedIndex)
2110:   {
2111:     int x = contentRect.x;
2112:     int y = contentRect.y;
2113:     int w = contentRect.width;
2114:     int h = contentRect.height;
2115:     paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2116:     paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2117:     paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2118:     paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2119:   }
2120: 
2121:   /**
2122:    * This method paints the top edge of the content border.
2123:    *
2124:    * @param g The Graphics object to paint with.
2125:    * @param tabPlacement The JTabbedPane's tab placement.
2126:    * @param selectedIndex The selected tab index.
2127:    * @param x The x coordinate for the content area.
2128:    * @param y The y coordinate for the content area.
2129:    * @param w The width of the content area.
2130:    * @param h The height of the content area.
2131:    */
2132:   protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
2133:                                            int selectedIndex, int x, int y,
2134:                                            int w, int h)
2135:   {
2136:     Color saved = g.getColor();
2137:     g.setColor(lightHighlight);
2138: 
2139:     int startgap = rects[selectedIndex].x;
2140:     int endgap = rects[selectedIndex].x + rects[selectedIndex].width;
2141: 
2142:     int diff = 0;
2143: 
2144:     if (tabPlacement == SwingConstants.TOP)
2145:       {
2146:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2147:           {
2148:             Point p = findPointForIndex(currentScrollLocation);
2149:             diff = p.x;
2150:           }
2151: 
2152:         g.drawLine(x, y, startgap - diff, y);
2153:         g.drawLine(endgap - diff, y, x + w, y);
2154:       }
2155:     else
2156:       g.drawLine(x, y, x + w, y);
2157: 
2158:     g.setColor(saved);
2159:   }
2160: 
2161:   /**
2162:    * This method paints the left edge of the content border.
2163:    *
2164:    * @param g The Graphics object to paint with.
2165:    * @param tabPlacement The JTabbedPane's tab placement.
2166:    * @param selectedIndex The selected tab index.
2167:    * @param x The x coordinate for the content area.
2168:    * @param y The y coordinate for the content area.
2169:    * @param w The width of the content area.
2170:    * @param h The height of the content area.
2171:    */
2172:   protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
2173:                                             int selectedIndex, int x, int y,
2174:                                             int w, int h)
2175:   {
2176:     Color saved = g.getColor();
2177:     g.setColor(lightHighlight);
2178: 
2179:     int startgap = rects[selectedIndex].y;
2180:     int endgap = rects[selectedIndex].y + rects[selectedIndex].height;
2181: 
2182:     int diff = 0;
2183: 
2184:     if (tabPlacement == SwingConstants.LEFT)
2185:       {
2186:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2187:           {
2188:             Point p = findPointForIndex(currentScrollLocation);
2189:             diff = p.y;
2190:           }
2191: 
2192:         g.drawLine(x, y, x, startgap - diff);
2193:         g.drawLine(x, endgap - diff, x, y + h);
2194:       }
2195:     else
2196:       g.drawLine(x, y, x, y + h);
2197: 
2198:     g.setColor(saved);
2199:   }
2200: 
2201:   /**
2202:    * This method paints the bottom edge of the content border.
2203:    *
2204:    * @param g The Graphics object to paint with.
2205:    * @param tabPlacement The JTabbedPane's tab placement.
2206:    * @param selectedIndex The selected tab index.
2207:    * @param x The x coordinate for the content area.
2208:    * @param y The y coordinate for the content area.
2209:    * @param w The width of the content area.
2210:    * @param h The height of the content area.
2211:    */
2212:   protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
2213:                                               int selectedIndex, int x, int y,
2214:                                               int w, int h)
2215:   {
2216:     Color saved = g.getColor();
2217: 
2218:     int startgap = rects[selectedIndex].x;
2219:     int endgap = rects[selectedIndex].x + rects[selectedIndex].width;
2220: 
2221:     int diff = 0;
2222: 
2223:     if (tabPlacement == SwingConstants.BOTTOM)
2224:       {
2225:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2226:           {
2227:             Point p = findPointForIndex(currentScrollLocation);
2228:             diff = p.x;
2229:           }
2230: 
2231:         g.setColor(shadow);
2232:         g.drawLine(x + 1, y + h - 1, startgap - diff, y + h - 1);
2233:         g.drawLine(endgap - diff, y + h - 1, x + w - 1, y + h - 1);
2234: 
2235:         g.setColor(darkShadow);
2236:         g.drawLine(x, y + h, startgap - diff, y + h);
2237:         g.drawLine(endgap - diff, y + h, x + w, y + h);
2238:       }
2239:     else
2240:       {
2241:         g.setColor(shadow);
2242:         g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1);
2243:         g.setColor(darkShadow);
2244:         g.drawLine(x, y + h, x + w, y + h);
2245:       }
2246: 
2247:     g.setColor(saved);
2248:   }
2249: 
2250:   /**
2251:    * This method paints the right edge of the content border.
2252:    *
2253:    * @param g The Graphics object to paint with.
2254:    * @param tabPlacement The JTabbedPane's tab placement.
2255:    * @param selectedIndex The selected tab index.
2256:    * @param x The x coordinate for the content area.
2257:    * @param y The y coordinate for the content area.
2258:    * @param w The width of the content area.
2259:    * @param h The height of the content area.
2260:    */
2261:   protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
2262:                                              int selectedIndex, int x, int y,
2263:                                              int w, int h)
2264:   {
2265:     Color saved = g.getColor();
2266:     int startgap = rects[selectedIndex].y;
2267:     int endgap = rects[selectedIndex].y + rects[selectedIndex].height;
2268: 
2269:     int diff = 0;
2270: 
2271:     if (tabPlacement == SwingConstants.RIGHT)
2272:       {
2273:         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2274:           {
2275:             Point p = findPointForIndex(currentScrollLocation);
2276:             diff = p.y;
2277:           }
2278: 
2279:         g.setColor(shadow);
2280:         g.drawLine(x + w - 1, y + 1, x + w - 1, startgap - diff);
2281:         g.drawLine(x + w - 1, endgap - diff, x + w - 1, y + h - 1);
2282: 
2283:         g.setColor(darkShadow);
2284:         g.drawLine(x + w, y, x + w, startgap - diff);
2285:         g.drawLine(x + w, endgap - diff, x + w, y + h);
2286:       }
2287:     else
2288:       {
2289:         g.setColor(shadow);
2290:         g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
2291:         g.setColor(darkShadow);
2292:         g.drawLine(x + w, y, x + w, y + h);
2293:       }
2294: 
2295:     g.setColor(saved);
2296:   }
2297: 
2298:   /**
2299:    * This method returns the tab bounds for the given index.
2300:    *
2301:    * @param pane The JTabbedPane.
2302:    * @param i The index to look for.
2303:    *
2304:    * @return The bounds of the tab with the given index.
2305:    */
2306:   public Rectangle getTabBounds(JTabbedPane pane, int i)
2307:   {
2308:     return rects[i];
2309:   }
2310: 
2311:   /**
2312:    * This method returns the number of runs.
2313:    *
2314:    * @param pane The JTabbedPane.
2315:    *
2316:    * @return The number of runs.
2317:    */
2318:   public int getTabRunCount(JTabbedPane pane)
2319:   {
2320:     return runCount;
2321:   }
2322: 
2323:   /**
2324:    * This method returns the tab index given a coordinate.
2325:    *
2326:    * @param pane The JTabbedPane.
2327:    * @param x The x coordinate.
2328:    * @param y The y coordinate.
2329:    *
2330:    * @return The tab index that the coordinate lands in.
2331:    */
2332:   public int tabForCoordinate(JTabbedPane pane, int x, int y)
2333:   {
2334:     Point p = new Point(x, y);
2335:     int tabCount = tabPane.getTabCount();
2336:     int currRun = 1;
2337:     for (int i = 0; i < runCount; i++)
2338:       {
2339:         int first = lastTabInRun(tabCount, getPreviousTabRun(currRun)) + 1;
2340:         if (first == tabCount)
2341:           first = 0;
2342:         int last = lastTabInRun(tabCount, currRun);
2343:         for (int j = first; j <= last; j++)
2344:           {
2345:             if (getTabBounds(pane, j).contains(p))
2346:               return j;
2347:           }
2348:         currRun = getNextTabRun(currRun);
2349:       }
2350:     return -1;
2351:   }
2352: 
2353:   /**
2354:    * This method returns the tab bounds in the given rectangle.
2355:    *
2356:    * @param tabIndex The index to get bounds for.
2357:    * @param dest The rectangle to store bounds in.
2358:    *
2359:    * @return The rectangle passed in.
2360:    */
2361:   protected Rectangle getTabBounds(int tabIndex, Rectangle dest)
2362:   {
2363:     dest.setBounds(getTabBounds(tabPane, tabIndex));
2364:     return dest;
2365:   }
2366: 
2367:   /**
2368:    * This method returns the component that is shown in  the content area.
2369:    *
2370:    * @return The component that is shown in the content area.
2371:    */
2372:   protected Component getVisibleComponent()
2373:   {
2374:     return tabPane.getComponentAt(tabPane.getSelectedIndex());
2375:   }
2376: 
2377:   /**
2378:    * This method sets the visible component.
2379:    *
2380:    * @param component The component to be set visible.
2381:    */
2382:   protected void setVisibleComponent(Component component)
2383:   {
2384:     component.setVisible(true);
2385:     tabPane.setSelectedComponent(component);
2386:   }
2387: 
2388:   /**
2389:    * This method assures that enough rectangles are created given the
2390:    * tabCount. The old array is copied to the  new one.
2391:    *
2392:    * @param tabCount The number of tabs.
2393:    */
2394:   protected void assureRectsCreated(int tabCount)
2395:   {
2396:     if (rects.length < tabCount)
2397:       {
2398:         Rectangle[] old = rects;
2399:         rects = new Rectangle[tabCount];
2400:         System.arraycopy(old, 0, rects, 0, old.length);
2401:         for (int i = old.length; i < rects.length; i++)
2402:           rects[i] = new Rectangle();
2403:       }
2404:   }
2405: 
2406:   /**
2407:    * This method expands the tabRuns array to give it more room. The old array
2408:    * is copied to the new one.
2409:    */
2410:   protected void expandTabRunsArray()
2411:   {
2412:     // This method adds another 10 index positions to the tabRuns array.
2413:     if (tabRuns == null)
2414:       tabRuns = new int[10];
2415:     else
2416:       {
2417:         int[] newRuns = new int[tabRuns.length + 10];
2418:         System.arraycopy(tabRuns, 0, newRuns, 0, tabRuns.length);
2419:         tabRuns = newRuns;
2420:       }
2421:   }
2422: 
2423:   /**
2424:    * This method returns which run a particular tab belongs to.
2425:    *
2426:    * @param tabCount The number of tabs.
2427:    * @param tabIndex The tab to find.
2428:    *
2429:    * @return The tabRuns index that it belongs to.
2430:    */
2431:   protected int getRunForTab(int tabCount, int tabIndex)
2432:   {
2433:     if (runCount == 1 && tabIndex < tabCount && tabIndex >= 0)
2434:       return 1;
2435:     for (int i = 0; i < runCount; i++)
2436:       {
2437:         int first = lastTabInRun(tabCount, getPreviousTabRun(i)) + 1;
2438:         if (first == tabCount)
2439:           first = 0;
2440:         int last = lastTabInRun(tabCount, i);
2441:         if (last >= tabIndex && first <= tabIndex)
2442:           return i;
2443:       }
2444:     return -1;
2445:   }
2446: 
2447:   /**
2448:    * This method returns the index of the last tab in  a run.
2449:    *
2450:    * @param tabCount The number of tabs.
2451:    * @param run The run to check.
2452:    *
2453:    * @return The last tab in the given run.
2454:    */
2455:   protected int lastTabInRun(int tabCount, int run)
2456:   {
2457:     if (tabRuns[run] == 0)
2458:       return tabCount - 1;
2459:     else
2460:       return tabRuns[run] - 1;
2461:   }
2462: 
2463:   /**
2464:    * This method returns the tab run overlay.
2465:    *
2466:    * @param tabPlacement The JTabbedPane's tab placement.
2467:    *
2468:    * @return The tab run overlay.
2469:    */
2470:   protected int getTabRunOverlay(int tabPlacement)
2471:   {
2472:     return tabRunOverlay;
2473:   }
2474: 
2475:   /**
2476:    * This method returns the tab run indent. It is used in WRAP_TAB_LAYOUT and
2477:    * makes each tab run start indented by a certain amount.
2478:    *
2479:    * @param tabPlacement The JTabbedPane's tab placement.
2480:    * @param run The run to get indent for.
2481:    *
2482:    * @return The amount a run should be indented.
2483:    */
2484:   protected int getTabRunIndent(int tabPlacement, int run)
2485:   {
2486:     return 0;
2487:   }
2488: 
2489:   /**
2490:    * This method returns whether a tab run should be padded.
2491:    *
2492:    * @param tabPlacement The JTabbedPane's tab placement.
2493:    * @param run The run to check.
2494:    *
2495:    * @return Whether the given run should be padded.
2496:    */
2497:   protected boolean shouldPadTabRun(int tabPlacement, int run)
2498:   {
2499:     return true;
2500:   }
2501: 
2502:   /**
2503:    * This method returns whether the tab runs should be rotated.
2504:    *
2505:    * @param tabPlacement The JTabbedPane's tab placement.
2506:    *
2507:    * @return Whether runs should be rotated.
2508:    */
2509:   protected boolean shouldRotateTabRuns(int tabPlacement)
2510:   {
2511:     return true;
2512:   }
2513: 
2514:   /**
2515:    * This method returns an icon for the tab. If the tab is disabled, it
2516:    * should return the disabledIcon. If it is enabled, then it should return
2517:    * the default icon.
2518:    *
2519:    * @param tabIndex The tab index to get an icon for.
2520:    *
2521:    * @return The icon for the tab index.
2522:    */
2523:   protected Icon getIconForTab(int tabIndex)
2524:   {
2525:     if (tabPane.isEnabledAt(tabIndex))
2526:       return tabPane.getIconAt(tabIndex);
2527:     else
2528:       return tabPane.getDisabledIconAt(tabIndex);
2529:   }
2530: 
2531:   /**
2532:    * This method returns a view that can paint the text for the label.
2533:    *
2534:    * @param tabIndex The tab index to get a view for.
2535:    *
2536:    * @return The view for the tab index.
2537:    */
2538:   protected View getTextViewForTab(int tabIndex)
2539:   {
2540:     return null;
2541:   }
2542: 
2543:   /**
2544:    * This method returns the tab height, including insets, for the given index
2545:    * and fontheight.
2546:    *
2547:    * @param tabPlacement The JTabbedPane's tab placement.
2548:    * @param tabIndex The index of the tab to calculate.
2549:    * @param fontHeight The font height.
2550:    *
2551:    * @return This tab's height.
2552:    */
2553:   protected int calculateTabHeight(int tabPlacement, int tabIndex,
2554:                                    int fontHeight)
2555:   {
2556:     Icon icon = getIconForTab(tabIndex);
2557:     Insets insets = getTabInsets(tabPlacement, tabIndex);
2558: 
2559:     int height = 0;
2560:     if (icon != null)
2561:       {
2562:         Rectangle vr = new Rectangle();
2563:         Rectangle ir = new Rectangle();
2564:         Rectangle tr = new Rectangle();
2565:         layoutLabel(tabPlacement, getFontMetrics(), tabIndex,
2566:                     tabPane.getTitleAt(tabIndex), icon, vr, ir, tr,
2567:                     tabIndex == tabPane.getSelectedIndex());
2568:         height = tr.union(ir).height;
2569:       }
2570:     else
2571:       height = fontHeight;
2572: 
2573:     height += insets.top + insets.bottom;
2574:     return height;
2575:   }
2576: 
2577:   /**
2578:    * This method returns the max tab height.
2579:    *
2580:    * @param tabPlacement The JTabbedPane's tab placement.
2581:    *
2582:    * @return The maximum tab height.
2583:    */
2584:   protected int calculateMaxTabHeight(int tabPlacement)
2585:   {
2586:     maxTabHeight = 0;
2587: 
2588:     FontMetrics fm = getFontMetrics();
2589:     int fontHeight = fm.getHeight();
2590: 
2591:     for (int i = 0; i < tabPane.getTabCount(); i++)
2592:       maxTabHeight = Math.max(calculateTabHeight(tabPlacement, i, fontHeight),
2593:                               maxTabHeight);
2594: 
2595:     return maxTabHeight;
2596:   }
2597: 
2598:   /**
2599:    * This method calculates the tab width, including insets, for the given tab
2600:    * index and font metrics.
2601:    *
2602:    * @param tabPlacement The JTabbedPane's tab placement.
2603:    * @param tabIndex The tab index to calculate for.
2604:    * @param metrics The font's metrics.
2605:    *
2606:    * @return The tab width for the given index.
2607:    */
2608:   protected int calculateTabWidth(int tabPlacement, int tabIndex,
2609:                                   FontMetrics metrics)
2610:   {
2611:     Icon icon = getIconForTab(tabIndex);
2612:     Insets insets = getTabInsets(tabPlacement, tabIndex);
2613: 
2614:     int width = 0;
2615:     if (icon != null)
2616:       {
2617:         Rectangle vr = new Rectangle();
2618:         Rectangle ir = new Rectangle();
2619:         Rectangle tr = new Rectangle();
2620:         layoutLabel(tabPlacement, getFontMetrics(), tabIndex,
2621:                     tabPane.getTitleAt(tabIndex), icon, vr, ir, tr,
2622:                     tabIndex == tabPane.getSelectedIndex());
2623:         width = tr.union(ir).width;
2624:       }
2625:     else
2626:       width = metrics.stringWidth(tabPane.getTitleAt(tabIndex));
2627: 
2628:     width += insets.left + insets.right;
2629:     return width;
2630:   }
2631: 
2632:   /**
2633:    * This method calculates the max tab width.
2634:    *
2635:    * @param tabPlacement The JTabbedPane's tab placement.
2636:    *
2637:    * @return The maximum tab width.
2638:    */
2639:   protected int calculateMaxTabWidth(int tabPlacement)
2640:   {
2641:     maxTabWidth = 0;
2642: 
2643:     FontMetrics fm = getFontMetrics();
2644: 
2645:     for (int i = 0; i < tabPane.getTabCount(); i++)
2646:       maxTabWidth = Math.max(calculateTabWidth(tabPlacement, i, fm),
2647:                              maxTabWidth);
2648: 
2649:     return maxTabWidth;
2650:   }
2651: 
2652:   /**
2653:    * This method calculates the tab area height, including insets, for the
2654:    * given amount of runs and tab height.
2655:    *
2656:    * @param tabPlacement The JTabbedPane's tab placement.
2657:    * @param horizRunCount The number of runs.
2658:    * @param maxTabHeight The max tab height.
2659:    *
2660:    * @return The tab area height.
2661:    */
2662:   protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount,
2663:                                        int maxTabHeight)
2664:   {
2665:     Insets insets = getTabAreaInsets(tabPlacement);
2666:     int tabAreaHeight = horizRunCount * maxTabHeight
2667:                         - (horizRunCount - 1) * tabRunOverlay;
2668: 
2669:     tabAreaHeight += insets.top + insets.bottom;
2670: 
2671:     return tabAreaHeight;
2672:   }
2673: 
2674:   /**
2675:    * This method calculates the tab area width, including insets, for the
2676:    * given amount of runs and tab width.
2677:    *
2678:    * @param tabPlacement The JTabbedPane's tab placement.
2679:    * @param vertRunCount The number of runs.
2680:    * @param maxTabWidth The max tab width.
2681:    *
2682:    * @return The tab area width.
2683:    */
2684:   protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount,
2685:                                       int maxTabWidth)
2686:   {
2687:     Insets insets = getTabAreaInsets(tabPlacement);
2688:     int tabAreaWidth = vertRunCount * maxTabWidth
2689:                        - (vertRunCount - 1) * tabRunOverlay;
2690: 
2691:     tabAreaWidth += insets.left + insets.right;
2692: 
2693:     return tabAreaWidth;
2694:   }
2695: 
2696:   /**
2697:    * This method returns the tab insets appropriately rotated.
2698:    *
2699:    * @param tabPlacement The JTabbedPane's tab placement.
2700:    * @param tabIndex The tab index.
2701:    *
2702:    * @return The tab insets for the given index.
2703:    */
2704:   protected Insets getTabInsets(int tabPlacement, int tabIndex)
2705:   {
2706:     Insets target = new Insets(0, 0, 0, 0);
2707:     rotateInsets(tabInsets, target, tabPlacement);
2708:     return target;
2709:   }
2710: 
2711:   /**
2712:    * This method returns the selected tab pad insets appropriately rotated.
2713:    *
2714:    * @param tabPlacement The JTabbedPane's tab placement.
2715:    *
2716:    * @return The selected tab pad insets.
2717:    */
2718:   protected Insets getSelectedTabPadInsets(int tabPlacement)
2719:   {
2720:     Insets target = new Insets(0, 0, 0, 0);
2721:     rotateInsets(selectedTabPadInsets, target, tabPlacement);
2722:     return target;
2723:   }
2724: 
2725:   /**
2726:    * This method returns the tab area insets appropriately rotated.
2727:    *
2728:    * @param tabPlacement The JTabbedPane's tab placement.
2729:    *
2730:    * @return The tab area insets.
2731:    */
2732:   protected Insets getTabAreaInsets(int tabPlacement)
2733:   {
2734:     Insets target = new Insets(0, 0, 0, 0);
2735:     rotateInsets(tabAreaInsets, target, tabPlacement);
2736:     return target;
2737:   }
2738: 
2739:   /**
2740:    * This method returns the content border insets appropriately rotated.
2741:    *
2742:    * @param tabPlacement The JTabbedPane's tab placement.
2743:    *
2744:    * @return The content border insets.
2745:    */
2746:   protected Insets getContentBorderInsets(int tabPlacement)
2747:   {
2748:     Insets target = new Insets(0, 0, 0, 0);
2749:     rotateInsets(contentBorderInsets, target, tabPlacement);
2750:     return target;
2751:   }
2752: 
2753:   /**
2754:    * This method returns the fontmetrics for the font of the JTabbedPane.
2755:    *
2756:    * @return The font metrics for the JTabbedPane.
2757:    */
2758:   protected FontMetrics getFontMetrics()
2759:   {
2760:     FontMetrics fm = tabPane.getFontMetrics(tabPane.getFont());
2761:     return fm;
2762:   }
2763: 
2764:   /**
2765:    * This method navigates from the selected tab into the given direction. As
2766:    * a result, a new tab will be selected (if possible).
2767:    *
2768:    * @param direction The direction to navigate in.
2769:    */
2770:   protected void navigateSelectedTab(int direction)
2771:   {
2772:     int tabPlacement = tabPane.getTabPlacement();
2773:     if (tabPlacement == SwingConstants.TOP
2774:         || tabPlacement == SwingConstants.BOTTOM)
2775:       {
2776:         if (direction == SwingConstants.WEST)
2777:           selectPreviousTabInRun(tabPane.getSelectedIndex());
2778:         else if (direction == SwingConstants.EAST)
2779:           selectNextTabInRun(tabPane.getSelectedIndex());
2780: 
2781:         else
2782:           {
2783:             int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(),
2784:                                          tabPane.getSelectedIndex(),
2785:                                          (tabPlacement == SwingConstants.RIGHT)
2786:                                          ? true : false);
2787:             selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(),
2788:                                  offset);
2789:           }
2790:       }
2791:     if (tabPlacement == SwingConstants.LEFT
2792:         || tabPlacement == SwingConstants.RIGHT)
2793:       {
2794:         if (direction == SwingConstants.NORTH)
2795:           selectPreviousTabInRun(tabPane.getSelectedIndex());
2796:         else if (direction == SwingConstants.SOUTH)
2797:           selectNextTabInRun(tabPane.getSelectedIndex());
2798:         else
2799:           {
2800:             int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(),
2801:                                          tabPane.getSelectedIndex(),
2802:                                          (tabPlacement == SwingConstants.RIGHT)
2803:                                          ? true : false);
2804:             selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(),
2805:                                  offset);
2806:           }
2807:       }
2808:   }
2809: 
2810:   /**
2811:    * This method selects the next tab in the run.
2812:    *
2813:    * @param current The current selected index.
2814:    */
2815:   protected void selectNextTabInRun(int current)
2816:   {
2817:     tabPane.setSelectedIndex(getNextTabIndexInRun(tabPane.getTabCount(),
2818:                                                   current));
2819:   }
2820: 
2821:   /**
2822:    * This method selects the previous tab in the run.
2823:    *
2824:    * @param current The current selected index.
2825:    */
2826:   protected void selectPreviousTabInRun(int current)
2827:   {
2828:     tabPane.setSelectedIndex(getPreviousTabIndexInRun(tabPane.getTabCount(),
2829:                                                       current));
2830:   }
2831: 
2832:   /**
2833:    * This method selects the next tab (regardless of runs).
2834:    *
2835:    * @param current The current selected index.
2836:    */
2837:   protected void selectNextTab(int current)
2838:   {
2839:     tabPane.setSelectedIndex(getNextTabIndex(current));
2840:   }
2841: 
2842:   /**
2843:    * This method selects the previous tab (regardless of runs).
2844:    *
2845:    * @param current The current selected index.
2846:    */
2847:   protected void selectPreviousTab(int current)
2848:   {
2849:     tabPane.setSelectedIndex(getPreviousTabIndex(current));
2850:   }
2851: 
2852:   /**
2853:    * This method selects the correct tab given an offset from the current tab
2854:    * index. If the tab placement is TOP or BOTTOM, the offset will be in the
2855:    * y direction, otherwise, it will be in the x direction. A new coordinate
2856:    * will be found by adding the offset to the current location of the tab.
2857:    * The tab that the new location will be selected.
2858:    *
2859:    * @param tabPlacement The JTabbedPane's tab placement.
2860:    * @param tabIndex The tab to start from.
2861:    * @param offset The coordinate offset.
2862:    */
2863:   protected void selectAdjacentRunTab(int tabPlacement, int tabIndex,
2864:                                       int offset)
2865:   {
2866:     int x = rects[tabIndex].x + rects[tabIndex].width / 2;
2867:     int y = rects[tabIndex].y + rects[tabIndex].height / 2;
2868: 
2869:     switch (tabPlacement)
2870:     {
2871:     case SwingConstants.TOP:
2872:     case SwingConstants.BOTTOM:
2873:       y += offset;
2874:       break;
2875:     case SwingConstants.RIGHT:
2876:     case SwingConstants.LEFT:
2877:       x += offset;
2878:       break;
2879:     }
2880: 
2881:     int index = tabForCoordinate(tabPane, x, y);
2882:     if (index != -1)
2883:       tabPane.setSelectedIndex(index);
2884:   }
2885: 
2886:   // This method is called when you press up/down to cycle through tab runs.
2887:   // it returns the distance (between the two runs' x/y position.
2888:   // where one run is the current selected run and the other run is the run in the
2889:   // direction of the scroll (dictated by the forward flag)
2890:   // the offset is an absolute value of the difference
2891: 
2892:   /**
2893:    * This method calculates the offset distance for use in
2894:    * selectAdjacentRunTab. The offset returned will be a difference in the y
2895:    * coordinate between the run in  the desired direction and the current run
2896:    * (for tabPlacement in TOP or BOTTOM). Use x coordinate for LEFT and
2897:    * RIGHT.
2898:    *
2899:    * @param tabPlacement The JTabbedPane's tab placement.
2900:    * @param tabCount The number of tabs.
2901:    * @param tabIndex The starting index.
2902:    * @param forward If forward, the run in the desired direction will be the
2903:    *        next run.
2904:    *
2905:    * @return The offset between the two runs.
2906:    */
2907:   protected int getTabRunOffset(int tabPlacement, int tabCount, int tabIndex,
2908:                                 boolean forward)
2909:   {
2910:     int currRun = getRunForTab(tabCount, tabIndex);
2911:     int offset;
2912:     int nextRun = (forward) ? getNextTabRun(currRun) : getPreviousTabRun(currRun);
2913:     if (tabPlacement == SwingConstants.TOP
2914:         || tabPlacement == SwingConstants.BOTTOM)
2915:       offset = rects[lastTabInRun(tabCount, nextRun)].y
2916:                - rects[lastTabInRun(tabCount, currRun)].y;
2917:     else
2918:       offset = rects[lastTabInRun(tabCount, nextRun)].x
2919:                - rects[lastTabInRun(tabCount, currRun)].x;
2920:     return offset;
2921:   }
2922: 
2923:   /**
2924:    * This method returns the previous tab index.
2925:    *
2926:    * @param base The index to start from.
2927:    *
2928:    * @return The previous tab index.
2929:    */
2930:   protected int getPreviousTabIndex(int base)
2931:   {
2932:     base--;
2933:     if (base < 0)
2934:       return tabPane.getTabCount() - 1;
2935:     return base;
2936:   }
2937: 
2938:   /**
2939:    * This method returns the next tab index.
2940:    *
2941:    * @param base The index to start from.
2942:    *
2943:    * @return The next tab index.
2944:    */
2945:   protected int getNextTabIndex(int base)
2946:   {
2947:     base++;
2948:     if (base == tabPane.getTabCount())
2949:       return 0;
2950:     return base;
2951:   }
2952: 
2953:   /**
2954:    * This method returns the next tab index in the run. If the next index is
2955:    * out of this run, it will return the starting tab index for the run.
2956:    *
2957:    * @param tabCount The number of tabs.
2958:    * @param base The index to start from.
2959:    *
2960:    * @return The next tab index in the run.
2961:    */
2962:   protected int getNextTabIndexInRun(int tabCount, int base)
2963:   {
2964:     int index = getNextTabIndex(base);
2965:     int run = getRunForTab(tabCount, base);
2966:     if (index == lastTabInRun(tabCount, run) + 1)
2967:       index = lastTabInRun(tabCount, getPreviousTabRun(run)) + 1;
2968:     return getNextTabIndex(base);
2969:   }
2970: 
2971:   /**
2972:    * This method returns the previous tab index in the run. If the previous
2973:    * index is out of this run, it will return the last index for the run.
2974:    *
2975:    * @param tabCount The number of tabs.
2976:    * @param base The index to start from.
2977:    *
2978:    * @return The previous tab index in the run.
2979:    */
2980:   protected int getPreviousTabIndexInRun(int tabCount, int base)
2981:   {
2982:     int index = getPreviousTabIndex(base);
2983:     int run = getRunForTab(tabCount, base);
2984:     if (index == lastTabInRun(tabCount, getPreviousTabRun(run)))
2985:       index = lastTabInRun(tabCount, run);
2986:     return getPreviousTabIndex(base);
2987:   }
2988: 
2989:   /**
2990:    * This method returns the index of the previous run.
2991:    *
2992:    * @param baseRun The run to start from.
2993:    *
2994:    * @return The index of the previous run.
2995:    */
2996:   protected int getPreviousTabRun(int baseRun)
2997:   {
2998:     if (getTabRunCount(tabPane) == 1)
2999:       return 1;
3000: 
3001:     int prevRun = --baseRun;
3002:     if (prevRun < 0)
3003:       prevRun = getTabRunCount(tabPane) - 1;
3004:     return prevRun;
3005:   }
3006: 
3007:   /**
3008:    * This method returns the index of the next run.
3009:    *
3010:    * @param baseRun The run to start from.
3011:    *
3012:    * @return The index of the next run.
3013:    */
3014:   protected int getNextTabRun(int baseRun)
3015:   {
3016:     if (getTabRunCount(tabPane) == 1)
3017:       return 1;
3018: 
3019:     int nextRun = ++baseRun;
3020:     if (nextRun == getTabRunCount(tabPane))
3021:       nextRun = 0;
3022:     return nextRun;
3023:   }
3024: 
3025:   /**
3026:    * This method rotates the insets given a direction to rotate them in.
3027:    * Target placement should be one of TOP, LEFT, BOTTOM, RIGHT. The  rotated
3028:    * insets will be stored in targetInsets. Passing in TOP as  the direction
3029:    * does nothing. Passing in LEFT switches top and left, right and bottom.
3030:    * Passing in BOTTOM switches top and bottom. Passing in RIGHT switches top
3031:    * for left, left for bottom, bottom for right, and right for top.
3032:    *
3033:    * @param topInsets The reference insets.
3034:    * @param targetInsets An Insets object to store the new insets.
3035:    * @param targetPlacement The rotation direction.
3036:    */
3037:   protected static void rotateInsets(Insets topInsets, Insets targetInsets,
3038:                                      int targetPlacement)
3039:   {
3040:     // Sun's version will happily throw an NPE if params are null,
3041:     // so I won't check it either.
3042:     switch (targetPlacement)
3043:     {
3044:     case SwingConstants.TOP:
3045:       targetInsets.top = topInsets.top;
3046:       targetInsets.left = topInsets.left;
3047:       targetInsets.right = topInsets.right;
3048:       targetInsets.bottom = topInsets.bottom;
3049:       break;
3050:     case SwingConstants.LEFT:
3051:       targetInsets.left = topInsets.top;
3052:       targetInsets.top = topInsets.left;
3053:       targetInsets.right = topInsets.bottom;
3054:       targetInsets.bottom = topInsets.right;
3055:       break;
3056:     case SwingConstants.BOTTOM:
3057:       targetInsets.top = topInsets.bottom;
3058:       targetInsets.bottom = topInsets.top;
3059:       targetInsets.left = topInsets.left;
3060:       targetInsets.right = topInsets.right;
3061:       break;
3062:     case SwingConstants.RIGHT:
3063:       targetInsets.top = topInsets.left;
3064:       targetInsets.left = topInsets.bottom;
3065:       targetInsets.bottom = topInsets.right;
3066:       targetInsets.right = topInsets.top;
3067:       break;
3068:     }
3069:   }
3070: }