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