001/* MetalTabbedPaneUI.java
002   Copyright (C) 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.metal;
040
041import java.awt.Color;
042import java.awt.Graphics;
043import java.awt.LayoutManager;
044import java.awt.Rectangle;
045
046import javax.swing.JComponent;
047import javax.swing.JTabbedPane;
048import javax.swing.UIManager;
049import javax.swing.plaf.ComponentUI;
050import javax.swing.plaf.UIResource;
051import javax.swing.plaf.basic.BasicTabbedPaneUI;
052
053/**
054 * A UI delegate for the {@link JTabbedPane} component.
055 */
056public class MetalTabbedPaneUI extends BasicTabbedPaneUI
057{
058
059  /**
060   * A {@link LayoutManager} responsible for placing all the tabs and the 
061   * visible component inside the {@link JTabbedPane}. This class is only used 
062   * for {@link JTabbedPane#WRAP_TAB_LAYOUT}.
063   *
064   * @specnote Apparently this class was intended to be protected,
065   *           but was made public by a compiler bug and is now
066   *           public for compatibility.
067   */
068  public class TabbedPaneLayout 
069    extends BasicTabbedPaneUI.TabbedPaneLayout
070  {
071    /**
072     * Creates a new instance of the layout manager.
073     */
074    public TabbedPaneLayout()
075    {
076      // Nothing to do here.
077    }
078    
079    /**
080     * Overridden to do nothing, because tab runs are not rotated in the 
081     * {@link MetalLookAndFeel}.
082     * 
083     * @param tabPlacement  the tab placement (one of {@link #TOP}, 
084     *        {@link #BOTTOM}, {@link #LEFT} or {@link #RIGHT}).
085     * @param selectedRun  the index of the selected run.
086     */
087    protected void rotateTabRuns(int tabPlacement, int selectedRun)
088    {
089      // do nothing, because tab runs are not rotated in the MetalLookAndFeel
090    }
091    
092    /**
093     * Overridden to do nothing, because the selected tab does not have extra
094     * padding in the {@link MetalLookAndFeel}.
095     * 
096     * @param tabPlacement  the tab placement (one of {@link #TOP}, 
097     *        {@link #BOTTOM}, {@link #LEFT} or {@link #RIGHT}).
098     * @param selectedIndex  the index of the selected tab.
099     */
100    protected void padSelectedTab(int tabPlacement, int selectedIndex)
101    {
102      // do nothing, because the selected tab does not have extra padding in 
103      // the MetalLookAndFeel
104    }
105
106    /**
107     * Overridden because tab runs are only normalized for TOP and BOTTOM
108     * tab placement in the Metal L&F.
109     */
110    protected void normalizeTabRuns(int tabPlacement, int tabCount, int start,
111                                    int max)
112    {
113      if (tabPlacement == TOP || tabPlacement == BOTTOM)
114        super.normalizeTabRuns(tabPlacement, tabCount, start, max);
115    }
116  }
117
118  /**
119   * The minimum tab width.
120   */
121  protected int minTabWidth;
122
123  /**
124   * The color for the selected tab.
125   */
126  protected Color selectColor;
127
128  /**
129   * The color for a highlighted selected tab.
130   */
131  protected Color selectHighlight;
132
133  /**
134   * The background color used for the tab area.
135   */
136  protected Color tabAreaBackground;
137
138  /** The graphics to draw the highlight below the tab. */
139  private Graphics hg;
140
141  /**
142   * Indicates if the tabs are having their background filled.
143   */
144  private boolean tabsOpaque;
145
146  /**
147   * Constructs a new instance of MetalTabbedPaneUI.
148   */
149  public MetalTabbedPaneUI()
150  {
151    super();
152  }
153
154  /**
155   * Returns an instance of MetalTabbedPaneUI.
156   *
157   * @param component the component for which we return an UI instance
158   *
159   * @return an instance of MetalTabbedPaneUI
160   */
161  public static ComponentUI createUI(JComponent component)
162  {
163    return new MetalTabbedPaneUI();
164  }
165  
166  /**
167   * Creates and returns an instance of {@link TabbedPaneLayout}.
168   * 
169   * @return A layout manager used by this UI delegate.
170   */
171  protected LayoutManager createLayoutManager()
172  {
173    return (tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT)
174           ? new MetalTabbedPaneUI.TabbedPaneLayout()
175           : super.createLayoutManager();
176  }
177  
178  /**
179   * Paints the border for a single tab.
180   * 
181   * @param g  the graphics device.
182   * @param tabPlacement  the tab placement ({@link #TOP}, {@link #LEFT}, 
183   *        {@link #BOTTOM} or {@link #RIGHT}).
184   * @param tabIndex  the index of the tab to draw the border for.
185   * @param x  the x-coordinate for the tab's bounding rectangle.
186   * @param y  the y-coordinate for the tab's bounding rectangle.
187   * @param w  the width for the tab's bounding rectangle.
188   * @param h  the height for the tab's bounding rectangle.
189   * @param isSelected  indicates whether or not the tab is selected.
190   */
191  protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, 
192          int x, int y, int w, int h, boolean isSelected) 
193  {
194    int bottom = y + h - 1;
195    int right = x + w - 1;
196
197    switch (tabPlacement)
198    {
199    case LEFT: 
200      paintLeftTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
201      break;
202    case BOTTOM:
203      paintBottomTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
204      break;
205    case  RIGHT:
206      paintRightTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
207      break;
208    case TOP:
209    default: 
210      paintTopTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
211    }
212  }
213
214  /**
215   * Paints the border for a tab assuming that the tab position is at the top
216   * ({@link #TOP}).
217   * 
218   * @param tabIndex  the tab index.
219   * @param g  the graphics device.
220   * @param x  the x-coordinate for the tab's bounding rectangle.
221   * @param y  the y-coordinate for the tab's bounding rectangle.
222   * @param w  the width for the tab's bounding rectangle.
223   * @param h  the height for the tab's bounding rectangle.
224   * @param btm  the y coordinate of the bottom border
225   * @param rght the x coordinate of the right border
226   * @param isSelected  indicates whether the tab is selected.
227   */
228  protected void paintTopTabBorder(int tabIndex, Graphics g, int x, int y,
229      int w, int h, int btm, int rght, boolean isSelected)
230  {
231    int tabCount = tabPane.getTabCount();
232    int currentRun = getRunForTab(tabCount, tabIndex);
233    int right = w - 1;
234    int bottom = h - 1;
235
236    // Paint gap.
237    if (shouldFillGap(currentRun, tabIndex, x, y))
238      {
239        g.translate(x, y);
240        g.setColor(getColorForGap(currentRun, x, y + 1));
241        g.fillRect(1, 0, 5, 3);
242        g.fillRect(1, 3, 2, 2);
243        g.translate(-x, -y);
244      }
245
246    g.translate(x, y);
247
248    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
249    Color oceanSelectedBorder =
250      UIManager.getColor("TabbedPane.borderHightlightColor");
251    if (isOcean && isSelected)
252      g.setColor(oceanSelectedBorder);
253    else
254      g.setColor(darkShadow);
255
256    // Slant
257    g.drawLine(1, 5, 6, 0);
258    // Top.
259    g.drawLine(6, 0, right, 0);
260    // Right.
261    int lastIndex = lastTabInRun(tabCount, currentRun);
262    if (tabIndex == lastIndex)
263      g.drawLine(right, 1, right, bottom);
264    // Left.
265    int selectedIndex = tabPane.getSelectedIndex();
266    if (isOcean && tabIndex - 1 == selectedIndex
267        && currentRun == getRunForTab(tabCount, selectedIndex))
268      {
269        g.setColor(oceanSelectedBorder);
270      }
271    if (tabIndex != tabRuns[runCount - 1])
272      {
273        if (isOcean && isSelected)
274          {
275            g.drawLine(0, 6, 0, bottom);
276            g.setColor(darkShadow);
277            g.drawLine(0, 0, 0, 5);
278          }
279        else
280          {
281            g.drawLine(0, 0, 0, bottom);
282          }
283      }
284    else
285      {
286        g.drawLine(0, 6, 0, bottom);
287      }
288
289    // Paint the highlight.
290    g.setColor(isSelected ? selectHighlight : highlight);
291    // Slant.
292    g.drawLine(1, 6, 6, 1);
293    // Top.
294    g.drawLine(6, 1, right, 1);
295    // Left.
296    g.drawLine(1, 6, 1, bottom);
297    int firstIndex = tabRuns[currentRun];
298    if (tabIndex == firstIndex && tabIndex != tabRuns[runCount - 1])
299      {
300        if (tabPane.getSelectedIndex() == tabRuns[currentRun + 1])
301          g.setColor(selectHighlight);
302        else
303          g.setColor(highlight);
304        g.drawLine(1, 0, 1, 4);
305      }
306
307    g.translate(-x, -y);
308  }
309  
310  /**
311   * Paints the border for a tab assuming that the tab position is at the left
312   * ({@link #LEFT}).
313   * 
314   * @param tabIndex  the tab index.
315   * @param g  the graphics device.
316   * @param x  the x-coordinate for the tab's bounding rectangle.
317   * @param y  the y-coordinate for the tab's bounding rectangle.
318   * @param w  the width for the tab's bounding rectangle.
319   * @param h  the height for the tab's bounding rectangle.
320   * @param btm  ???
321   * @param rght  ???
322   * @param isSelected  indicates whether the tab is selected.
323   */
324  protected void paintLeftTabBorder(int tabIndex, Graphics g, int x, int y,
325      int w, int h, int btm, int rght, boolean isSelected)
326  {
327    g.translate(x, y);
328    int bottom = h - 1;
329    int right = w - 1;
330
331    int tabCount = tabPane.getTabCount();
332    int currentRun = getRunForTab(tabCount, tabIndex);
333    int firstIndex = tabRuns[currentRun];
334
335    // Paint the part of the above tab.
336    if (tabIndex != firstIndex && tabIndex > 0 && tabsOpaque)
337      {
338        Color c;
339        if (tabPane.getSelectedIndex() == tabIndex - 1)
340          c = selectColor;
341        else
342          c = getUnselectedBackground(tabIndex - 1);
343        g.setColor(c);
344        g.fillRect(2, 0, 4, 3);
345        g.drawLine(2, 3, 2, 3);
346      }
347
348    // Paint the highlight.
349    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
350    if (isOcean)
351      {
352        g.setColor(isSelected ? selectHighlight : MetalLookAndFeel.getWhite());
353      }
354    else
355      {
356        g.setColor(isSelected ? selectHighlight : highlight);
357      }
358    // Slant.
359    g.drawLine(1, 6, 6, 1);
360    // Left.
361    g.drawLine(1, 6, 1, bottom);
362    // Top.
363    g.drawLine(6, 1, right, 1);
364    if (tabIndex != firstIndex)
365      {
366        if (isOcean)
367          {
368            g.setColor(MetalLookAndFeel.getWhite());
369          }
370        g.drawLine(1, 0, 1, 4);
371      }
372
373    // Paint border.
374    Color oceanSelectedBorder =
375      UIManager.getColor("TabbedPane.borderHightlightColor");
376    if (isOcean && isSelected)
377      {
378        g.setColor(oceanSelectedBorder);
379      }
380    else
381      {
382        g.setColor(darkShadow);
383      }
384
385    // Slant.
386    g.drawLine(1, 5, 6, 0);
387    // Top.
388    g.drawLine(6, 0, right, 0);
389    // Bottom.
390    int lastIndex = lastTabInRun(tabCount, currentRun);
391    if (tabIndex == lastIndex)
392      {
393        g.drawLine(0, bottom, right, bottom);
394      }
395    // Left.
396    if (isOcean)
397      {
398        if (tabPane.getSelectedIndex() == tabIndex - 1)
399          {
400            g.drawLine(0, 6, 0, bottom);
401            if (tabIndex != firstIndex)
402              {
403                g.setColor(oceanSelectedBorder);
404                g.drawLine(0, 0, 0, 5);
405              }
406          }
407        else if (isSelected)
408          {
409            g.drawLine(0, 5, 0, bottom);
410            if (tabIndex != firstIndex)
411              {
412                g.setColor(darkShadow);
413                g.drawLine(0, 0, 0, 5);
414              }
415          }
416        else if (tabIndex != firstIndex)
417          {
418            g.drawLine(0, 0, 0, bottom);
419          }
420        else
421          {
422            g.drawLine(0, 6, 0, bottom);
423          }
424      }
425    else
426      {
427        if (tabIndex != firstIndex)
428          {
429            g.drawLine(0, 0, 0, bottom);
430          }
431        else
432          {
433            g.drawLine(0, 6, 0, bottom);
434          }
435      }
436
437    g.translate(-x, -y);
438  }
439  
440  /**
441   * Paints the border for a tab assuming that the tab position is at the right
442   * ({@link #RIGHT}).
443   * 
444   * @param tabIndex  the tab index.
445   * @param g  the graphics device.
446   * @param x  the x-coordinate for the tab's bounding rectangle.
447   * @param y  the y-coordinate for the tab's bounding rectangle.
448   * @param w  the width for the tab's bounding rectangle.
449   * @param h  the height for the tab's bounding rectangle.
450   * @param btm  ???
451   * @param rght  ???
452   * @param isSelected  indicates whether the tab is selected.
453   */
454  protected void paintRightTabBorder(int tabIndex, Graphics g, int x, int y,
455      int w, int h, int btm, int rght, boolean isSelected)
456  {
457    g.translate(x, y);
458    int bottom = h - 1;
459    int right = w - 1;
460
461    int tabCount = tabPane.getTabCount();
462    int currentRun = getRunForTab(tabCount, tabIndex);
463    int firstIndex = tabRuns[currentRun];
464
465    // Paint part of the above tab.
466    if (tabIndex != firstIndex && tabIndex > 0 && tabsOpaque)
467      {
468        Color c;
469        if (tabPane.getSelectedIndex() == tabIndex - 1)
470          c = selectColor;
471        else
472          c = getUnselectedBackground(tabIndex - 1);
473        g.setColor(c);
474        g.fillRect(right - 5, 0, 5, 3);
475        g.fillRect(right - 2, 3, 2, 2);
476      }
477
478    // Paint highlight.
479    g.setColor(isSelected ? selectHighlight : highlight);
480
481    // Slant.
482    g.drawLine(right - 6, 1, right - 1, 6);
483    // Top.
484    g.drawLine(0, 1, right - 6, 1);
485    // Left.
486    if (! isSelected)
487      {
488        g.drawLine(0, 1, 0, bottom);
489      }
490
491    // Paint border.
492    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
493    Color oceanSelectedBorder =
494      UIManager.getColor("TabbedPane.borderHightlightColor");
495    if (isOcean && isSelected)
496      {
497        g.setColor(oceanSelectedBorder);
498      }
499    else
500      {
501        g.setColor(darkShadow);
502      }
503
504    // Bottom.
505    int lastIndex = lastTabInRun(tabCount, currentRun);
506    if (tabIndex == lastIndex)
507      {
508        g.drawLine(0, bottom, right, bottom);
509      }
510    // Slant.
511    if (isOcean && tabPane.getSelectedIndex() == tabIndex - 1)
512      {
513        g.setColor(oceanSelectedBorder);
514      }
515    g.drawLine(right - 6, 0, right, 6);
516    // Top.
517    g.drawLine(0, 0, right - 6, 0);
518    // Right.
519    if (isOcean && isSelected)
520      {
521        g.drawLine(right, 6, right, bottom);
522        if (tabIndex != firstIndex)
523          {
524            g.setColor(darkShadow);
525            g.drawLine(right, 0, right, 5);
526          }
527      }
528    else if (isOcean && tabPane.getSelectedIndex() == tabIndex - 1)
529      {
530        if (tabIndex != firstIndex)
531          {
532            g.setColor(oceanSelectedBorder);
533            g.drawLine(right, 0, right, 6);
534          }
535        g.setColor(darkShadow);
536        g.drawLine(right, 7, right, bottom);
537      }
538    else if (tabIndex != firstIndex)
539      {
540        g.drawLine(right, 0, right, bottom);
541      }
542    else
543      {
544        g.drawLine(right, 6, right, bottom);
545      }
546    g.translate(-x, -y);
547  }
548  
549  /**
550   * Paints the border for a tab assuming that the tab position is at the bottom
551   * ({@link #BOTTOM}).
552   * 
553   * @param tabIndex  the tab index.
554   * @param g  the graphics device.
555   * @param x  the x-coordinate for the tab's bounding rectangle.
556   * @param y  the y-coordinate for the tab's bounding rectangle.
557   * @param w  the width for the tab's bounding rectangle.
558   * @param h  the height for the tab's bounding rectangle.
559   * @param btm  ???
560   * @param rght  ???
561   * @param isSelected  indicates whether the tab is selected.
562   */
563  protected void paintBottomTabBorder(int tabIndex, Graphics g, int x, int y,
564      int w, int h, int btm, int rght, boolean isSelected)
565  {
566    int bottom = h - 1;
567    int right = w - 1;
568
569    int tabCount = tabPane.getTabCount();
570    int currentRun = getRunForTab(tabCount, tabIndex);
571    // Paint gap if necessary.
572    if (shouldFillGap(currentRun, tabIndex, x, y))
573      {
574        g.translate(x, y);
575        g.setColor(getColorForGap(currentRun, x, y));
576        g.fillRect(1, bottom - 4, 3, 5);
577        g.fillRect(4, bottom - 1, 2, 2);
578        g.translate(-x, -y);
579      }
580
581    g.translate(x, y);
582
583    // Paint border.
584    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
585    Color oceanSelectedBorder =
586      UIManager.getColor("TabbedPane.borderHightlightColor");
587    if (isOcean && isSelected)
588      {
589        g.setColor(oceanSelectedBorder);
590      }
591    else
592      {
593        g.setColor(darkShadow);
594      }
595    // Slant.
596    g.drawLine(1, bottom - 5, 6, bottom);
597    // Bottom.
598    g.drawLine(6, bottom, right, bottom);
599    // Right.
600    int lastIndex = lastTabInRun(tabCount, currentRun);
601    if (tabIndex == lastIndex)
602      {
603        g.drawLine(right, 0, right, bottom);
604      }
605    // Left.
606    if (isOcean && isSelected)
607      {
608        g.drawLine(0, 0, 0, bottom - 5);
609        
610        // Paint a connecting line to the tab below for all
611        // but the first tab in the last run.
612        if (tabIndex != tabRuns[runCount-1])
613          {
614            g.setColor(darkShadow);
615            g.drawLine(0, bottom - 5, 0, bottom);
616          }
617      }
618    else
619      {
620        if (isOcean && tabIndex == tabPane.getSelectedIndex() + 1)
621          {
622            g.setColor(oceanSelectedBorder);
623          }
624        if (tabIndex != tabRuns[runCount - 1])
625          {
626            g.drawLine(0, 0, 0, bottom);
627          }
628        else
629          {
630            g.drawLine(0, 0, 0, bottom - 6);
631          }
632      }
633
634    // Paint highlight.
635    g.setColor(isSelected ? selectHighlight : highlight);
636    // Slant.
637    g.drawLine(1, bottom - 6, 6, bottom - 1);
638    // Left.
639    g.drawLine(1, 0, 1, bottom - 6);
640
641    int firstIndex = tabRuns[currentRun];
642    if (tabIndex == firstIndex && tabIndex != tabRuns[runCount - 1])
643      {
644        if (tabPane.getSelectedIndex() == tabRuns[currentRun + 1])
645          {
646            g.setColor(selectHighlight);
647          }
648        else
649          {
650            g.setColor(highlight);
651          }
652        g.drawLine(1, bottom - 4, 1, bottom);
653      }
654
655    g.translate(-x, -y);
656  }
657
658  /**
659   * Paints the background for a tab.
660   * 
661   * @param g  the graphics device.
662   * @param tabPlacement  the tab placement ({@link #TOP}, {@link #LEFT}, 
663   *        {@link #BOTTOM} or {@link #RIGHT}).
664   * @param tabIndex  the index of the tab to draw the border for.
665   * @param x  the x-coordinate for the tab's bounding rectangle.
666   * @param y  the y-coordinate for the tab's bounding rectangle.
667   * @param w  the width for the tab's bounding rectangle.
668   * @param h  the height for the tab's bounding rectangle.
669   * @param isSelected  indicates whether or not the tab is selected.
670   */
671  protected void paintTabBackground(Graphics g, int tabPlacement,
672      int tabIndex, int x, int y, int w, int h, boolean isSelected)
673  {
674    if (isSelected)
675      g.setColor(selectColor);
676    else
677      g.setColor(getUnselectedBackground(tabIndex));
678
679    switch (tabPlacement)
680    {
681      case LEFT:
682        g.fillRect(x + 5, y + 1, w - 5, h - 1);
683        g.fillRect(x + 2, y + 4, 3, h - 4);
684        break;
685      case BOTTOM:
686        g.fillRect(x + 2, y, w - 2, h - 3);
687        g.fillRect(x + 5, y + h - 4, w - 5, 3);
688        break;
689      case RIGHT:
690        g.fillRect(x, y + 1, w - 4, h - 1);
691        g.fillRect(x + w - 4, y + 5, 3, h - 5);
692        break;
693      case TOP:
694      default:
695        g.fillRect(x + 4, y + 2, w - 4, h - 2);
696        g.fillRect(x + 2, y + 5, 2, h - 5);
697    }
698  }
699  
700  /**
701   * This method paints the focus rectangle around the selected tab.
702   *
703   * @param g The Graphics object to paint with.
704   * @param tabPlacement The JTabbedPane's tab placement.
705   * @param rects The array of rectangles keeping track of size and position.
706   * @param tabIndex The tab index.
707   * @param iconRect The icon bounds.
708   * @param textRect The text bounds.
709   * @param isSelected Whether this tab is selected.
710   */
711  protected void paintFocusIndicator(Graphics g, int tabPlacement,
712                                     Rectangle[] rects, int tabIndex,
713                                     Rectangle iconRect, Rectangle textRect,
714                                     boolean isSelected)
715  {
716    if (tabPane.hasFocus() && isSelected)
717      {
718        Rectangle rect = rects[tabIndex];
719
720        g.setColor(focus);
721        g.translate(rect.x, rect.y);
722        
723        switch (tabPlacement)
724          {
725          case LEFT:
726            // Top line
727            g.drawLine(7, 2, rect.width-2, 2);
728            
729            // Right line
730            g.drawLine(rect.width-1, 2, rect.width-1, rect.height-3);
731            
732            // Bottom line
733            g.drawLine(rect.width-2, rect.height-2, 3, rect.height-2);
734            
735            // Left line
736            g.drawLine(2, rect.height-3, 2, 7);
737
738            // Slant
739            g.drawLine(2, 6, 6, 2);
740            break;
741          case RIGHT:
742            // Top line
743            g.drawLine(1, 2, rect.width-8, 2);
744            
745            // Slant
746            g.drawLine(rect.width-7, 2, rect.width-3, 6);
747            
748            // Right line
749            g.drawLine(rect.width-3, 7, rect.width-3, rect.height-3);
750            
751            // Bottom line
752            g.drawLine(rect.width-3, rect.height-2, 2, rect.height-2);
753
754            // Left line
755            g.drawLine(1, rect.height-2, 1, 2);
756            break;
757          case BOTTOM:
758            // Top line
759            g.drawLine(2, 1, rect.width-2, 1);
760            
761            // Right line
762            g.drawLine(rect.width-1, 2, rect.width-1, rect.height-3);
763            
764            // Bottom line
765            g.drawLine(7, rect.height-3, rect.width-2, rect.height-3);
766            
767            // Slant
768            g.drawLine(6, rect.height-3, 2, rect.height-7);
769            
770            // Left line
771            g.drawLine(2, rect.height-8, 2, 2);
772            
773            break;
774          case TOP:
775          default:
776            // Top line
777            g.drawLine(6, 2, rect.width-2, 2);
778            
779            // Right line
780            g.drawLine(rect.width-1, 2, rect.width-1, rect.height-3);
781            
782            // Bottom line
783            g.drawLine(3, rect.height-3, rect.width-2, rect.height-3);
784            
785            // Left line
786            g.drawLine(2, rect.height-3, 2, 7);
787            
788            // Slant
789            g.drawLine(2, 6, 6, 2);
790            
791          }
792        
793        g.translate(-rect.x, -rect.y);
794      }
795  }
796  
797  /**
798   * Returns <code>true</code> if the tabs in the specified run should be 
799   * padded to make the run fill the width/height of the {@link JTabbedPane}.
800   * 
801   * @param tabPlacement  the tab placement for the {@link JTabbedPane} (one of
802   *        {@link #TOP}, {@link #BOTTOM}, {@link #LEFT} and {@link #RIGHT}).
803   * @param run  the run index.
804   * 
805   * @return A boolean.
806   */
807  protected boolean shouldPadTabRun(int tabPlacement, int run)
808  {
809    // as far as I can tell, all runs should be padded except the last run
810    // (which is drawn at the very top for tabPlacement == TOP)
811    return run < this.runCount - 1;
812  }
813
814  /**
815   * Installs the defaults for this UI. This method calls super.installDefaults
816   * and then loads the Metal specific defaults for TabbedPane.
817   */
818  protected void installDefaults()
819  {
820    super.installDefaults();
821    selectColor = UIManager.getColor("TabbedPane.selected");
822    selectHighlight = UIManager.getColor("TabbedPane.selectHighlight");
823    tabAreaBackground = UIManager.getColor("TabbedPane.tabAreaBackground");
824    tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
825    minTabWidth = 0;
826  }
827  
828  /**
829   * Returns the color for the gap.
830   * 
831   * @param currentRun - The current run to return the color for
832   * @param x - The x position of the current run
833   * @param y - The y position of the current run
834   * 
835   * @return the color for the gap in the current run.
836   */
837  protected Color getColorForGap(int currentRun, int x, int y)
838  {
839    int index = tabForCoordinate(tabPane, x, y);
840    int selected = tabPane.getSelectedIndex();
841    if (selected == index)
842      return selectColor;
843    return tabAreaBackground;
844  }
845  
846  /**
847   * Returns true if the gap should be filled in.
848   * 
849   * @param currentRun - The current run
850   * @param tabIndex - The current tab
851   * @param x - The x position of the tab
852   * @param y - The y position of the tab
853   * 
854   * @return true if the gap at the current run should be filled 
855   */
856  protected boolean shouldFillGap(int currentRun, int tabIndex, int x, int y)
857  {
858    // As far as I can tell, the gap is never filled in.
859    return false;
860  }
861  
862  /**
863   * Paints the highlight below the tab, if there is one.
864   */
865  protected void paintHighlightBelowTab()
866  {
867    int selected = tabPane.getSelectedIndex();
868    int tabPlacement = tabPane.getTabPlacement();
869    Rectangle bounds = getTabBounds(tabPane, selected);
870    
871    hg.setColor(selectColor);
872    int x = bounds.x;
873    int y = bounds.y;
874    int w = bounds.width;
875    int h = bounds.height;
876
877    if (tabPlacement == TOP) 
878        hg.fillRect(x, y + h - 2, w, 30);
879    else if (tabPlacement == LEFT)
880        hg.fillRect(x + w - 1, y, 20, h);
881    else if (tabPlacement == BOTTOM)
882        hg.fillRect(x, y - h + 2, w, 30);
883    else if (tabPlacement == RIGHT)
884        hg.fillRect(x - 18, y, 20, h);
885    else 
886      throw new AssertionError("Unrecognised 'tabPlacement' argument.");
887    hg = null;
888  }
889  
890  /**
891   * Returns true if we should rotate the tab runs. 
892   * 
893   * @param tabPlacement - The current tab placement.
894   * @param selectedRun - The selected run.
895   * 
896   * @return true if the tab runs should be rotated.
897   */
898  protected boolean shouldRotateTabRuns(int tabPlacement,
899                                        int selectedRun)
900  {
901    // false because tab runs are not rotated in the MetalLookAndFeel
902    return false;
903  }
904
905  protected int calculateMaxTabHeight(int tabPlacement)
906  {
907    // FIXME: Why is this overridden?
908    return super.calculateMaxTabHeight(tabPlacement);
909  }
910
911  /**
912   * Returns the amount of overlay among the tabs. In
913   * the Metal L&F the overlay for LEFT and RIGHT placement
914   * is half of the maxTabHeight. For TOP and BOTTOM placement
915   * the tabs do not overlay.
916   *
917   * @param tabPlacement the placement
918   *
919   * @return the amount of overlay among the tabs
920   */
921  protected int getTabRunOverlay(int tabPlacement)
922  {
923    int overlay = 0;
924    if (tabPlacement == LEFT || tabPlacement == RIGHT)
925      {
926        int maxHeight = calculateMaxTabHeight(tabPlacement);
927        overlay = maxTabHeight / 2;
928      }
929    return overlay;
930  }
931
932  /**
933   * Paints the upper edge of the content border.
934   *
935   * @param g the graphics to use for painting
936   * @param tabPlacement the tab placement
937   * @param selectedIndex the index of the selected tab
938   * @param x the upper left coordinate of the content area
939   * @param y the upper left coordinate of the content area
940   * @param w the width of the content area
941   * @param h the height of the content area
942   */
943  protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
944                                           int selectedIndex, int x, int y,
945                                           int w, int h)
946  {
947    Color oceanSelectedBorder =
948      UIManager.getColor("TabbedPane.borderHightlightColor");
949    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
950    if (isOcean)
951      {
952        g.setColor(oceanSelectedBorder);
953      }
954    else
955      {
956        g.setColor(selectHighlight);
957      }
958
959    Rectangle rect = selectedIndex < 0 ? null :
960                                         getTabBounds(selectedIndex, calcRect);
961
962    // If tabs are not placed on TOP, or if the selected tab is not in the
963    // run directly above the content or the selected tab is not visible,
964    // then we draw an unbroken line.
965    if (tabPlacement != TOP || selectedIndex < 0
966        || rect.y  + rect.height + 1 < y || rect.x < x || rect.x > x + w)
967      {
968        g.drawLine(x, y, x + w - 2, y);
969        if (isOcean && tabPlacement == TOP)
970          {
971            g.setColor(MetalLookAndFeel.getWhite());
972            g.drawLine(x, y + 1, x + w - 2, y + 1);
973          }
974      }
975    else
976      {
977        boolean isLast = isLastTabInRun(selectedIndex);
978        if (isLast)
979          {
980            g.drawLine(x, y, rect.x + 1, y);
981          }
982        else
983          {
984            g.drawLine(x, y, rect.x, y);
985          }
986
987        int right = x + w - 1;
988        if (rect.x + rect.width < right - 1)
989          {
990            if (isLast)
991              {
992                g.drawLine(rect.x + rect.width - 1, y, right - 1, y);
993              }
994            else
995              {
996                g.drawLine(rect.x + rect.width, y, right - 1, y);
997              }
998          }
999        else
1000          {
1001            g.setColor(shadow);
1002            g.drawLine(x + w - 2, y, x + w - 2, y);
1003          }
1004
1005        // When in OceanTheme, draw another white line.
1006        if (isOcean)
1007          {
1008            g.setColor(MetalLookAndFeel.getWhite());
1009            if (isLast)
1010              {
1011                g.drawLine(x, y + 1, rect.x + 1, y + 1);
1012              }
1013            else
1014              {
1015                g.drawLine(x, y + 1, rect.x, y + 1);
1016              }
1017
1018            if (rect.x + rect.width < right - 1)
1019              {
1020                if (isLast)
1021                  {
1022                    g.drawLine(rect.x + rect.width - 1, y + 1, right - 1,
1023                               y + 1);
1024                  }
1025                else
1026                  {
1027                    g.drawLine(rect.x + rect.width, y + 1, right - 1, y + 1);
1028                  }
1029              }
1030            else
1031              {
1032                g.setColor(shadow);
1033                g.drawLine(x + w - 2, y + 1, x + w - 2, y + 1);
1034              }
1035          }
1036      }
1037  }
1038
1039  /**
1040   * Paints the lower edge of the content border.
1041   *
1042   * @param g the graphics to use for painting
1043   * @param tabPlacement the tab placement
1044   * @param selectedIndex the index of the selected tab
1045   * @param x the upper left coordinate of the content area
1046   * @param y the upper left coordinate of the content area
1047   * @param w the width of the content area
1048   * @param h the height of the content area
1049   */
1050  protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
1051                                              int selectedIndex, int x, int y,
1052                                              int w, int h)
1053  {
1054    g.setColor(darkShadow);
1055    
1056    // If tabs are not placed on BOTTOM, or if the selected tab is not in the
1057    // run directly below the content or the selected tab is not visible,
1058    // then we draw an unbroken line.
1059    Rectangle rect = selectedIndex < 0 ? null :
1060                                         getTabBounds(selectedIndex, calcRect);
1061    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
1062    Color oceanSelectedBorder =
1063      UIManager.getColor("TabbedPane.borderHightlightColor");
1064    if (tabPlacement != BOTTOM || selectedIndex < 0 || rect.y - 1 > h
1065        || rect.x < x || rect.x > x + w)
1066      {
1067        if (isOcean && tabPlacement == BOTTOM)
1068          {
1069            g.setColor(oceanSelectedBorder);
1070          }
1071        g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
1072      }
1073    else
1074      {
1075        boolean isLast = isLastTabInRun(selectedIndex);
1076        if (isOcean)
1077          {
1078            g.setColor(oceanSelectedBorder);
1079          }
1080
1081        int bottom = y + h - 1;
1082        int right = x + w - 1;
1083        if (isLast)
1084          {
1085            g.drawLine(x, bottom, rect.x, bottom);
1086          }
1087        else
1088          {
1089            g.drawLine(x, bottom, rect.x - 1, bottom);
1090          }
1091
1092        if (rect.x + rect.width < x + w - 2)
1093          {
1094            if (isLast)
1095              {
1096                g.drawLine(rect.x + rect.width - 1, bottom, right, bottom);
1097              }
1098            else
1099              {
1100                g.drawLine(rect.x + rect.width, bottom, right, bottom);
1101              }
1102          }
1103      }
1104  }
1105
1106  /**
1107   * Paints the left edge of the content border.
1108   *
1109   * @param g the graphics to use for painting
1110   * @param tabPlacement the tab placement
1111   * @param selectedIndex the index of the selected tab
1112   * @param x the upper left coordinate of the content area
1113   * @param y the upper left coordinate of the content area
1114   * @param w the width of the content area
1115   * @param h the height of the content area
1116   */
1117  protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
1118                                            int selectedIndex, int x, int y,
1119                                            int w, int h)
1120  {
1121    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
1122    Color oceanSelectedBorder =
1123      UIManager.getColor("TabbedPane.borderHightlightColor");
1124    Rectangle rect = selectedIndex < 0 ? null :
1125      getTabBounds(selectedIndex, calcRect);
1126
1127    if (isOcean)
1128      {
1129        g.setColor(oceanSelectedBorder);
1130      }
1131    else
1132      {
1133        g.setColor(selectHighlight);
1134      }
1135
1136    // If tabs are not placed on LEFT, or if the selected tab is not in the
1137    // run directly left to the content or the selected tab is not visible,
1138    // then we draw an unbroken line.
1139    if (tabPlacement != LEFT || selectedIndex < 0
1140        || rect.x + rect.width + 1 < x || rect.y < y || rect.y > y + h)
1141      {
1142        g.drawLine(x, y + 1, x, y + h - 2);
1143        if (isOcean && tabPlacement == LEFT)
1144          {
1145            g.setColor(MetalLookAndFeel.getWhite());
1146            g.drawLine(x, y + 1, x, y + h - 2);
1147          }       
1148      }
1149    else
1150      {
1151        g.drawLine(x, y, x, rect.y + 1);
1152        if (rect.y + rect.height < y + h - 2)
1153          {
1154            g.drawLine(x, rect.y + rect.height + 1, x, y + h + 2);
1155          }
1156        if (isOcean)
1157          {
1158            g.setColor(MetalLookAndFeel.getWhite());
1159            g.drawLine(x + 1, y + 1, x + 1, rect.y + 1);
1160            if (rect.y + rect.height < y + h - 2)
1161              {
1162                g.drawLine(x + 1, rect.y + rect.height + 1, x + 1, y + h + 2);
1163              }
1164          }
1165      }
1166    
1167  }
1168
1169  /**
1170   * Paints the right edge of the content border.
1171   *
1172   * @param g the graphics to use for painting
1173   * @param tabPlacement the tab placement
1174   * @param selectedIndex the index of the selected tab
1175   * @param x the upper left coordinate of the content area
1176   * @param y the upper left coordinate of the content area
1177   * @param w the width of the content area
1178   * @param h the height of the content area
1179   */
1180  protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
1181                                             int selectedIndex, int x, int y,
1182                                             int w, int h)
1183  {
1184    g.setColor(darkShadow);
1185    Rectangle rect = selectedIndex < 0 ? null :
1186      getTabBounds(selectedIndex, calcRect);
1187    boolean isOcean = MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme;
1188    Color oceanSelectedBorder =
1189      UIManager.getColor("TabbedPane.borderHightlightColor");
1190
1191    // If tabs are not placed on RIGHT, or if the selected tab is not in the
1192    // run directly right to the content or the selected tab is not visible,
1193    // then we draw an unbroken line.
1194    if (tabPlacement != RIGHT || selectedIndex < 0 || rect.x - 1 > w
1195        || rect.y < y || rect.y > y + h)
1196      {
1197        if (isOcean && tabPlacement == RIGHT)
1198          {
1199            g.setColor(oceanSelectedBorder);
1200          }
1201        g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
1202      }
1203    else
1204      {
1205        if (isOcean)
1206          {
1207            g.setColor(oceanSelectedBorder);
1208          }
1209        g.drawLine(x + w - 1, y, x + w - 1, rect.y);
1210
1211        if (rect.y + rect.height < y + h - 2)
1212          {
1213            g.drawLine(x + w - 1, rect.y + rect.height, x + w - 1, y + h - 2);
1214          }
1215      }
1216  }
1217
1218  /**
1219   * Determines if the specified tab is the last tab in its tab run.
1220   *
1221   * @param tabIndex the index of the tab
1222   *
1223   * @return if the specified tab is the last tab in its tab run
1224   */
1225  private boolean isLastTabInRun(int tabIndex)
1226  {
1227    int count = tabPane.getTabCount();
1228    int run = getRunForTab(count, tabIndex);
1229    int lastIndex = lastTabInRun(count, run);
1230    return tabIndex == lastIndex;
1231  }
1232
1233  /**
1234   * Returns the background for an unselected tab. This first asks the
1235   * JTabbedPane for the background at the specified tab index, if this
1236   * is an UIResource (that means, it is inherited from the JTabbedPane)
1237   * and the TabbedPane.unselectedBackground UI property is not null,
1238   * this returns the value of the TabbedPane.unselectedBackground property,
1239   * otherwise the value returned by the JTabbedPane.
1240   *
1241   * @param tabIndex the index of the tab for which we query the background
1242   *
1243   * @return the background for an unselected tab
1244   */
1245  private Color getUnselectedBackground(int tabIndex)
1246  {
1247    Color bg = tabPane.getBackgroundAt(tabIndex);
1248    Color unselectedBackground =
1249      UIManager.getColor("TabbedPane.unselectedBackground");
1250    if (bg instanceof UIResource && unselectedBackground != null)
1251      bg = unselectedBackground;
1252    return bg;
1253  }
1254  
1255  protected int getTabLabelShiftX(int tabPlacement,
1256                                  int index,
1257                                  boolean isSelected)
1258  {
1259    return 0;
1260  }
1261
1262  protected int getTabLabelShiftY(int tabPlacement,
1263                                  int index,
1264                                  boolean isSelected)
1265  {
1266    return 0;
1267  }
1268  
1269}