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