OSDN Git Service

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