OSDN Git Service

Imported GNU Classpath 0.20
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / plaf / basic / BasicTabbedPaneUI.java
1 /* BasicTabbedPaneUI.java --
2    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.plaf.basic;
40
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.Container;
44 import java.awt.Dimension;
45 import java.awt.Font;
46 import java.awt.FontMetrics;
47 import java.awt.Graphics;
48 import java.awt.Insets;
49 import java.awt.LayoutManager;
50 import java.awt.Point;
51 import java.awt.Rectangle;
52 import java.awt.event.FocusAdapter;
53 import java.awt.event.FocusEvent;
54 import java.awt.event.FocusListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseListener;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
60
61 import javax.swing.Icon;
62 import javax.swing.JComponent;
63 import javax.swing.JPanel;
64 import javax.swing.JTabbedPane;
65 import javax.swing.JViewport;
66 import javax.swing.KeyStroke;
67 import javax.swing.LookAndFeel;
68 import javax.swing.SwingConstants;
69 import javax.swing.SwingUtilities;
70 import javax.swing.UIManager;
71 import javax.swing.event.ChangeEvent;
72 import javax.swing.event.ChangeListener;
73 import javax.swing.plaf.ComponentUI;
74 import javax.swing.plaf.PanelUI;
75 import javax.swing.plaf.TabbedPaneUI;
76 import javax.swing.plaf.UIResource;
77 import javax.swing.text.View;
78
79 /**
80  * This is the Basic Look and Feel's UI delegate for JTabbedPane.
81  */
82 public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants
83 {
84   /**
85    * A helper class that handles focus.
86    *
87    * @specnote Apparently this class was intended to be protected,
88    *           but was made public by a compiler bug and is now
89    *           public for compatibility.
90    */
91   public class FocusHandler extends FocusAdapter
92   {
93     /**
94      * This method is called when the component gains focus.
95      *
96      * @param e The FocusEvent.
97      */
98     public void focusGained(FocusEvent e)
99     {
100       // FIXME: Implement.
101     }
102
103     /**
104      * This method is called when the component loses focus.
105      *
106      * @param e The FocusEvent.
107      */
108     public void focusLost(FocusEvent e)
109     {
110       // FIXME: Implement.
111     }
112   }
113
114   /**
115    * A helper class for determining if mouse presses occur inside tabs and
116    * sets the index appropriately. In SCROLL_TAB_MODE, this class also
117    * handles the mouse clicks on the scrolling buttons.
118    *
119    * @specnote Apparently this class was intended to be protected,
120    *           but was made public by a compiler bug and is now
121    *           public for compatibility.
122    */
123   public class MouseHandler extends MouseAdapter
124   {
125     /**
126      * This method is called when the mouse is pressed. The index cannot
127      * change to a tab that is  not enabled.
128      *
129      * @param e The MouseEvent.
130      */
131     public void mousePressed(MouseEvent e)
132     {
133       int x = e.getX();
134       int y = e.getY();
135       int tabCount = tabPane.getTabCount();
136
137       if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
138         {
139           if (e.getSource() == incrButton)
140             {
141               if (++currentScrollLocation >= tabCount)
142                 currentScrollLocation = tabCount - 1;
143               
144               int width = 0;
145               for (int i = currentScrollLocation - 1; i < tabCount; i++)
146                 width += rects[i].width;
147               if (width < viewport.getWidth())
148                 // FIXME: Still getting mouse events after the button is disabled.
149                 //      incrButton.setEnabled(false);
150                 currentScrollLocation--;
151               else if (! decrButton.isEnabled())
152                 decrButton.setEnabled(true);
153               tabPane.revalidate();
154               tabPane.repaint();
155               return;
156             }
157           else if (e.getSource() == decrButton)
158             {
159               if (--currentScrollLocation < 0)
160                 currentScrollLocation = 0;
161               if (currentScrollLocation == 0)
162                 decrButton.setEnabled(false);
163               else if (! incrButton.isEnabled())
164                 incrButton.setEnabled(true);
165               tabPane.revalidate();
166               tabPane.repaint();
167               return;
168             }
169         }
170
171       int index = tabForCoordinate(tabPane, x, y);
172
173       // We need to check since there are areas where tabs cannot be
174       // e.g. in the inset area.
175       if (index != -1 && tabPane.isEnabledAt(index))
176         tabPane.setSelectedIndex(index);
177       tabPane.revalidate();
178       tabPane.repaint();
179     }
180   }
181
182   /**
183    * This class handles PropertyChangeEvents fired from the JTabbedPane.
184    *
185    * @specnote Apparently this class was intended to be protected,
186    *           but was made public by a compiler bug and is now
187    *           public for compatibility.
188    */
189   public class PropertyChangeHandler implements PropertyChangeListener
190   {
191     /**
192      * This method is called whenever one of the properties of the JTabbedPane
193      * changes.
194      *
195      * @param e The PropertyChangeEvent.
196      */
197     public void propertyChange(PropertyChangeEvent e)
198     {
199       if (e.getPropertyName().equals("tabLayoutPolicy"))
200         {
201           layoutManager = createLayoutManager();
202           
203           tabPane.setLayout(layoutManager);
204         }
205       else if (e.getPropertyName().equals("tabPlacement")
206           && tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
207         {
208           incrButton = createIncreaseButton();
209           decrButton = createDecreaseButton();
210         }
211       tabPane.revalidate();
212       tabPane.repaint();
213     }
214   }
215
216   /**
217    * A LayoutManager responsible for placing all the tabs and the visible
218    * component inside the JTabbedPane. This class is only used for
219    * WRAP_TAB_LAYOUT.
220    *
221    * @specnote Apparently this class was intended to be protected,
222    *           but was made public by a compiler bug and is now
223    *           public for compatibility.
224    */
225   public class TabbedPaneLayout implements LayoutManager
226   {
227     /**
228      * This method is called when a component is added to the JTabbedPane.
229      *
230      * @param name The name of the component.
231      * @param comp The component being added.
232      */
233     public void addLayoutComponent(String name, Component comp)
234     {
235       // Do nothing.
236     }
237
238     /**
239      * This method is called when the rectangles need to be calculated. It
240      * also fixes the size of the visible component.
241      */
242     public void calculateLayoutInfo()
243     {
244       assureRectsCreated(tabPane.getTabCount());
245       contentRect = SwingUtilities.calculateInnerArea(tabPane, contentRect);
246
247       calculateTabRects(tabPane.getTabPlacement(), tabPane.getTabCount());
248
249       if (tabPane.getSelectedIndex() != -1)
250         {
251           Component visible = getVisibleComponent();
252           Insets insets = getContentBorderInsets(tabPane.getTabPlacement());
253           if (visible != null)
254             visible.setBounds(contentRect.x + insets.left,
255                               contentRect.y + insets.top,
256                               contentRect.width - insets.left - insets.right,
257                               contentRect.height - insets.top - insets.bottom);
258         }
259     }
260
261     /**
262      * This method calculates the size of the the JTabbedPane.
263      *
264      * @param minimum Whether the JTabbedPane will try to be as small as it
265      *        can.
266      *
267      * @return The desired size of the JTabbedPane.
268      */
269     protected Dimension calculateSize(boolean minimum)
270     {
271       int tabPlacement = tabPane.getTabPlacement();
272       int width = 0;
273       int height = 0;
274
275       int componentHeight = 0;
276       int componentWidth = 0;
277       Component c;
278       Dimension dims;
279       for (int i = 0; i < tabPane.getTabCount(); i++)
280         {
281           c = tabPane.getComponentAt(i);
282           if (c == null)
283             continue;
284           calcRect = c.getBounds();
285           dims = c.getPreferredSize();
286           if (dims != null)
287             {
288               componentHeight = Math.max(componentHeight, dims.height);
289               componentWidth = Math.max(componentWidth, dims.width);
290             }
291         }
292       if (tabPlacement == SwingConstants.TOP
293           || tabPlacement == SwingConstants.BOTTOM)
294         {
295           int min = calculateMaxTabWidth(tabPlacement);
296           width = Math.max(min, componentWidth);
297           
298           int tabAreaHeight = preferredTabAreaHeight(tabPlacement, width);
299           height = tabAreaHeight + componentHeight;
300         }
301       else
302         {
303           int min = calculateMaxTabHeight(tabPlacement);
304           height = Math.max(min, componentHeight);
305           
306           int tabAreaWidth = preferredTabAreaWidth(tabPlacement, height);
307           width = tabAreaWidth + componentWidth;
308         }
309
310       return new Dimension(width, height);
311     }
312
313     // if tab placement is LEFT OR RIGHT, they share width.
314     // if tab placement is TOP OR BOTTOM, they share height
315     // PRE STEP: finds the default sizes for the labels as well as their locations.
316     // AND where they will be placed within the run system.
317     // 1. calls normalizeTab Runs.
318     // 2. calls rotate tab runs.
319     // 3. pads the tab runs.
320     // 4. pads the selected tab.
321
322     /**
323      * This method is called to calculate the tab rectangles.  This method
324      * will calculate the size and position of all  rectangles (taking into
325      * account which ones should be in which tab run). It will pad them and
326      * normalize them  as necessary.
327      *
328      * @param tabPlacement The JTabbedPane's tab placement.
329      * @param tabCount The run the current selection is in.
330      */
331     protected void calculateTabRects(int tabPlacement, int tabCount)
332     {
333       if (tabCount == 0)
334         return;
335
336       FontMetrics fm = getFontMetrics();
337       SwingUtilities.calculateInnerArea(tabPane, calcRect);
338       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
339       Insets insets = tabPane.getInsets();
340       int max = 0;
341       int runs = 0;
342       int start = getTabRunIndent(tabPlacement, 1);
343       if (tabPlacement == SwingConstants.TOP
344           || tabPlacement == SwingConstants.BOTTOM)
345         {
346           int maxHeight = calculateMaxTabHeight(tabPlacement);
347
348           calcRect.width -= tabAreaInsets.left + tabAreaInsets.right;
349           max = calcRect.width + tabAreaInsets.left + insets.left;
350           start += tabAreaInsets.left + insets.left;
351           int width = 0;
352           int runWidth = start;
353
354           for (int i = 0; i < tabCount; i++)
355             {
356               width = calculateTabWidth(tabPlacement, i, fm);
357               if (runWidth + width > max)
358                 {
359                   runWidth = tabAreaInsets.left + insets.left
360                   + getTabRunIndent(tabPlacement, ++runs);
361                   rects[i] = new Rectangle(runWidth,
362                                            insets.top + tabAreaInsets.top,
363                                            width, maxHeight);
364                   runWidth += width;
365                   if (runs > tabRuns.length - 1)
366                     expandTabRunsArray();
367                   tabRuns[runs] = i;
368                 }
369               else
370                 {
371                   rects[i] = new Rectangle(runWidth,
372                                            insets.top + tabAreaInsets.top,
373                                            width, maxHeight);
374                   runWidth += width;
375                 }
376             }
377           runs++;
378           tabAreaRect.width = tabPane.getWidth() - insets.left - insets.right;
379           tabAreaRect.height = runs * maxTabHeight
380           - (runs - 1) * tabRunOverlay
381           + tabAreaInsets.top + tabAreaInsets.bottom;
382           contentRect.width = tabAreaRect.width;
383           contentRect.height = tabPane.getHeight() - insets.top
384           - insets.bottom - tabAreaRect.height;
385           contentRect.x = insets.left;
386           tabAreaRect.x = insets.left;
387           if (tabPlacement == SwingConstants.BOTTOM)
388             {
389               contentRect.y = insets.top;
390               tabAreaRect.y = contentRect.y + contentRect.height;
391             }
392           else
393             {
394               tabAreaRect.y = insets.top;
395               contentRect.y = tabAreaRect.y + tabAreaRect.height;
396             }
397         }
398       else
399         {
400           int maxWidth = calculateMaxTabWidth(tabPlacement);
401           calcRect.height -= tabAreaInsets.top + tabAreaInsets.bottom;
402           max = calcRect.height + tabAreaInsets.top + insets.top;
403
404           int height = 0;
405           start += tabAreaInsets.top + insets.top;
406           int runHeight = start;
407
408           int fontHeight = fm.getHeight();
409
410           for (int i = 0; i < tabCount; i++)
411             {
412               height = calculateTabHeight(tabPlacement, i, fontHeight);
413               if (runHeight + height > max)
414                 {
415                   runHeight = tabAreaInsets.top + insets.top
416                   + getTabRunIndent(tabPlacement, ++runs);
417                   rects[i] = new Rectangle(insets.left + tabAreaInsets.left,
418                                            runHeight, maxWidth, height);
419                   runHeight += height;
420                   if (runs > tabRuns.length - 1)
421                     expandTabRunsArray();
422                   tabRuns[runs] = i;
423                 }
424               else
425                 {
426                   rects[i] = new Rectangle(insets.left + tabAreaInsets.left,
427                                            runHeight, maxWidth, height);
428                   runHeight += height;
429                 }
430             }
431           runs++;
432
433           tabAreaRect.width = runs * maxTabWidth - (runs - 1) * tabRunOverlay
434           + tabAreaInsets.left + tabAreaInsets.right;
435           tabAreaRect.height = tabPane.getHeight() - insets.top
436           - insets.bottom;
437           tabAreaRect.y = insets.top;
438           contentRect.width = tabPane.getWidth() - insets.left - insets.right
439           - tabAreaRect.width;
440           contentRect.height = tabAreaRect.height;
441           contentRect.y = insets.top;
442           if (tabPlacement == SwingConstants.LEFT)
443             {
444               tabAreaRect.x = insets.left;
445               contentRect.x = tabAreaRect.x + tabAreaRect.width;
446             }
447           else
448             {
449               contentRect.x = insets.left;
450               tabAreaRect.x = contentRect.x + contentRect.width;
451             }
452         }
453       runCount = runs;
454
455       tabRuns[0] = 0;
456       normalizeTabRuns(tabPlacement, tabCount, start, max);
457       selectedRun = getRunForTab(tabCount, tabPane.getSelectedIndex());
458       if (shouldRotateTabRuns(tabPlacement))
459         rotateTabRuns(tabPlacement, selectedRun);
460
461       // Need to pad the runs and move them to the correct location.
462       for (int i = 0; i < runCount; i++)
463         {
464           int first = lastTabInRun(tabCount, getPreviousTabRun(i)) + 1;
465           if (first == tabCount)
466             first = 0;
467           int last = lastTabInRun(tabCount, i);
468           if (shouldPadTabRun(tabPlacement, i))
469             padTabRun(tabPlacement, first, last, max);
470
471           // Done padding, now need to move it.
472           if (tabPlacement == SwingConstants.TOP && i > 0)
473             {
474               for (int j = first; j <= last; j++)
475                 rects[j].y += (runCount - i) * maxTabHeight
476                 - (runCount - i) * tabRunOverlay;
477             }
478
479           if (tabPlacement == SwingConstants.BOTTOM)
480             {
481               int height = tabPane.getBounds().height - insets.bottom
482               - tabAreaInsets.bottom;
483               int adjustment;
484               if (i == 0)
485                 adjustment = height - maxTabHeight;
486               else
487                 adjustment = height - (runCount - i + 1) * maxTabHeight
488                 - (runCount - i) * tabRunOverlay;
489
490               for (int j = first; j <= last; j++)
491                 rects[j].y = adjustment;
492             }
493
494           if (tabPlacement == SwingConstants.LEFT && i > 0)
495             {
496               for (int j = first; j <= last; j++)
497                 rects[j].x += (runCount - i) * maxTabWidth
498                 - (runCount - i) * tabRunOverlay;
499             }
500
501           if (tabPlacement == SwingConstants.RIGHT)
502             {
503               int width = tabPane.getBounds().width - insets.right
504               - tabAreaInsets.right;
505               int adjustment;
506               if (i == 0)
507                 adjustment = width - maxTabWidth;
508               else
509                 adjustment = width - (runCount - i + 1) * maxTabWidth
510                 + (runCount - i) * tabRunOverlay;
511
512               for (int j = first; j <= last; j++)
513                 rects[j].x = adjustment;
514             }
515         }
516       padSelectedTab(tabPlacement, tabPane.getSelectedIndex());
517     }
518
519     /**
520      * This method is called when the JTabbedPane is laid out in
521      * WRAP_TAB_LAYOUT. It calls calculateLayoutInfo to  find the positions
522      * of all its components.
523      *
524      * @param parent The Container to lay out.
525      */
526     public void layoutContainer(Container parent)
527     {
528       calculateLayoutInfo();
529     }
530
531     /**
532      * This method returns the minimum layout size for the given container.
533      *
534      * @param parent The container that is being sized.
535      *
536      * @return The minimum size.
537      */
538     public Dimension minimumLayoutSize(Container parent)
539     {
540       return calculateSize(false);
541     }
542
543     // If there is more free space in an adjacent run AND the tab in the run can fit in the 
544     // adjacent run, move it. This method is not perfect, it is merely an approximation.
545     // If you play around with Sun's JTabbedPane, you'll see that 
546     // it does do some pretty strange things with regards to not moving tabs 
547     // that should be moved. 
548     // start = the x position where the tabs will begin
549     // max = the maximum position of where the tabs can go to (tabAreaInsets.left + the width of the tab area)
550
551     /**
552      * This method tries to "even out" the number of tabs in each run based on
553      * their widths.
554      *
555      * @param tabPlacement The JTabbedPane's tab placement.
556      * @param tabCount The number of tabs.
557      * @param start The x position where the tabs will begin.
558      * @param max The maximum x position where the tab can run to.
559      */
560     protected void normalizeTabRuns(int tabPlacement, int tabCount, int start,
561                                     int max)
562     {
563       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
564       if (tabPlacement == SwingUtilities.TOP
565           || tabPlacement == SwingUtilities.BOTTOM)
566         {
567           // We should only do this for runCount - 1, cause we can only shift that many times between
568           // runs.
569           for (int i = 1; i < runCount; i++)
570             {
571               Rectangle currRun = rects[lastTabInRun(tabCount, i)];
572               Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))];
573               int spaceInCurr = currRun.x + currRun.width;
574               int spaceInNext = nextRun.x + nextRun.width;
575
576               int diffNow = spaceInCurr - spaceInNext;
577               int diffLater = (spaceInCurr - currRun.width)
578               - (spaceInNext + currRun.width);
579               while (Math.abs(diffLater) < Math.abs(diffNow)
580                   && spaceInNext + currRun.width < max)
581                 {
582                   tabRuns[i]--;
583                   spaceInNext += currRun.width;
584                   spaceInCurr -= currRun.width;
585                   currRun = rects[lastTabInRun(tabCount, i)];
586                   diffNow = spaceInCurr - spaceInNext;
587                   diffLater = (spaceInCurr - currRun.width)
588                   - (spaceInNext + currRun.width);
589                 }
590
591               // Fix the bounds.
592               int first = lastTabInRun(tabCount, i) + 1;
593               int last = lastTabInRun(tabCount, getNextTabRun(i));
594               int currX = tabAreaInsets.left;
595               for (int j = first; j <= last; j++)
596                 {
597                   rects[j].x = currX;
598                   currX += rects[j].width;
599                 }
600             }
601         }
602       else
603         {
604           for (int i = 1; i < runCount; i++)
605             {
606               Rectangle currRun = rects[lastTabInRun(tabCount, i)];
607               Rectangle nextRun = rects[lastTabInRun(tabCount, getNextTabRun(i))];
608               int spaceInCurr = currRun.y + currRun.height;
609               int spaceInNext = nextRun.y + nextRun.height;
610
611               int diffNow = spaceInCurr - spaceInNext;
612               int diffLater = (spaceInCurr - currRun.height)
613               - (spaceInNext + currRun.height);
614               while (Math.abs(diffLater) < Math.abs(diffNow)
615                   && spaceInNext + currRun.height < max)
616                 {
617                   tabRuns[i]--;
618                   spaceInNext += currRun.height;
619                   spaceInCurr -= currRun.height;
620                   currRun = rects[lastTabInRun(tabCount, i)];
621                   diffNow = spaceInCurr - spaceInNext;
622                   diffLater = (spaceInCurr - currRun.height)
623                   - (spaceInNext + currRun.height);
624                 }
625
626               int first = lastTabInRun(tabCount, i) + 1;
627               int last = lastTabInRun(tabCount, getNextTabRun(i));
628               int currY = tabAreaInsets.top;
629               for (int j = first; j <= last; j++)
630                 {
631                   rects[j].y = currY;
632                   currY += rects[j].height;
633                 }
634             }
635         }
636     }
637
638     /**
639      * This method pads the tab at the selected index by the  selected tab pad
640      * insets (so that it looks larger).
641      *
642      * @param tabPlacement The placement of the tabs.
643      * @param selectedIndex The selected index.
644      */
645     protected void padSelectedTab(int tabPlacement, int selectedIndex)
646     {
647       Insets insets = getSelectedTabPadInsets(tabPlacement);
648       rects[selectedIndex].x -= insets.left;
649       rects[selectedIndex].y -= insets.top;
650       rects[selectedIndex].width += insets.left + insets.right;
651       rects[selectedIndex].height += insets.top + insets.bottom;
652     }
653
654     // If the tabs on the run don't fill the width of the window, make it fit now.
655     // start = starting index of the run
656     // end = last index of the run
657     // max = tabAreaInsets.left + width (or equivalent)
658     // assert start <= end.
659
660     /**
661      * This method makes each tab in the run larger so that the  tabs expand
662      * to fill the runs width/height (depending on tabPlacement).
663      *
664      * @param tabPlacement The placement of the tabs.
665      * @param start The index of the first tab.
666      * @param end The last index of the tab
667      * @param max The amount of space in the run (width for TOP and BOTTOM
668      *        tabPlacement).
669      */
670     protected void padTabRun(int tabPlacement, int start, int end, int max)
671     {
672       if (tabPlacement == SwingConstants.TOP
673           || tabPlacement == SwingConstants.BOTTOM)
674         {
675           int runWidth = rects[end].x + rects[end].width;
676           int spaceRemaining = max - runWidth;
677           int numTabs = end - start + 1;
678
679           // now divvy up the space.
680           int spaceAllocated = spaceRemaining / numTabs;
681           int currX = rects[start].x;
682           for (int i = start; i <= end; i++)
683             {
684               rects[i].x = currX;
685               rects[i].width += spaceAllocated;
686               currX += rects[i].width;
687               // This is used because since the spaceAllocated 
688               // variable is an int, it rounds down. Sometimes,
689               // we don't fill an entire row, so we make it do
690               // so now.
691               if (i == end && rects[i].x + rects[i].width != max)
692                 rects[i].width = max - rects[i].x;
693             }
694         }
695       else
696         {
697           int runHeight = rects[end].y + rects[end].height;
698           int spaceRemaining = max - runHeight;
699           int numTabs = end - start + 1;
700
701           int spaceAllocated = spaceRemaining / numTabs;
702           int currY = rects[start].y;
703           for (int i = start; i <= end; i++)
704             {
705               rects[i].y = currY;
706               rects[i].height += spaceAllocated;
707               currY += rects[i].height;
708               if (i == end && rects[i].y + rects[i].height != max)
709                 rects[i].height = max - rects[i].y;
710             }
711         }
712     }
713
714     /**
715      * This method returns the preferred layout size for the given container.
716      *
717      * @param parent The container to size.
718      *
719      * @return The preferred layout size.
720      */
721     public Dimension preferredLayoutSize(Container parent)
722     {
723       return calculateSize(false);
724     }
725
726     /**
727      * This method returns the preferred tab height given a tabPlacement and
728      * width.
729      *
730      * @param tabPlacement The JTabbedPane's tab placement.
731      * @param width The expected width.
732      *
733      * @return The preferred tab area height.
734      */
735     protected int preferredTabAreaHeight(int tabPlacement, int width)
736     {
737       if (tabPane.getTabCount() == 0)
738         return calculateTabAreaHeight(tabPlacement, 0, 0);
739
740       int runs = 0;
741       int runWidth = 0;
742       int tabWidth = 0;
743
744       FontMetrics fm = getFontMetrics();
745
746       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
747       Insets insets = tabPane.getInsets();
748
749       // Only interested in width, this is a messed up rectangle now.
750       width -= tabAreaInsets.left + tabAreaInsets.right + insets.left
751       + insets.right;
752
753       // The reason why we can't use runCount:
754       // This method is only called to calculate the size request
755       // for the tabbedPane. However, this size request is dependent on 
756       // our desired width. We need to find out what the height would
757       // be IF we got our desired width.
758       for (int i = 0; i < tabPane.getTabCount(); i++)
759         {
760           tabWidth = calculateTabWidth(tabPlacement, i, fm);
761           if (runWidth + tabWidth > width)
762             {
763               runWidth = tabWidth;
764               runs++;
765             }
766           else
767             runWidth += tabWidth;
768         }
769       runs++;
770
771       int maxTabHeight = calculateMaxTabHeight(tabPlacement);
772       int tabAreaHeight = calculateTabAreaHeight(tabPlacement, runs,
773                                                  maxTabHeight);
774       return tabAreaHeight;
775     }
776
777     /**
778      * This method calculates the preferred tab area width given a tab
779      * placement and height.
780      *
781      * @param tabPlacement The JTabbedPane's tab placement.
782      * @param height The expected height.
783      *
784      * @return The preferred tab area width.
785      */
786     protected int preferredTabAreaWidth(int tabPlacement, int height)
787     {
788       if (tabPane.getTabCount() == 0)
789         return calculateTabAreaHeight(tabPlacement, 0, 0);
790
791       int runs = 0;
792       int runHeight = 0;
793       int tabHeight = 0;
794
795       FontMetrics fm = getFontMetrics();
796
797       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
798       Insets insets = tabPane.getInsets();
799
800       height -= tabAreaInsets.top + tabAreaInsets.bottom + insets.top
801       + insets.bottom;
802       int fontHeight = fm.getHeight();
803
804       for (int i = 0; i < tabPane.getTabCount(); i++)
805         {
806           tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
807           if (runHeight + tabHeight > height)
808             {
809               runHeight = tabHeight;
810               runs++;
811             }
812           else
813             runHeight += tabHeight;
814         }
815       runs++;
816
817       int maxTabWidth = calculateMaxTabWidth(tabPlacement);
818       int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, maxTabWidth);
819       return tabAreaWidth;
820     }
821
822     /**
823      * This method rotates the places each run in the correct place  the
824      * tabRuns array. See the comment for tabRuns for how the runs are placed
825      * in the array.
826      *
827      * @param tabPlacement The JTabbedPane's tab placement.
828      * @param selectedRun The run the current selection is in.
829      */
830     protected void rotateTabRuns(int tabPlacement, int selectedRun)
831     {
832       if (runCount == 1 || selectedRun == 1 || selectedRun == -1)
833         return;
834       int[] newTabRuns = new int[tabRuns.length];
835       int currentRun = selectedRun;
836       int i = 1;
837       do
838         {
839           newTabRuns[i] = tabRuns[currentRun];
840           currentRun = getNextTabRun(currentRun);
841           i++;
842         }
843       while (i < runCount);
844       if (runCount > 1)
845         newTabRuns[0] = tabRuns[currentRun];
846
847       tabRuns = newTabRuns;
848       BasicTabbedPaneUI.this.selectedRun = 1;
849     }
850
851     /**
852      * This method is called when a component is removed  from the
853      * JTabbedPane.
854      *
855      * @param comp The component removed.
856      */
857     public void removeLayoutComponent(Component comp)
858     {
859       // Do nothing.
860     }
861   }
862
863   /**
864    * This class acts as the LayoutManager for the JTabbedPane in
865    * SCROLL_TAB_MODE.
866    */
867   private class TabbedPaneScrollLayout extends TabbedPaneLayout
868   {
869     /**
870      * This method returns the preferred layout size for the given container.
871      *
872      * @param parent The container to calculate a size for.
873      *
874      * @return The preferred layout size.
875      */
876     public Dimension preferredLayoutSize(Container parent)
877     {
878       return super.calculateSize(true);
879     }
880
881     /**
882      * This method returns the minimum layout size for the given container.
883      *
884      * @param parent The container to calculate a size for.
885      *
886      * @return The minimum layout size.
887      */
888     public Dimension minimumLayoutSize(Container parent)
889     {
890       return super.calculateSize(true);
891     }
892
893     /**
894      * This method calculates the tab area height given  a desired width.
895      *
896      * @param tabPlacement The JTabbedPane's tab placement.
897      * @param width The expected width.
898      *
899      * @return The tab area height given the width.
900      */
901     protected int preferredTabAreaHeight(int tabPlacement, int width)
902     {
903       if (tabPane.getTabCount() == 0)
904         return calculateTabAreaHeight(tabPlacement, 0, 0);
905
906       int runs = 1;
907
908       int maxTabHeight = calculateMaxTabHeight(tabPlacement);
909       int tabAreaHeight = calculateTabAreaHeight(tabPlacement, runs,
910                                                  maxTabHeight);
911       return tabAreaHeight;
912     }
913
914     /**
915      * This method calculates the tab area width given a desired height.
916      *
917      * @param tabPlacement The JTabbedPane's tab placement.
918      * @param height The expected height.
919      *
920      * @return The tab area width given the height.
921      */
922     protected int preferredTabAreaWidth(int tabPlacement, int height)
923     {
924       if (tabPane.getTabCount() == 0)
925         return calculateTabAreaHeight(tabPlacement, 0, 0);
926
927       int runs = 1;
928
929       int maxTabWidth = calculateMaxTabWidth(tabPlacement);
930       int tabAreaWidth = calculateTabAreaWidth(tabPlacement, runs, maxTabWidth);
931       return tabAreaWidth;
932     }
933
934     /**
935      * This method is called to calculate the tab rectangles.  This method
936      * will calculate the size and position of all  rectangles (taking into
937      * account which ones should be in which tab run). It will pad them and
938      * normalize them  as necessary.
939      *
940      * @param tabPlacement The JTabbedPane's tab placement.
941      * @param tabCount The number of tabs.
942      */
943     protected void calculateTabRects(int tabPlacement, int tabCount)
944     {
945       if (tabCount == 0)
946         return;
947
948       FontMetrics fm = getFontMetrics();
949       SwingUtilities.calculateInnerArea(tabPane, calcRect);
950       Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
951       Insets insets = tabPane.getInsets();
952       int runs = 1;
953       int start = 0;
954       int top = 0;
955       if (tabPlacement == SwingConstants.TOP
956           || tabPlacement == SwingConstants.BOTTOM)
957         {
958           int maxHeight = calculateMaxTabHeight(tabPlacement);
959           calcRect.width -= tabAreaInsets.left + tabAreaInsets.right;
960           start = tabAreaInsets.left + insets.left;
961           int width = 0;
962           int runWidth = start;
963           top = insets.top + tabAreaInsets.top;
964           for (int i = 0; i < tabCount; i++)
965             {
966               width = calculateTabWidth(tabPlacement, i, fm);
967
968               rects[i] = new Rectangle(runWidth, top, width, maxHeight);
969               runWidth += width;
970             }
971           tabAreaRect.width = tabPane.getWidth() - insets.left - insets.right;
972           tabAreaRect.height = runs * maxTabHeight
973           - (runs - 1) * tabRunOverlay
974           + tabAreaInsets.top + tabAreaInsets.bottom;
975           contentRect.width = tabAreaRect.width;
976           contentRect.height = tabPane.getHeight() - insets.top
977           - insets.bottom - tabAreaRect.height;
978           contentRect.x = insets.left;
979           tabAreaRect.x = insets.left;
980           if (tabPlacement == SwingConstants.BOTTOM)
981             {
982               contentRect.y = insets.top;
983               tabAreaRect.y = contentRect.y + contentRect.height;
984             }
985           else
986             {
987               tabAreaRect.y = insets.top;
988               contentRect.y = tabAreaRect.y + tabAreaRect.height;
989             }
990         }
991       else
992         {
993           int maxWidth = calculateMaxTabWidth(tabPlacement);
994
995           calcRect.height -= tabAreaInsets.top + tabAreaInsets.bottom;
996           int height = 0;
997           start = tabAreaInsets.top + insets.top;
998           int runHeight = start;
999           int fontHeight = fm.getHeight();
1000           top = insets.left + tabAreaInsets.left;
1001           for (int i = 0; i < tabCount; i++)
1002             {
1003               height = calculateTabHeight(tabPlacement, i, fontHeight);
1004               rects[i] = new Rectangle(top, runHeight, maxWidth, height);
1005               runHeight += height;
1006             }
1007           tabAreaRect.width = runs * maxTabWidth - (runs - 1) * tabRunOverlay
1008           + tabAreaInsets.left + tabAreaInsets.right;
1009           tabAreaRect.height = tabPane.getHeight() - insets.top
1010           - insets.bottom;
1011           tabAreaRect.y = insets.top;
1012           contentRect.width = tabPane.getWidth() - insets.left - insets.right
1013           - tabAreaRect.width;
1014           contentRect.height = tabAreaRect.height;
1015           contentRect.y = insets.top;
1016           if (tabPlacement == SwingConstants.LEFT)
1017             {
1018               tabAreaRect.x = insets.left;
1019               contentRect.x = tabAreaRect.x + tabAreaRect.width;
1020             }
1021           else
1022             {
1023               contentRect.x = insets.left;
1024               tabAreaRect.x = contentRect.x + contentRect.width;
1025             }
1026         }
1027       runCount = runs;
1028
1029       padSelectedTab(tabPlacement, tabPane.getSelectedIndex());
1030     }
1031
1032     /**
1033      * This method is called when the JTabbedPane is laid out in
1034      * SCROLL_TAB_LAYOUT. It finds the position for all components in the
1035      * JTabbedPane.
1036      *
1037      * @param pane The JTabbedPane to be laid out.
1038      */
1039     public void layoutContainer(Container pane)
1040     {
1041       super.layoutContainer(pane);
1042       int tabCount = tabPane.getTabCount();
1043       Point p = null;
1044       if (tabCount == 0)
1045         return;
1046       int tabPlacement = tabPane.getTabPlacement();
1047       incrButton.setVisible(false);
1048       decrButton.setVisible(false);
1049       if (tabPlacement == SwingConstants.TOP
1050           || tabPlacement == SwingConstants.BOTTOM)
1051         {
1052           if (tabAreaRect.x + tabAreaRect.width < rects[tabCount - 1].x
1053               + rects[tabCount - 1].width)
1054             {
1055               Dimension incrDims = incrButton.getPreferredSize();
1056               Dimension decrDims = decrButton.getPreferredSize();
1057
1058               decrButton.setBounds(tabAreaRect.x + tabAreaRect.width
1059                                    - incrDims.width - decrDims.width,
1060                                    tabAreaRect.y, decrDims.width,
1061                                    tabAreaRect.height);
1062               incrButton.setBounds(tabAreaRect.x + tabAreaRect.width
1063                                    - incrDims.width, tabAreaRect.y,
1064                                    decrDims.width, tabAreaRect.height);
1065
1066               tabAreaRect.width -= decrDims.width + incrDims.width;
1067               incrButton.setVisible(true);
1068               decrButton.setVisible(true);
1069             }
1070         }
1071
1072       if (tabPlacement == SwingConstants.LEFT
1073           || tabPlacement == SwingConstants.RIGHT)
1074         {
1075           if (tabAreaRect.y + tabAreaRect.height < rects[tabCount - 1].y
1076               + rects[tabCount - 1].height)
1077             {
1078               Dimension incrDims = incrButton.getPreferredSize();
1079               Dimension decrDims = decrButton.getPreferredSize();
1080
1081               decrButton.setBounds(tabAreaRect.x,
1082                                    tabAreaRect.y + tabAreaRect.height
1083                                    - incrDims.height - decrDims.height,
1084                                    tabAreaRect.width, decrDims.height);
1085               incrButton.setBounds(tabAreaRect.x,
1086                                    tabAreaRect.y + tabAreaRect.height
1087                                    - incrDims.height, tabAreaRect.width,
1088                                    incrDims.height);
1089
1090               tabAreaRect.height -= decrDims.height + incrDims.height;
1091               incrButton.setVisible(true);
1092               decrButton.setVisible(true);
1093             }
1094         }
1095       viewport.setBounds(tabAreaRect.x, tabAreaRect.y, tabAreaRect.width,
1096                          tabAreaRect.height);
1097       int tabC = tabPane.getTabCount() - 1;
1098       if (tabCount > 0)
1099         {
1100           int w = Math.max(rects[tabC].width + rects[tabC].x, tabAreaRect.width);
1101           int h = Math.max(rects[tabC].height, tabAreaRect.height);
1102           p = findPointForIndex(currentScrollLocation);
1103           
1104           // we want to cover that entire space so that borders that run under
1105           // the tab area don't show up when we move the viewport around.
1106           panel.setSize(w + p.x, h + p.y);
1107         }
1108       viewport.setViewPosition(p);
1109       viewport.repaint();
1110     }
1111   }
1112
1113   /**
1114    * This class handles ChangeEvents from the JTabbedPane.
1115    *
1116    * @specnote Apparently this class was intended to be protected,
1117    *           but was made public by a compiler bug and is now
1118    *           public for compatibility.
1119    */
1120   public class TabSelectionHandler implements ChangeListener
1121   {
1122     /**
1123      * This method is called whenever a ChangeEvent is fired from the
1124      * JTabbedPane.
1125      *
1126      * @param e The ChangeEvent fired.
1127      */
1128     public void stateChanged(ChangeEvent e)
1129     {
1130       selectedRun = getRunForTab(tabPane.getTabCount(),
1131                                  tabPane.getSelectedIndex());
1132       tabPane.revalidate();
1133       tabPane.repaint();
1134     }
1135   }
1136
1137   /**
1138    * This helper class is a JPanel that fits inside the ScrollViewport. This
1139    * panel's sole job is to paint the tab rectangles inside the  viewport so
1140    * that it's clipped correctly.
1141    */
1142   private class ScrollingPanel extends JPanel
1143   {
1144     /**
1145      * This is a private UI class for our panel.
1146      */
1147     private class ScrollingPanelUI extends BasicPanelUI
1148     {
1149       /**
1150        * This method overrides the default paint method. It paints the tab
1151        * rectangles for the JTabbedPane in the panel.
1152        *
1153        * @param g The Graphics object to paint with.
1154        * @param c The JComponent to paint.
1155        */
1156       public void paint(Graphics g, JComponent c)
1157       {
1158         paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1159       }
1160     }
1161
1162     /**
1163      * This method overrides the updateUI method. It makes the default UI for
1164      * this ScrollingPanel to be  a ScrollingPanelUI.
1165      */
1166     public void updateUI()
1167     {
1168       setUI((PanelUI) new ScrollingPanelUI());
1169     }
1170   }
1171
1172   /**
1173    * This is a helper class that paints the panel that paints tabs. This
1174    * custom JViewport is used so that the tabs painted in the panel will be
1175    * clipped. This class implements UIResource so tabs are not added when
1176    * this objects of this class are added to the  JTabbedPane.
1177    */
1178   private class ScrollingViewport extends JViewport implements UIResource
1179   {
1180     // TODO: Maybe remove this inner class.
1181   }
1182
1183   /**
1184    * This is a helper class that implements UIResource so it is not added as a
1185    * tab when an object of this class is added to the JTabbedPane.
1186    */
1187   private class ScrollingButton extends BasicArrowButton implements UIResource
1188   {
1189     /**
1190      * Creates a ScrollingButton given the direction.
1191      *
1192      * @param dir The direction to point in.
1193      */
1194     public ScrollingButton(int dir)
1195     {
1196       super(dir);
1197     }
1198   }
1199
1200   /** The button that increments the current scroll location.
1201    * This is package-private to avoid an accessor method.  */
1202   transient ScrollingButton incrButton;
1203
1204   /** The button that decrements the current scroll location.
1205    * This is package-private to avoid an accessor method.  */
1206   transient ScrollingButton decrButton;
1207
1208   /** The viewport used to display the tabs.
1209    * This is package-private to avoid an accessor method.  */
1210   transient ScrollingViewport viewport;
1211
1212   /** The panel inside the viewport that paints the tabs.
1213    * This is package-private to avoid an accessor method.  */
1214   transient ScrollingPanel panel;
1215
1216   /** The starting visible tab in the run in SCROLL_TAB_MODE.
1217    * This is package-private to avoid an accessor method.  */
1218   transient int currentScrollLocation;
1219
1220   /** A reusable rectangle. */
1221   protected Rectangle calcRect;
1222
1223   /** An array of Rectangles keeping track of the tabs' area and position. */
1224   protected Rectangle[] rects;
1225
1226   /** The insets around the content area. */
1227   protected Insets contentBorderInsets;
1228
1229   /** The extra insets around the selected tab. */
1230   protected Insets selectedTabPadInsets;
1231
1232   /** The insets around the tab area. */
1233   protected Insets tabAreaInsets;
1234
1235   /** The insets around each and every tab. */
1236   protected Insets tabInsets;
1237
1238   /**
1239    * The outer bottom and right edge color for both the tab and content
1240    * border.
1241    */
1242   protected Color darkShadow;
1243
1244   /** The color of the focus outline on the selected tab. */
1245   protected Color focus;
1246
1247   /** FIXME: find a use for this. */
1248   protected Color highlight;
1249
1250   /** The top and left edge color for both the tab and content border. */
1251   protected Color lightHighlight;
1252
1253   /** The inner bottom and right edge color for the tab and content border. */
1254   protected Color shadow;
1255
1256   /** The maximum tab height. */
1257   protected int maxTabHeight;
1258
1259   /** The maximum tab width. */
1260   protected int maxTabWidth;
1261
1262   /** The number of runs in the JTabbedPane. */
1263   protected int runCount;
1264
1265   /** The index of the run that the selected index is in. */
1266   protected int selectedRun;
1267
1268   /** The amount of space each run overlaps the previous by. */
1269   protected int tabRunOverlay;
1270
1271   /** The gap between text and label */
1272   protected int textIconGap;
1273
1274   // Keeps track of tab runs.
1275   // The organization of this array is as follows (lots of experimentation to
1276   // figure this out)
1277   // index 0 = furthest away from the component area (aka outer run)
1278   // index 1 = closest to component area (aka selected run)
1279   // index > 1 = listed in order leading from selected run to outer run.
1280   // each int in the array is the tab index + 1 (counting starts at 1)
1281   // for the last tab in the run. (same as the rects array)
1282
1283   /** This array keeps track of which tabs are in which run. See above. */
1284   protected int[] tabRuns;
1285
1286   /**
1287    * This is the keystroke for moving down.
1288    *
1289    * @deprecated 1.3
1290    */
1291   protected KeyStroke downKey;
1292
1293   /**
1294    * This is the keystroke for moving left.
1295    *
1296    * @deprecated 1.3
1297    */
1298   protected KeyStroke leftKey;
1299
1300   /**
1301    * This is the keystroke for moving right.
1302    *
1303    * @deprecated 1.3
1304    */
1305   protected KeyStroke rightKey;
1306
1307   /**
1308    * This is the keystroke for moving up.
1309    *
1310    * @deprecated 1.3
1311    */
1312   protected KeyStroke upKey;
1313
1314   /** The listener that listens for focus events. */
1315   protected FocusListener focusListener;
1316
1317   /** The listener that listens for mouse events. */
1318   protected MouseListener mouseListener;
1319
1320   /** The listener that listens for property change events. */
1321   protected PropertyChangeListener propertyChangeListener;
1322
1323   /** The listener that listens for change events. */
1324   protected ChangeListener tabChangeListener;
1325
1326   /** The tab pane that this UI paints. */
1327   protected JTabbedPane tabPane;
1328
1329   /** The current layout manager for the tabPane.
1330    * This is package-private to avoid an accessor method.  */
1331   transient LayoutManager layoutManager;
1332
1333   /** The rectangle that describes the tab area's position and size.
1334    * This is package-private to avoid an accessor method.  */
1335   transient Rectangle tabAreaRect;
1336
1337   /** The rectangle that describes the content area's position and
1338    * size.  This is package-private to avoid an accessor method.  */
1339   transient Rectangle contentRect;
1340
1341   /**
1342    * Creates a new BasicTabbedPaneUI object.
1343    */
1344   public BasicTabbedPaneUI()
1345   {
1346     super();
1347     rects = new Rectangle[0];
1348     tabRuns = new int[10];
1349   }
1350
1351   /**
1352    * This method creates a ScrollingButton that  points in the appropriate
1353    * direction for an increasing button.
1354    * This is package-private to avoid an accessor method.
1355    *
1356    * @return The increase ScrollingButton.
1357    */
1358   ScrollingButton createIncreaseButton()
1359   {
1360     if (incrButton == null)
1361       incrButton = new ScrollingButton(SwingConstants.NORTH);
1362     if (tabPane.getTabPlacement() == SwingConstants.TOP
1363         || tabPane.getTabPlacement() == SwingConstants.BOTTOM)
1364       incrButton.setDirection(SwingConstants.EAST);
1365     else
1366       incrButton.setDirection(SwingConstants.SOUTH);
1367     return incrButton;
1368   }
1369
1370   /**
1371    * This method creates a ScrollingButton that points in the appropriate
1372    * direction for a decreasing button.
1373    * This is package-private to avoid an accessor method.
1374    *
1375    * @return The decrease ScrollingButton.
1376    */
1377   ScrollingButton createDecreaseButton()
1378   {
1379     if (decrButton == null)
1380       decrButton = new ScrollingButton(SwingConstants.SOUTH);
1381     if (tabPane.getTabPlacement() == SwingConstants.TOP
1382         || tabPane.getTabPlacement() == SwingConstants.BOTTOM)
1383       decrButton.setDirection(SwingConstants.WEST);
1384     else
1385       decrButton.setDirection(SwingConstants.NORTH);
1386     return decrButton;
1387   }
1388
1389   /**
1390    * This method finds the point to set the view  position at given the index
1391    * of a tab. The tab will be the first visible tab in the run.
1392    * This is package-private to avoid an accessor method.
1393    *
1394    * @param index The index of the first visible tab.
1395    *
1396    * @return The position of the first visible tab.
1397    */
1398   Point findPointForIndex(int index)
1399   {
1400     int tabPlacement = tabPane.getTabPlacement();
1401     int selectedIndex = tabPane.getSelectedIndex();
1402     Insets insets = getSelectedTabPadInsets(tabPlacement);
1403     int w = 0;
1404     int h = 0;
1405
1406     if (tabPlacement == TOP || tabPlacement == BOTTOM)
1407       {
1408         if (index > 0)
1409           {
1410             w += rects[index - 1].x + rects[index - 1].width;
1411             if (index > selectedIndex)
1412               w -= insets.left + insets.right;
1413           }
1414       }
1415
1416     else
1417       {
1418         if (index > 0)
1419           {
1420             h += rects[index - 1].y + rects[index - 1].height;
1421             if (index > selectedIndex)
1422               h -= insets.top + insets.bottom;
1423           }
1424       }
1425
1426     Point p = new Point(w, h);
1427     return p;
1428   }
1429
1430   /**
1431    * This method creates a new BasicTabbedPaneUI.
1432    *
1433    * @param c The JComponent to create a UI for.
1434    *
1435    * @return A new BasicTabbedPaneUI.
1436    */
1437   public static ComponentUI createUI(JComponent c)
1438   {
1439     return new BasicTabbedPaneUI();
1440   }
1441
1442   /**
1443    * This method installs the UI for the given JComponent.
1444    *
1445    * @param c The JComponent to install the UI for.
1446    */
1447   public void installUI(JComponent c)
1448   {
1449     super.installUI(c);
1450     if (c instanceof JTabbedPane)
1451       {
1452         tabPane = (JTabbedPane) c;
1453         
1454         installComponents();
1455         installDefaults();
1456         installListeners();
1457         installKeyboardActions();
1458         
1459         layoutManager = createLayoutManager();
1460         tabPane.setLayout(layoutManager);
1461       }
1462   }
1463
1464   /**
1465    * This method uninstalls the UI for the  given JComponent.
1466    *
1467    * @param c The JComponent to uninstall the UI for.
1468    */
1469   public void uninstallUI(JComponent c)
1470   {
1471     layoutManager = null;
1472
1473     uninstallKeyboardActions();
1474     uninstallListeners();
1475     uninstallDefaults();
1476     uninstallComponents();
1477
1478     tabPane = null;
1479   }
1480
1481   /**
1482    * This method creates the appropriate layout manager for the JTabbedPane's
1483    * current tab layout policy. If the tab layout policy is
1484    * SCROLL_TAB_LAYOUT, then all the associated components that need to be
1485    * created will be done so now.
1486    *
1487    * @return A layout manager given the tab layout policy.
1488    */
1489   protected LayoutManager createLayoutManager()
1490   {
1491     if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT)
1492       return new TabbedPaneLayout();
1493     else
1494       {
1495         incrButton = createIncreaseButton();
1496         decrButton = createDecreaseButton();
1497         viewport = new ScrollingViewport();
1498         viewport.setLayout(null);
1499         panel = new ScrollingPanel();
1500         viewport.setView(panel);
1501         tabPane.add(incrButton);
1502         tabPane.add(decrButton);
1503         tabPane.add(viewport);
1504         currentScrollLocation = 0;
1505         decrButton.setEnabled(false);
1506         panel.addMouseListener(mouseListener);
1507         incrButton.addMouseListener(mouseListener);
1508         decrButton.addMouseListener(mouseListener);
1509         viewport.setBackground(Color.LIGHT_GRAY);
1510
1511         return new TabbedPaneScrollLayout();
1512       }
1513   }
1514
1515   /**
1516    * This method installs components for this JTabbedPane.
1517    */
1518   protected void installComponents()
1519   {
1520     // Nothing to be done.
1521   }
1522
1523   /**
1524    * This method uninstalls components for this JTabbedPane.
1525    */
1526   protected void uninstallComponents()
1527   {
1528     // Nothing to be done.
1529   }
1530
1531   /**
1532    * This method installs defaults for the Look and Feel.
1533    */
1534   protected void installDefaults()
1535   {
1536     LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
1537                                      "TabbedPane.foreground",
1538                                      "TabbedPane.font");
1539     tabPane.setOpaque(false);
1540
1541     highlight = UIManager.getColor("TabbedPane.highlight");
1542     lightHighlight = UIManager.getColor("TabbedPane.lightHighlight");
1543
1544     shadow = UIManager.getColor("TabbedPane.shadow");
1545     darkShadow = UIManager.getColor("TabbedPane.darkShadow");
1546
1547     focus = UIManager.getColor("TabbedPane.focus");
1548
1549     textIconGap = UIManager.getInt("TabbedPane.textIconGap");
1550     tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
1551
1552     tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
1553     selectedTabPadInsets = UIManager.getInsets("TabbedPane.tabbedPaneTabPadInsets");
1554     tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
1555     contentBorderInsets = UIManager.getInsets("TabbedPane.tabbedPaneContentBorderInsets");
1556
1557     calcRect = new Rectangle();
1558     tabRuns = new int[10];
1559     tabAreaRect = new Rectangle();
1560     contentRect = new Rectangle();
1561   }
1562
1563   /**
1564    * This method uninstalls defaults for the Look and Feel.
1565    */
1566   protected void uninstallDefaults()
1567   {
1568     calcRect = null;
1569     tabAreaRect = null;
1570     contentRect = null;
1571     tabRuns = null;
1572
1573     contentBorderInsets = null;
1574     tabAreaInsets = null;
1575     selectedTabPadInsets = null;
1576     tabInsets = null;
1577
1578     focus = null;
1579     darkShadow = null;
1580     shadow = null;
1581     lightHighlight = null;
1582     highlight = null;
1583
1584     tabPane.setBackground(null);
1585     tabPane.setForeground(null);
1586     tabPane.setFont(null);
1587   }
1588
1589   /**
1590    * This method creates and installs the listeners for this UI.
1591    */
1592   protected void installListeners()
1593   {
1594     mouseListener = createMouseListener();
1595     tabChangeListener = createChangeListener();
1596     propertyChangeListener = createPropertyChangeListener();
1597     focusListener = createFocusListener();
1598
1599     tabPane.addMouseListener(mouseListener);
1600     tabPane.addChangeListener(tabChangeListener);
1601     tabPane.addPropertyChangeListener(propertyChangeListener);
1602     tabPane.addFocusListener(focusListener);
1603   }
1604
1605   /**
1606    * This method removes and nulls the listeners for this UI.
1607    */
1608   protected void uninstallListeners()
1609   {
1610     tabPane.removeFocusListener(focusListener);
1611     tabPane.removePropertyChangeListener(propertyChangeListener);
1612     tabPane.removeChangeListener(tabChangeListener);
1613     tabPane.removeMouseListener(mouseListener);
1614
1615     focusListener = null;
1616     propertyChangeListener = null;
1617     tabChangeListener = null;
1618     mouseListener = null;
1619   }
1620
1621   /**
1622    * This method creates a new MouseListener.
1623    *
1624    * @return A new MouseListener.
1625    */
1626   protected MouseListener createMouseListener()
1627   {
1628     return new MouseHandler();
1629   }
1630
1631   /**
1632    * This method creates a new FocusListener.
1633    *
1634    * @return A new FocusListener.
1635    */
1636   protected FocusListener createFocusListener()
1637   {
1638     return new FocusHandler();
1639   }
1640
1641   /**
1642    * This method creates a new ChangeListener.
1643    *
1644    * @return A new ChangeListener.
1645    */
1646   protected ChangeListener createChangeListener()
1647   {
1648     return new TabSelectionHandler();
1649   }
1650
1651   /**
1652    * This method creates a new PropertyChangeListener.
1653    *
1654    * @return A new PropertyChangeListener.
1655    */
1656   protected PropertyChangeListener createPropertyChangeListener()
1657   {
1658     return new PropertyChangeHandler();
1659   }
1660
1661   /**
1662    * This method installs keyboard actions for the JTabbedPane.
1663    */
1664   protected void installKeyboardActions()
1665   {
1666     // FIXME: Implement.
1667   }
1668
1669   /**
1670    * This method uninstalls keyboard actions for the JTabbedPane.
1671    */
1672   protected void uninstallKeyboardActions()
1673   {
1674     // FIXME: Implement.
1675   }
1676
1677   /**
1678    * This method returns the minimum size of the JTabbedPane.
1679    *
1680    * @param c The JComponent to find a size for.
1681    *
1682    * @return The minimum size.
1683    */
1684   public Dimension getMinimumSize(JComponent c)
1685   {
1686     return layoutManager.minimumLayoutSize(tabPane);
1687   }
1688
1689   /**
1690    * This method returns the maximum size of the JTabbedPane.
1691    *
1692    * @param c The JComponent to find a size for.
1693    *
1694    * @return The maximum size.
1695    */
1696   public Dimension getMaximumSize(JComponent c)
1697   {
1698     return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
1699   }
1700
1701   /**
1702    * This method paints the JTabbedPane.
1703    *
1704    * @param g The Graphics object to paint with.
1705    * @param c The JComponent to paint.
1706    */
1707   public void paint(Graphics g, JComponent c)
1708   {
1709     if (tabPane.getTabCount() == 0)
1710       return;
1711     if (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT)
1712       paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1713     paintContentBorder(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
1714   }
1715
1716   /**
1717    * This method paints the tab area. This includes painting the rectangles
1718    * that make up the tabs.
1719    *
1720    * @param g The Graphics object to paint with.
1721    * @param tabPlacement The JTabbedPane's tab placement.
1722    * @param selectedIndex The selected index.
1723    */
1724   protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex)
1725   {
1726     Rectangle ir = new Rectangle();
1727     Rectangle tr = new Rectangle();
1728
1729     boolean isScroll = tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
1730
1731     // Please note: the ordering of the painting is important. 
1732     // we WANT to paint the outermost run first and then work our way in.
1733     int tabCount = tabPane.getTabCount();
1734     int currRun = 1;
1735     
1736     if (tabCount > runCount)
1737       runCount = tabCount;
1738     
1739     if (tabCount < 1)
1740       return;
1741     
1742     if (runCount > 1)
1743       currRun = 0;    
1744     for (int i = 0; i < runCount; i++)
1745       {
1746         int first = lastTabInRun(tabCount, getPreviousTabRun(currRun)) + 1;
1747         if (isScroll)
1748           first = currentScrollLocation;
1749         else if (first == tabCount)
1750           first = 0;
1751         int last = lastTabInRun(tabCount, currRun);
1752         if (isScroll)
1753           {
1754             for (int k = first; k < tabCount; k++)
1755               {
1756                 if (rects[k].x + rects[k].width - rects[first].x > viewport
1757                     .getWidth())
1758                   {
1759                     last = k;
1760                     break;
1761                   }
1762               }
1763           }
1764
1765         for (int j = first; j <= last; j++)
1766           {
1767             if (j != selectedIndex || isScroll)
1768               paintTab(g, tabPlacement, rects, j, ir, tr);
1769           }
1770         currRun = getPreviousTabRun(currRun);
1771       }
1772     if (! isScroll)
1773       paintTab(g, tabPlacement, rects, selectedIndex, ir, tr);
1774   }
1775
1776   /**
1777    * This method paints an individual tab.
1778    *
1779    * @param g The Graphics object to paint with.
1780    * @param tabPlacement The JTabbedPane's tab placement.
1781    * @param rects The array of rectangles that keep the size and position of
1782    *        the tabs.
1783    * @param tabIndex The tab index to paint.
1784    * @param iconRect The rectangle to use for the icon.
1785    * @param textRect The rectangle to use for the text.
1786    */
1787   protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects,
1788                           int tabIndex, Rectangle iconRect, Rectangle textRect)
1789   {
1790     FontMetrics fm = getFontMetrics();
1791     Icon icon = getIconForTab(tabIndex);
1792     String title = tabPane.getTitleAt(tabIndex);
1793     boolean isSelected = tabIndex == tabPane.getSelectedIndex();
1794     calcRect = getTabBounds(tabPane, tabIndex);
1795
1796     int x = calcRect.x;
1797     int y = calcRect.y;
1798     int w = calcRect.width;
1799     int h = calcRect.height;
1800     if (getRunForTab(tabPane.getTabCount(), tabIndex) == 1)
1801       {
1802         Insets insets = getTabAreaInsets(tabPlacement);
1803         switch (tabPlacement)
1804         {
1805         case TOP:
1806           h += insets.bottom;
1807           break;
1808         case LEFT:
1809           w += insets.right;
1810           break;
1811         case BOTTOM:
1812           y -= insets.top;
1813           h += insets.top;
1814           break;
1815         case RIGHT:
1816           x -= insets.left;
1817           w += insets.left;
1818           break;
1819         }
1820       }
1821
1822     layoutLabel(tabPlacement, fm, tabIndex, title, icon, calcRect, iconRect,
1823                 textRect, isSelected);
1824     paintTabBackground(g, tabPlacement, tabIndex, x, y, w, h, isSelected);
1825     paintTabBorder(g, tabPlacement, tabIndex, x, y, w, h, isSelected);
1826
1827     // FIXME: Paint little folding corner and jagged edge clipped tab.
1828     if (icon != null)
1829       paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
1830     if (title != null && ! title.equals(""))
1831       paintText(g, tabPlacement, tabPane.getFont(), fm, tabIndex, title,
1832                 textRect, isSelected);
1833   }
1834
1835   /**
1836    * This method lays out the tab and finds the location to paint the  icon
1837    * and text.
1838    *
1839    * @param tabPlacement The JTabbedPane's tab placement.
1840    * @param metrics The font metrics for the font to paint with.
1841    * @param tabIndex The tab index to paint.
1842    * @param title The string painted.
1843    * @param icon The icon painted.
1844    * @param tabRect The tab bounds.
1845    * @param iconRect The calculated icon bounds.
1846    * @param textRect The calculated text bounds.
1847    * @param isSelected Whether this tab is selected.
1848    */
1849   protected void layoutLabel(int tabPlacement, FontMetrics metrics,
1850                              int tabIndex, String title, Icon icon,
1851                              Rectangle tabRect, Rectangle iconRect,
1852                              Rectangle textRect, boolean isSelected)
1853   {
1854     SwingUtilities.layoutCompoundLabel(metrics, title, icon,
1855                                        SwingConstants.CENTER,
1856                                        SwingConstants.CENTER,
1857                                        SwingConstants.CENTER,
1858                                        SwingConstants.RIGHT, tabRect,
1859                                        iconRect, textRect, textIconGap);
1860
1861     int shiftX = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
1862     int shiftY = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
1863
1864     iconRect.x += shiftX;
1865     iconRect.y += shiftY;
1866
1867     textRect.x += shiftX;
1868     textRect.y += shiftY;
1869   }
1870
1871   /**
1872    * This method paints the icon.
1873    *
1874    * @param g The Graphics object to paint.
1875    * @param tabPlacement The JTabbedPane's tab placement.
1876    * @param tabIndex The tab index to paint.
1877    * @param icon The icon to paint.
1878    * @param iconRect The bounds of the icon.
1879    * @param isSelected Whether this tab is selected.
1880    */
1881   protected void paintIcon(Graphics g, int tabPlacement, int tabIndex,
1882                            Icon icon, Rectangle iconRect, boolean isSelected)
1883   {
1884     if (icon != null)
1885       icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
1886   }
1887
1888   /**
1889    * This method paints the text for the given tab.
1890    *
1891    * @param g The Graphics object to paint with.
1892    * @param tabPlacement The JTabbedPane's tab placement.
1893    * @param font The font to paint with.
1894    * @param metrics The fontmetrics of the given font.
1895    * @param tabIndex The tab index.
1896    * @param title The string to paint.
1897    * @param textRect The bounds of the string.
1898    * @param isSelected Whether this tab is selected.
1899    */
1900   protected void paintText(Graphics g, int tabPlacement, Font font,
1901                            FontMetrics metrics, int tabIndex, String title,
1902                            Rectangle textRect, boolean isSelected)
1903   {
1904     View textView = getTextViewForTab(tabIndex);
1905     if (textView != null)
1906       {
1907         textView.paint(g, textRect);
1908         return;
1909       }
1910
1911     Color fg = tabPane.getForegroundAt(tabIndex);
1912     if (fg == null)
1913       fg = tabPane.getForeground();
1914     Color bg = tabPane.getBackgroundAt(tabIndex);
1915     if (bg == null)
1916       bg = tabPane.getBackground();
1917
1918     Color saved_color = g.getColor();
1919     Font f = g.getFont();
1920     g.setFont(font);
1921
1922     if (tabPane.isEnabledAt(tabIndex))
1923       {
1924         g.setColor(fg);
1925
1926         int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1927
1928         if (mnemIndex != -1)
1929           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1930                                                        textRect.x,
1931                                                        textRect.y
1932                                                        + metrics.getAscent());
1933         else
1934           g.drawString(title, textRect.x, textRect.y + metrics.getAscent());
1935       }
1936     else
1937       {
1938         g.setColor(bg.brighter());
1939
1940         int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1941
1942         if (mnemIndex != -1)
1943           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1944                                                        textRect.x, textRect.y);
1945         else
1946           g.drawString(title, textRect.x, textRect.y);
1947
1948         g.setColor(bg.darker());
1949         if (mnemIndex != -1)
1950           BasicGraphicsUtils.drawStringUnderlineCharAt(g, title, mnemIndex,
1951                                                        textRect.x + 1,
1952                                                        textRect.y + 1);
1953         else
1954           g.drawString(title, textRect.x + 1, textRect.y + 1);
1955       }
1956
1957     g.setColor(saved_color);
1958     g.setFont(f);
1959   }
1960
1961   /**
1962    * This method returns how much the label for the tab should shift in the X
1963    * direction.
1964    *
1965    * @param tabPlacement The JTabbedPane's tab placement.
1966    * @param tabIndex The tab index being painted.
1967    * @param isSelected Whether this tab is selected.
1968    *
1969    * @return The amount the label should shift by in the X direction.
1970    */
1971   protected int getTabLabelShiftX(int tabPlacement, int tabIndex,
1972                                   boolean isSelected)
1973   {
1974     // No reason to shift.
1975     return 0;
1976   }
1977
1978   /**
1979    * This method returns how much the label for the tab should shift in the Y
1980    * direction.
1981    *
1982    * @param tabPlacement The JTabbedPane's tab placement.
1983    * @param tabIndex The tab index being painted.
1984    * @param isSelected Whether this tab is selected.
1985    *
1986    * @return The amount the label should shift by in the Y direction.
1987    */
1988   protected int getTabLabelShiftY(int tabPlacement, int tabIndex,
1989                                   boolean isSelected)
1990   {
1991     // No reason to shift.
1992     return 0;
1993   }
1994
1995   /**
1996    * This method paints the focus rectangle around the selected tab.
1997    *
1998    * @param g The Graphics object to paint with.
1999    * @param tabPlacement The JTabbedPane's tab placement.
2000    * @param rects The array of rectangles keeping track of size and position.
2001    * @param tabIndex The tab index.
2002    * @param iconRect The icon bounds.
2003    * @param textRect The text bounds.
2004    * @param isSelected Whether this tab is selected.
2005    */
2006   protected void paintFocusIndicator(Graphics g, int tabPlacement,
2007                                      Rectangle[] rects, int tabIndex,
2008                                      Rectangle iconRect, Rectangle textRect,
2009                                      boolean isSelected)
2010   {
2011     Color saved = g.getColor();
2012     calcRect = iconRect.union(textRect);
2013
2014     g.setColor(focus);
2015
2016     g.drawRect(calcRect.x, calcRect.y, calcRect.width, calcRect.height);
2017
2018     g.setColor(saved);
2019   }
2020
2021   /**
2022    * This method paints the border for an individual tab.
2023    *
2024    * @param g The Graphics object to paint with.
2025    * @param tabPlacement The JTabbedPane's tab placement.
2026    * @param tabIndex The tab index.
2027    * @param x The x position of the tab.
2028    * @param y The y position of the tab.
2029    * @param w The width of the tab.
2030    * @param h The height of the tab.
2031    * @param isSelected Whether the tab is selected.
2032    */
2033   protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex,
2034                                 int x, int y, int w, int h, boolean isSelected)
2035   {
2036     Color saved = g.getColor();
2037
2038     if (! isSelected || tabPlacement != SwingConstants.TOP)
2039       {
2040         g.setColor(shadow);
2041         g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1);
2042         g.setColor(darkShadow);
2043         g.drawLine(x, y + h, x + w, y + h);
2044       }
2045
2046     if (! isSelected || tabPlacement != SwingConstants.LEFT)
2047       {
2048         g.setColor(darkShadow);
2049         g.drawLine(x + w, y, x + w, y + h);
2050         g.setColor(shadow);
2051         g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
2052       }
2053
2054     if (! isSelected || tabPlacement != SwingConstants.RIGHT)
2055       {
2056         g.setColor(lightHighlight);
2057         g.drawLine(x, y, x, y + h);
2058       }
2059
2060     if (! isSelected || tabPlacement != SwingConstants.BOTTOM)
2061       {
2062         g.setColor(lightHighlight);
2063         g.drawLine(x, y, x + w, y);
2064       }
2065
2066     g.setColor(saved);
2067   }
2068
2069   /**
2070    * This method paints the background for an individual tab.
2071    *
2072    * @param g The Graphics object to paint with.
2073    * @param tabPlacement The JTabbedPane's tab placement.
2074    * @param tabIndex The tab index.
2075    * @param x The x position of the tab.
2076    * @param y The y position of the tab.
2077    * @param w The width of the tab.
2078    * @param h The height of the tab.
2079    * @param isSelected Whether the tab is selected.
2080    */
2081   protected void paintTabBackground(Graphics g, int tabPlacement,
2082                                     int tabIndex, int x, int y, int w, int h,
2083                                     boolean isSelected)
2084   {
2085     Color saved = g.getColor();
2086     if (isSelected)
2087       g.setColor(Color.LIGHT_GRAY);
2088     else
2089       {
2090         Color bg = tabPane.getBackgroundAt(tabIndex);
2091         if (bg == null)
2092           bg = Color.GRAY;
2093         g.setColor(bg);
2094       }
2095
2096     g.fillRect(x, y, w, h);
2097
2098     g.setColor(saved);
2099   }
2100
2101   /**
2102    * This method paints the border around the content area.
2103    *
2104    * @param g The Graphics object to paint with.
2105    * @param tabPlacement The JTabbedPane's tab placement.
2106    * @param selectedIndex The index of the selected tab.
2107    */
2108   protected void paintContentBorder(Graphics g, int tabPlacement,
2109                                     int selectedIndex)
2110   {
2111     int x = contentRect.x;
2112     int y = contentRect.y;
2113     int w = contentRect.width;
2114     int h = contentRect.height;
2115     paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2116     paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2117     paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2118     paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
2119   }
2120
2121   /**
2122    * This method paints the top edge of the content border.
2123    *
2124    * @param g The Graphics object to paint with.
2125    * @param tabPlacement The JTabbedPane's tab placement.
2126    * @param selectedIndex The selected tab index.
2127    * @param x The x coordinate for the content area.
2128    * @param y The y coordinate for the content area.
2129    * @param w The width of the content area.
2130    * @param h The height of the content area.
2131    */
2132   protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
2133                                            int selectedIndex, int x, int y,
2134                                            int w, int h)
2135   {
2136     Color saved = g.getColor();
2137     g.setColor(lightHighlight);
2138
2139     int startgap = rects[selectedIndex].x;
2140     int endgap = rects[selectedIndex].x + rects[selectedIndex].width;
2141
2142     int diff = 0;
2143
2144     if (tabPlacement == SwingConstants.TOP)
2145       {
2146         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2147           {
2148             Point p = findPointForIndex(currentScrollLocation);
2149             diff = p.x;
2150           }
2151
2152         g.drawLine(x, y, startgap - diff, y);
2153         g.drawLine(endgap - diff, y, x + w, y);
2154       }
2155     else
2156       g.drawLine(x, y, x + w, y);
2157
2158     g.setColor(saved);
2159   }
2160
2161   /**
2162    * This method paints the left edge of the content border.
2163    *
2164    * @param g The Graphics object to paint with.
2165    * @param tabPlacement The JTabbedPane's tab placement.
2166    * @param selectedIndex The selected tab index.
2167    * @param x The x coordinate for the content area.
2168    * @param y The y coordinate for the content area.
2169    * @param w The width of the content area.
2170    * @param h The height of the content area.
2171    */
2172   protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
2173                                             int selectedIndex, int x, int y,
2174                                             int w, int h)
2175   {
2176     Color saved = g.getColor();
2177     g.setColor(lightHighlight);
2178
2179     int startgap = rects[selectedIndex].y;
2180     int endgap = rects[selectedIndex].y + rects[selectedIndex].height;
2181
2182     int diff = 0;
2183
2184     if (tabPlacement == SwingConstants.LEFT)
2185       {
2186         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2187           {
2188             Point p = findPointForIndex(currentScrollLocation);
2189             diff = p.y;
2190           }
2191
2192         g.drawLine(x, y, x, startgap - diff);
2193         g.drawLine(x, endgap - diff, x, y + h);
2194       }
2195     else
2196       g.drawLine(x, y, x, y + h);
2197
2198     g.setColor(saved);
2199   }
2200
2201   /**
2202    * This method paints the bottom edge of the content border.
2203    *
2204    * @param g The Graphics object to paint with.
2205    * @param tabPlacement The JTabbedPane's tab placement.
2206    * @param selectedIndex The selected tab index.
2207    * @param x The x coordinate for the content area.
2208    * @param y The y coordinate for the content area.
2209    * @param w The width of the content area.
2210    * @param h The height of the content area.
2211    */
2212   protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
2213                                               int selectedIndex, int x, int y,
2214                                               int w, int h)
2215   {
2216     Color saved = g.getColor();
2217
2218     int startgap = rects[selectedIndex].x;
2219     int endgap = rects[selectedIndex].x + rects[selectedIndex].width;
2220
2221     int diff = 0;
2222
2223     if (tabPlacement == SwingConstants.BOTTOM)
2224       {
2225         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2226           {
2227             Point p = findPointForIndex(currentScrollLocation);
2228             diff = p.x;
2229           }
2230
2231         g.setColor(shadow);
2232         g.drawLine(x + 1, y + h - 1, startgap - diff, y + h - 1);
2233         g.drawLine(endgap - diff, y + h - 1, x + w - 1, y + h - 1);
2234
2235         g.setColor(darkShadow);
2236         g.drawLine(x, y + h, startgap - diff, y + h);
2237         g.drawLine(endgap - diff, y + h, x + w, y + h);
2238       }
2239     else
2240       {
2241         g.setColor(shadow);
2242         g.drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1);
2243         g.setColor(darkShadow);
2244         g.drawLine(x, y + h, x + w, y + h);
2245       }
2246
2247     g.setColor(saved);
2248   }
2249
2250   /**
2251    * This method paints the right edge of the content border.
2252    *
2253    * @param g The Graphics object to paint with.
2254    * @param tabPlacement The JTabbedPane's tab placement.
2255    * @param selectedIndex The selected tab index.
2256    * @param x The x coordinate for the content area.
2257    * @param y The y coordinate for the content area.
2258    * @param w The width of the content area.
2259    * @param h The height of the content area.
2260    */
2261   protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
2262                                              int selectedIndex, int x, int y,
2263                                              int w, int h)
2264   {
2265     Color saved = g.getColor();
2266     int startgap = rects[selectedIndex].y;
2267     int endgap = rects[selectedIndex].y + rects[selectedIndex].height;
2268
2269     int diff = 0;
2270
2271     if (tabPlacement == SwingConstants.RIGHT)
2272       {
2273         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT)
2274           {
2275             Point p = findPointForIndex(currentScrollLocation);
2276             diff = p.y;
2277           }
2278
2279         g.setColor(shadow);
2280         g.drawLine(x + w - 1, y + 1, x + w - 1, startgap - diff);
2281         g.drawLine(x + w - 1, endgap - diff, x + w - 1, y + h - 1);
2282
2283         g.setColor(darkShadow);
2284         g.drawLine(x + w, y, x + w, startgap - diff);
2285         g.drawLine(x + w, endgap - diff, x + w, y + h);
2286       }
2287     else
2288       {
2289         g.setColor(shadow);
2290         g.drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
2291         g.setColor(darkShadow);
2292         g.drawLine(x + w, y, x + w, y + h);
2293       }
2294
2295     g.setColor(saved);
2296   }
2297
2298   /**
2299    * This method returns the tab bounds for the given index.
2300    *
2301    * @param pane The JTabbedPane.
2302    * @param i The index to look for.
2303    *
2304    * @return The bounds of the tab with the given index.
2305    */
2306   public Rectangle getTabBounds(JTabbedPane pane, int i)
2307   {
2308     return rects[i];
2309   }
2310
2311   /**
2312    * This method returns the number of runs.
2313    *
2314    * @param pane The JTabbedPane.
2315    *
2316    * @return The number of runs.
2317    */
2318   public int getTabRunCount(JTabbedPane pane)
2319   {
2320     return runCount;
2321   }
2322
2323   /**
2324    * This method returns the tab index given a coordinate.
2325    *
2326    * @param pane The JTabbedPane.
2327    * @param x The x coordinate.
2328    * @param y The y coordinate.
2329    *
2330    * @return The tab index that the coordinate lands in.
2331    */
2332   public int tabForCoordinate(JTabbedPane pane, int x, int y)
2333   {
2334     Point p = new Point(x, y);
2335     int tabCount = tabPane.getTabCount();
2336     int currRun = 1;
2337     for (int i = 0; i < runCount; i++)
2338       {
2339         int first = lastTabInRun(tabCount, getPreviousTabRun(currRun)) + 1;
2340         if (first == tabCount)
2341           first = 0;
2342         int last = lastTabInRun(tabCount, currRun);
2343         for (int j = first; j <= last; j++)
2344           {
2345             if (getTabBounds(pane, j).contains(p))
2346               return j;
2347           }
2348         currRun = getNextTabRun(currRun);
2349       }
2350     return -1;
2351   }
2352
2353   /**
2354    * This method returns the tab bounds in the given rectangle.
2355    *
2356    * @param tabIndex The index to get bounds for.
2357    * @param dest The rectangle to store bounds in.
2358    *
2359    * @return The rectangle passed in.
2360    */
2361   protected Rectangle getTabBounds(int tabIndex, Rectangle dest)
2362   {
2363     dest.setBounds(getTabBounds(tabPane, tabIndex));
2364     return dest;
2365   }
2366
2367   /**
2368    * This method returns the component that is shown in  the content area.
2369    *
2370    * @return The component that is shown in the content area.
2371    */
2372   protected Component getVisibleComponent()
2373   {
2374     return tabPane.getComponentAt(tabPane.getSelectedIndex());
2375   }
2376
2377   /**
2378    * This method sets the visible component.
2379    *
2380    * @param component The component to be set visible.
2381    */
2382   protected void setVisibleComponent(Component component)
2383   {
2384     component.setVisible(true);
2385     tabPane.setSelectedComponent(component);
2386   }
2387
2388   /**
2389    * This method assures that enough rectangles are created given the
2390    * tabCount. The old array is copied to the  new one.
2391    *
2392    * @param tabCount The number of tabs.
2393    */
2394   protected void assureRectsCreated(int tabCount)
2395   {
2396     if (rects.length < tabCount)
2397       {
2398         Rectangle[] old = rects;
2399         rects = new Rectangle[tabCount];
2400         System.arraycopy(old, 0, rects, 0, old.length);
2401         for (int i = old.length; i < rects.length; i++)
2402           rects[i] = new Rectangle();
2403       }
2404   }
2405
2406   /**
2407    * This method expands the tabRuns array to give it more room. The old array
2408    * is copied to the new one.
2409    */
2410   protected void expandTabRunsArray()
2411   {
2412     // This method adds another 10 index positions to the tabRuns array.
2413     if (tabRuns == null)
2414       tabRuns = new int[10];
2415     else
2416       {
2417         int[] newRuns = new int[tabRuns.length + 10];
2418         System.arraycopy(tabRuns, 0, newRuns, 0, tabRuns.length);
2419         tabRuns = newRuns;
2420       }
2421   }
2422
2423   /**
2424    * This method returns which run a particular tab belongs to.
2425    *
2426    * @param tabCount The number of tabs.
2427    * @param tabIndex The tab to find.
2428    *
2429    * @return The tabRuns index that it belongs to.
2430    */
2431   protected int getRunForTab(int tabCount, int tabIndex)
2432   {
2433     if (runCount == 1 && tabIndex < tabCount && tabIndex >= 0)
2434       return 1;
2435     for (int i = 0; i < runCount; i++)
2436       {
2437         int first = lastTabInRun(tabCount, getPreviousTabRun(i)) + 1;
2438         if (first == tabCount)
2439           first = 0;
2440         int last = lastTabInRun(tabCount, i);
2441         if (last >= tabIndex && first <= tabIndex)
2442           return i;
2443       }
2444     return -1;
2445   }
2446
2447   /**
2448    * This method returns the index of the last tab in  a run.
2449    *
2450    * @param tabCount The number of tabs.
2451    * @param run The run to check.
2452    *
2453    * @return The last tab in the given run.
2454    */
2455   protected int lastTabInRun(int tabCount, int run)
2456   {
2457     if (tabRuns[run] == 0)
2458       return tabCount - 1;
2459     else
2460       return tabRuns[run] - 1;
2461   }
2462
2463   /**
2464    * This method returns the tab run overlay.
2465    *
2466    * @param tabPlacement The JTabbedPane's tab placement.
2467    *
2468    * @return The tab run overlay.
2469    */
2470   protected int getTabRunOverlay(int tabPlacement)
2471   {
2472     return tabRunOverlay;
2473   }
2474
2475   /**
2476    * This method returns the tab run indent. It is used in WRAP_TAB_LAYOUT and
2477    * makes each tab run start indented by a certain amount.
2478    *
2479    * @param tabPlacement The JTabbedPane's tab placement.
2480    * @param run The run to get indent for.
2481    *
2482    * @return The amount a run should be indented.
2483    */
2484   protected int getTabRunIndent(int tabPlacement, int run)
2485   {
2486     return 0;
2487   }
2488
2489   /**
2490    * This method returns whether a tab run should be padded.
2491    *
2492    * @param tabPlacement The JTabbedPane's tab placement.
2493    * @param run The run to check.
2494    *
2495    * @return Whether the given run should be padded.
2496    */
2497   protected boolean shouldPadTabRun(int tabPlacement, int run)
2498   {
2499     return true;
2500   }
2501
2502   /**
2503    * This method returns whether the tab runs should be rotated.
2504    *
2505    * @param tabPlacement The JTabbedPane's tab placement.
2506    *
2507    * @return Whether runs should be rotated.
2508    */
2509   protected boolean shouldRotateTabRuns(int tabPlacement)
2510   {
2511     return true;
2512   }
2513
2514   /**
2515    * This method returns an icon for the tab. If the tab is disabled, it
2516    * should return the disabledIcon. If it is enabled, then it should return
2517    * the default icon.
2518    *
2519    * @param tabIndex The tab index to get an icon for.
2520    *
2521    * @return The icon for the tab index.
2522    */
2523   protected Icon getIconForTab(int tabIndex)
2524   {
2525     if (tabPane.isEnabledAt(tabIndex))
2526       return tabPane.getIconAt(tabIndex);
2527     else
2528       return tabPane.getDisabledIconAt(tabIndex);
2529   }
2530
2531   /**
2532    * This method returns a view that can paint the text for the label.
2533    *
2534    * @param tabIndex The tab index to get a view for.
2535    *
2536    * @return The view for the tab index.
2537    */
2538   protected View getTextViewForTab(int tabIndex)
2539   {
2540     return null;
2541   }
2542
2543   /**
2544    * This method returns the tab height, including insets, for the given index
2545    * and fontheight.
2546    *
2547    * @param tabPlacement The JTabbedPane's tab placement.
2548    * @param tabIndex The index of the tab to calculate.
2549    * @param fontHeight The font height.
2550    *
2551    * @return This tab's height.
2552    */
2553   protected int calculateTabHeight(int tabPlacement, int tabIndex,
2554                                    int fontHeight)
2555   {
2556     Icon icon = getIconForTab(tabIndex);
2557     Insets insets = getTabInsets(tabPlacement, tabIndex);
2558
2559     int height = 0;
2560     if (icon != null)
2561       {
2562         Rectangle vr = new Rectangle();
2563         Rectangle ir = new Rectangle();
2564         Rectangle tr = new Rectangle();
2565         layoutLabel(tabPlacement, getFontMetrics(), tabIndex,
2566                     tabPane.getTitleAt(tabIndex), icon, vr, ir, tr,
2567                     tabIndex == tabPane.getSelectedIndex());
2568         height = tr.union(ir).height;
2569       }
2570     else
2571       height = fontHeight;
2572
2573     height += insets.top + insets.bottom;
2574     return height;
2575   }
2576
2577   /**
2578    * This method returns the max tab height.
2579    *
2580    * @param tabPlacement The JTabbedPane's tab placement.
2581    *
2582    * @return The maximum tab height.
2583    */
2584   protected int calculateMaxTabHeight(int tabPlacement)
2585   {
2586     maxTabHeight = 0;
2587
2588     FontMetrics fm = getFontMetrics();
2589     int fontHeight = fm.getHeight();
2590
2591     for (int i = 0; i < tabPane.getTabCount(); i++)
2592       maxTabHeight = Math.max(calculateTabHeight(tabPlacement, i, fontHeight),
2593                               maxTabHeight);
2594
2595     return maxTabHeight;
2596   }
2597
2598   /**
2599    * This method calculates the tab width, including insets, for the given tab
2600    * index and font metrics.
2601    *
2602    * @param tabPlacement The JTabbedPane's tab placement.
2603    * @param tabIndex The tab index to calculate for.
2604    * @param metrics The font's metrics.
2605    *
2606    * @return The tab width for the given index.
2607    */
2608   protected int calculateTabWidth(int tabPlacement, int tabIndex,
2609                                   FontMetrics metrics)
2610   {
2611     Icon icon = getIconForTab(tabIndex);
2612     Insets insets = getTabInsets(tabPlacement, tabIndex);
2613
2614     int width = 0;
2615     if (icon != null)
2616       {
2617         Rectangle vr = new Rectangle();
2618         Rectangle ir = new Rectangle();
2619         Rectangle tr = new Rectangle();
2620         layoutLabel(tabPlacement, getFontMetrics(), tabIndex,
2621                     tabPane.getTitleAt(tabIndex), icon, vr, ir, tr,
2622                     tabIndex == tabPane.getSelectedIndex());
2623         width = tr.union(ir).width;
2624       }
2625     else
2626       width = metrics.stringWidth(tabPane.getTitleAt(tabIndex));
2627
2628     width += insets.left + insets.right;
2629     return width;
2630   }
2631
2632   /**
2633    * This method calculates the max tab width.
2634    *
2635    * @param tabPlacement The JTabbedPane's tab placement.
2636    *
2637    * @return The maximum tab width.
2638    */
2639   protected int calculateMaxTabWidth(int tabPlacement)
2640   {
2641     maxTabWidth = 0;
2642
2643     FontMetrics fm = getFontMetrics();
2644
2645     for (int i = 0; i < tabPane.getTabCount(); i++)
2646       maxTabWidth = Math.max(calculateTabWidth(tabPlacement, i, fm),
2647                              maxTabWidth);
2648
2649     return maxTabWidth;
2650   }
2651
2652   /**
2653    * This method calculates the tab area height, including insets, for the
2654    * given amount of runs and tab height.
2655    *
2656    * @param tabPlacement The JTabbedPane's tab placement.
2657    * @param horizRunCount The number of runs.
2658    * @param maxTabHeight The max tab height.
2659    *
2660    * @return The tab area height.
2661    */
2662   protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount,
2663                                        int maxTabHeight)
2664   {
2665     Insets insets = getTabAreaInsets(tabPlacement);
2666     int tabAreaHeight = horizRunCount * maxTabHeight
2667                         - (horizRunCount - 1) * tabRunOverlay;
2668
2669     tabAreaHeight += insets.top + insets.bottom;
2670
2671     return tabAreaHeight;
2672   }
2673
2674   /**
2675    * This method calculates the tab area width, including insets, for the
2676    * given amount of runs and tab width.
2677    *
2678    * @param tabPlacement The JTabbedPane's tab placement.
2679    * @param vertRunCount The number of runs.
2680    * @param maxTabWidth The max tab width.
2681    *
2682    * @return The tab area width.
2683    */
2684   protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount,
2685                                       int maxTabWidth)
2686   {
2687     Insets insets = getTabAreaInsets(tabPlacement);
2688     int tabAreaWidth = vertRunCount * maxTabWidth
2689                        - (vertRunCount - 1) * tabRunOverlay;
2690
2691     tabAreaWidth += insets.left + insets.right;
2692
2693     return tabAreaWidth;
2694   }
2695
2696   /**
2697    * This method returns the tab insets appropriately rotated.
2698    *
2699    * @param tabPlacement The JTabbedPane's tab placement.
2700    * @param tabIndex The tab index.
2701    *
2702    * @return The tab insets for the given index.
2703    */
2704   protected Insets getTabInsets(int tabPlacement, int tabIndex)
2705   {
2706     Insets target = new Insets(0, 0, 0, 0);
2707     rotateInsets(tabInsets, target, tabPlacement);
2708     return target;
2709   }
2710
2711   /**
2712    * This method returns the selected tab pad insets appropriately rotated.
2713    *
2714    * @param tabPlacement The JTabbedPane's tab placement.
2715    *
2716    * @return The selected tab pad insets.
2717    */
2718   protected Insets getSelectedTabPadInsets(int tabPlacement)
2719   {
2720     Insets target = new Insets(0, 0, 0, 0);
2721     rotateInsets(selectedTabPadInsets, target, tabPlacement);
2722     return target;
2723   }
2724
2725   /**
2726    * This method returns the tab area insets appropriately rotated.
2727    *
2728    * @param tabPlacement The JTabbedPane's tab placement.
2729    *
2730    * @return The tab area insets.
2731    */
2732   protected Insets getTabAreaInsets(int tabPlacement)
2733   {
2734     Insets target = new Insets(0, 0, 0, 0);
2735     rotateInsets(tabAreaInsets, target, tabPlacement);
2736     return target;
2737   }
2738
2739   /**
2740    * This method returns the content border insets appropriately rotated.
2741    *
2742    * @param tabPlacement The JTabbedPane's tab placement.
2743    *
2744    * @return The content border insets.
2745    */
2746   protected Insets getContentBorderInsets(int tabPlacement)
2747   {
2748     Insets target = new Insets(0, 0, 0, 0);
2749     rotateInsets(contentBorderInsets, target, tabPlacement);
2750     return target;
2751   }
2752
2753   /**
2754    * This method returns the fontmetrics for the font of the JTabbedPane.
2755    *
2756    * @return The font metrics for the JTabbedPane.
2757    */
2758   protected FontMetrics getFontMetrics()
2759   {
2760     FontMetrics fm = tabPane.getFontMetrics(tabPane.getFont());
2761     return fm;
2762   }
2763
2764   /**
2765    * This method navigates from the selected tab into the given direction. As
2766    * a result, a new tab will be selected (if possible).
2767    *
2768    * @param direction The direction to navigate in.
2769    */
2770   protected void navigateSelectedTab(int direction)
2771   {
2772     int tabPlacement = tabPane.getTabPlacement();
2773     if (tabPlacement == SwingConstants.TOP
2774         || tabPlacement == SwingConstants.BOTTOM)
2775       {
2776         if (direction == SwingConstants.WEST)
2777           selectPreviousTabInRun(tabPane.getSelectedIndex());
2778         else if (direction == SwingConstants.EAST)
2779           selectNextTabInRun(tabPane.getSelectedIndex());
2780
2781         else
2782           {
2783             int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(),
2784                                          tabPane.getSelectedIndex(),
2785                                          (tabPlacement == SwingConstants.RIGHT)
2786                                          ? true : false);
2787             selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(),
2788                                  offset);
2789           }
2790       }
2791     if (tabPlacement == SwingConstants.LEFT
2792         || tabPlacement == SwingConstants.RIGHT)
2793       {
2794         if (direction == SwingConstants.NORTH)
2795           selectPreviousTabInRun(tabPane.getSelectedIndex());
2796         else if (direction == SwingConstants.SOUTH)
2797           selectNextTabInRun(tabPane.getSelectedIndex());
2798         else
2799           {
2800             int offset = getTabRunOffset(tabPlacement, tabPane.getTabCount(),
2801                                          tabPane.getSelectedIndex(),
2802                                          (tabPlacement == SwingConstants.RIGHT)
2803                                          ? true : false);
2804             selectAdjacentRunTab(tabPlacement, tabPane.getSelectedIndex(),
2805                                  offset);
2806           }
2807       }
2808   }
2809
2810   /**
2811    * This method selects the next tab in the run.
2812    *
2813    * @param current The current selected index.
2814    */
2815   protected void selectNextTabInRun(int current)
2816   {
2817     tabPane.setSelectedIndex(getNextTabIndexInRun(tabPane.getTabCount(),
2818                                                   current));
2819   }
2820
2821   /**
2822    * This method selects the previous tab in the run.
2823    *
2824    * @param current The current selected index.
2825    */
2826   protected void selectPreviousTabInRun(int current)
2827   {
2828     tabPane.setSelectedIndex(getPreviousTabIndexInRun(tabPane.getTabCount(),
2829                                                       current));
2830   }
2831
2832   /**
2833    * This method selects the next tab (regardless of runs).
2834    *
2835    * @param current The current selected index.
2836    */
2837   protected void selectNextTab(int current)
2838   {
2839     tabPane.setSelectedIndex(getNextTabIndex(current));
2840   }
2841
2842   /**
2843    * This method selects the previous tab (regardless of runs).
2844    *
2845    * @param current The current selected index.
2846    */
2847   protected void selectPreviousTab(int current)
2848   {
2849     tabPane.setSelectedIndex(getPreviousTabIndex(current));
2850   }
2851
2852   /**
2853    * This method selects the correct tab given an offset from the current tab
2854    * index. If the tab placement is TOP or BOTTOM, the offset will be in the
2855    * y direction, otherwise, it will be in the x direction. A new coordinate
2856    * will be found by adding the offset to the current location of the tab.
2857    * The tab that the new location will be selected.
2858    *
2859    * @param tabPlacement The JTabbedPane's tab placement.
2860    * @param tabIndex The tab to start from.
2861    * @param offset The coordinate offset.
2862    */
2863   protected void selectAdjacentRunTab(int tabPlacement, int tabIndex,
2864                                       int offset)
2865   {
2866     int x = rects[tabIndex].x + rects[tabIndex].width / 2;
2867     int y = rects[tabIndex].y + rects[tabIndex].height / 2;
2868
2869     switch (tabPlacement)
2870     {
2871     case SwingConstants.TOP:
2872     case SwingConstants.BOTTOM:
2873       y += offset;
2874       break;
2875     case SwingConstants.RIGHT:
2876     case SwingConstants.LEFT:
2877       x += offset;
2878       break;
2879     }
2880
2881     int index = tabForCoordinate(tabPane, x, y);
2882     if (index != -1)
2883       tabPane.setSelectedIndex(index);
2884   }
2885
2886   // This method is called when you press up/down to cycle through tab runs.
2887   // it returns the distance (between the two runs' x/y position.
2888   // where one run is the current selected run and the other run is the run in the
2889   // direction of the scroll (dictated by the forward flag)
2890   // the offset is an absolute value of the difference
2891
2892   /**
2893    * This method calculates the offset distance for use in
2894    * selectAdjacentRunTab. The offset returned will be a difference in the y
2895    * coordinate between the run in  the desired direction and the current run
2896    * (for tabPlacement in TOP or BOTTOM). Use x coordinate for LEFT and
2897    * RIGHT.
2898    *
2899    * @param tabPlacement The JTabbedPane's tab placement.
2900    * @param tabCount The number of tabs.
2901    * @param tabIndex The starting index.
2902    * @param forward If forward, the run in the desired direction will be the
2903    *        next run.
2904    *
2905    * @return The offset between the two runs.
2906    */
2907   protected int getTabRunOffset(int tabPlacement, int tabCount, int tabIndex,
2908                                 boolean forward)
2909   {
2910     int currRun = getRunForTab(tabCount, tabIndex);
2911     int offset;
2912     int nextRun = (forward) ? getNextTabRun(currRun) : getPreviousTabRun(currRun);
2913     if (tabPlacement == SwingConstants.TOP
2914         || tabPlacement == SwingConstants.BOTTOM)
2915       offset = rects[lastTabInRun(tabCount, nextRun)].y
2916                - rects[lastTabInRun(tabCount, currRun)].y;
2917     else
2918       offset = rects[lastTabInRun(tabCount, nextRun)].x
2919                - rects[lastTabInRun(tabCount, currRun)].x;
2920     return offset;
2921   }
2922
2923   /**
2924    * This method returns the previous tab index.
2925    *
2926    * @param base The index to start from.
2927    *
2928    * @return The previous tab index.
2929    */
2930   protected int getPreviousTabIndex(int base)
2931   {
2932     base--;
2933     if (base < 0)
2934       return tabPane.getTabCount() - 1;
2935     return base;
2936   }
2937
2938   /**
2939    * This method returns the next tab index.
2940    *
2941    * @param base The index to start from.
2942    *
2943    * @return The next tab index.
2944    */
2945   protected int getNextTabIndex(int base)
2946   {
2947     base++;
2948     if (base == tabPane.getTabCount())
2949       return 0;
2950     return base;
2951   }
2952
2953   /**
2954    * This method returns the next tab index in the run. If the next index is
2955    * out of this run, it will return the starting tab index for the run.
2956    *
2957    * @param tabCount The number of tabs.
2958    * @param base The index to start from.
2959    *
2960    * @return The next tab index in the run.
2961    */
2962   protected int getNextTabIndexInRun(int tabCount, int base)
2963   {
2964     int index = getNextTabIndex(base);
2965     int run = getRunForTab(tabCount, base);
2966     if (index == lastTabInRun(tabCount, run) + 1)
2967       index = lastTabInRun(tabCount, getPreviousTabRun(run)) + 1;
2968     return getNextTabIndex(base);
2969   }
2970
2971   /**
2972    * This method returns the previous tab index in the run. If the previous
2973    * index is out of this run, it will return the last index for the run.
2974    *
2975    * @param tabCount The number of tabs.
2976    * @param base The index to start from.
2977    *
2978    * @return The previous tab index in the run.
2979    */
2980   protected int getPreviousTabIndexInRun(int tabCount, int base)
2981   {
2982     int index = getPreviousTabIndex(base);
2983     int run = getRunForTab(tabCount, base);
2984     if (index == lastTabInRun(tabCount, getPreviousTabRun(run)))
2985       index = lastTabInRun(tabCount, run);
2986     return getPreviousTabIndex(base);
2987   }
2988
2989   /**
2990    * This method returns the index of the previous run.
2991    *
2992    * @param baseRun The run to start from.
2993    *
2994    * @return The index of the previous run.
2995    */
2996   protected int getPreviousTabRun(int baseRun)
2997   {
2998     if (getTabRunCount(tabPane) == 1)
2999       return 1;
3000
3001     int prevRun = --baseRun;
3002     if (prevRun < 0)
3003       prevRun = getTabRunCount(tabPane) - 1;
3004     return prevRun;
3005   }
3006
3007   /**
3008    * This method returns the index of the next run.
3009    *
3010    * @param baseRun The run to start from.
3011    *
3012    * @return The index of the next run.
3013    */
3014   protected int getNextTabRun(int baseRun)
3015   {
3016     if (getTabRunCount(tabPane) == 1)
3017       return 1;
3018
3019     int nextRun = ++baseRun;
3020     if (nextRun == getTabRunCount(tabPane))
3021       nextRun = 0;
3022     return nextRun;
3023   }
3024
3025   /**
3026    * This method rotates the insets given a direction to rotate them in.
3027    * Target placement should be one of TOP, LEFT, BOTTOM, RIGHT. The  rotated
3028    * insets will be stored in targetInsets. Passing in TOP as  the direction
3029    * does nothing. Passing in LEFT switches top and left, right and bottom.
3030    * Passing in BOTTOM switches top and bottom. Passing in RIGHT switches top
3031    * for left, left for bottom, bottom for right, and right for top.
3032    *
3033    * @param topInsets The reference insets.
3034    * @param targetInsets An Insets object to store the new insets.
3035    * @param targetPlacement The rotation direction.
3036    */
3037   protected static void rotateInsets(Insets topInsets, Insets targetInsets,
3038                                      int targetPlacement)
3039   {
3040     // Sun's version will happily throw an NPE if params are null,
3041     // so I won't check it either.
3042     switch (targetPlacement)
3043     {
3044     case SwingConstants.TOP:
3045       targetInsets.top = topInsets.top;
3046       targetInsets.left = topInsets.left;
3047       targetInsets.right = topInsets.right;
3048       targetInsets.bottom = topInsets.bottom;
3049       break;
3050     case SwingConstants.LEFT:
3051       targetInsets.left = topInsets.top;
3052       targetInsets.top = topInsets.left;
3053       targetInsets.right = topInsets.bottom;
3054       targetInsets.bottom = topInsets.right;
3055       break;
3056     case SwingConstants.BOTTOM:
3057       targetInsets.top = topInsets.bottom;
3058       targetInsets.bottom = topInsets.top;
3059       targetInsets.left = topInsets.left;
3060       targetInsets.right = topInsets.right;
3061       break;
3062     case SwingConstants.RIGHT:
3063       targetInsets.top = topInsets.left;
3064       targetInsets.left = topInsets.bottom;
3065       targetInsets.bottom = topInsets.right;
3066       targetInsets.right = topInsets.top;
3067       break;
3068     }
3069   }
3070 }