001    /* MetalTabbedPaneUI.java
002       Copyright (C) 2005, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.metal;
040    
041    import java.awt.Color;
042    import java.awt.Graphics;
043    import java.awt.LayoutManager;
044    import java.awt.Rectangle;
045    
046    import javax.swing.JComponent;
047    import javax.swing.JTabbedPane;
048    import javax.swing.UIManager;
049    import javax.swing.plaf.ComponentUI;
050    import javax.swing.plaf.UIResource;
051    import javax.swing.plaf.basic.BasicTabbedPaneUI;
052    
053    /**
054     * A UI delegate for the {@link JTabbedPane} component.
055     */
056    public 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    }