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