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