Source for javax.swing.plaf.basic.BasicTabbedPaneUI

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