OSDN Git Service

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