001    /* BasicTabbedPaneUI.java --
002       Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.basic;
040    
041    import java.awt.Color;
042    import java.awt.Component;
043    import java.awt.Container;
044    import java.awt.Dimension;
045    import java.awt.Font;
046    import java.awt.FontMetrics;
047    import java.awt.Graphics;
048    import java.awt.Insets;
049    import java.awt.LayoutManager;
050    import java.awt.Point;
051    import java.awt.Rectangle;
052    import java.awt.event.ActionEvent;
053    import java.awt.event.FocusAdapter;
054    import java.awt.event.FocusEvent;
055    import java.awt.event.FocusListener;
056    import java.awt.event.MouseAdapter;
057    import java.awt.event.MouseEvent;
058    import java.awt.event.MouseListener;
059    import java.beans.PropertyChangeEvent;
060    import java.beans.PropertyChangeListener;
061    
062    import javax.swing.AbstractAction;
063    import javax.swing.ActionMap;
064    import javax.swing.Icon;
065    import javax.swing.InputMap;
066    import javax.swing.JComponent;
067    import javax.swing.JPanel;
068    import javax.swing.JTabbedPane;
069    import javax.swing.JViewport;
070    import javax.swing.KeyStroke;
071    import javax.swing.LookAndFeel;
072    import javax.swing.SwingConstants;
073    import javax.swing.SwingUtilities;
074    import javax.swing.UIManager;
075    import javax.swing.event.ChangeEvent;
076    import javax.swing.event.ChangeListener;
077    import javax.swing.plaf.ActionMapUIResource;
078    import javax.swing.plaf.ComponentUI;
079    import javax.swing.plaf.TabbedPaneUI;
080    import javax.swing.plaf.UIResource;
081    import javax.swing.text.View;
082    
083    /**
084     * This is the Basic Look and Feel's UI delegate for JTabbedPane.
085     * 
086     * @author Lillian Angel (langel@redhat.com)
087     * @author Kim Ho (kho@redhat.com)
088     * @author Roman Kennke (kennke@aicas.com)
089     * @author Robert Schuster (robertschuster@fsfe.org)
090     */
091    public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants
092    {
093      
094      static class NavigateAction extends AbstractAction
095      {
096        int direction;
097        
098        NavigateAction(String name, int dir)
099        {
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    }