1 /*
2  * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.metal;
27 
28 import javax.swing.*;
29 import javax.swing.event.*;
30 import java.awt.*;
31 import java.awt.event.*;
32 import javax.swing.plaf.*;
33 import java.io.Serializable;
34 import javax.swing.plaf.basic.BasicTabbedPaneUI;
35 
36 /**
37  * The Metal subclass of BasicTabbedPaneUI.
38  * <p>
39  * <strong>Warning:</strong>
40  * Serialized objects of this class will not be compatible with
41  * future Swing releases. The current serialization support is
42  * appropriate for short term storage or RMI between applications running
43  * the same version of Swing.  As of 1.4, support for long term storage
44  * of all JavaBeans&trade;
45  * has been added to the <code>java.beans</code> package.
46  * Please see {@link java.beans.XMLEncoder}.
47  *
48  * @author Tom Santos
49  */
50 @SuppressWarnings("serial") // Same-version serialization only
51 public class MetalTabbedPaneUI extends BasicTabbedPaneUI {
52 
53     /**
54      * The minimum width of a pane.
55      */
56     protected int minTabWidth = 40;
57     // Background color for unselected tabs that don't have an explicitly
58     // set color.
59     private Color unselectedBackground;
60 
61     /**
62      * The color of tab's background.
63      */
64     protected Color tabAreaBackground;
65 
66     /**
67      * The color of the selected pane.
68      */
69     protected Color selectColor;
70 
71     /**
72      * The color of the highlight.
73      */
74     protected Color selectHighlight;
75     private boolean tabsOpaque = true;
76 
77     // Whether or not we're using ocean. This is cached as it is used
78     // extensively during painting.
79     private boolean ocean;
80     // Selected border color for ocean.
81     private Color oceanSelectedBorderColor;
82 
83     /**
84      * Constructs {@code MetalTabbedPaneUI}.
85      *
86      * @param x a component
87      * @return an instance of {@code MetalTabbedPaneUI}
88      */
createUI( JComponent x )89     public static ComponentUI createUI( JComponent x ) {
90         return new MetalTabbedPaneUI();
91     }
92 
createLayoutManager()93     protected LayoutManager createLayoutManager() {
94         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
95             return super.createLayoutManager();
96         }
97         return new TabbedPaneLayout();
98     }
99 
installDefaults()100     protected void installDefaults() {
101         super.installDefaults();
102 
103         tabAreaBackground = UIManager.getColor("TabbedPane.tabAreaBackground");
104         selectColor = UIManager.getColor("TabbedPane.selected");
105         selectHighlight = UIManager.getColor("TabbedPane.selectHighlight");
106         tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
107         unselectedBackground = UIManager.getColor(
108                                          "TabbedPane.unselectedBackground");
109         ocean = MetalLookAndFeel.usingOcean();
110         if (ocean) {
111             oceanSelectedBorderColor = UIManager.getColor(
112                          "TabbedPane.borderHightlightColor");
113         }
114     }
115 
116 
paintTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected)117     protected void paintTabBorder( Graphics g, int tabPlacement,
118                                    int tabIndex, int x, int y, int w, int h,
119                                    boolean isSelected) {
120         int bottom = y + (h-1);
121         int right = x + (w-1);
122 
123         switch ( tabPlacement ) {
124         case LEFT:
125             paintLeftTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
126             break;
127         case BOTTOM:
128             paintBottomTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
129             break;
130         case RIGHT:
131             paintRightTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
132             break;
133         case TOP:
134         default:
135             paintTopTabBorder(tabIndex, g, x, y, w, h, bottom, right, isSelected);
136         }
137     }
138 
139 
140     /**
141      * Paints the top tab border.
142      *
143      * @param tabIndex a tab index
144      * @param g an instance of {@code Graphics}
145      * @param x an X coordinate
146      * @param y an Y coordinate
147      * @param w a width
148      * @param h a height
149      * @param btm bottom
150      * @param rght right
151      * @param isSelected a selection
152      */
paintTopTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )153     protected void paintTopTabBorder( int tabIndex, Graphics g,
154                                       int x, int y, int w, int h,
155                                       int btm, int rght,
156                                       boolean isSelected ) {
157         int currentRun = getRunForTab( tabPane.getTabCount(), tabIndex );
158         int lastIndex = lastTabInRun( tabPane.getTabCount(), currentRun );
159         int firstIndex = tabRuns[ currentRun ];
160         boolean leftToRight = MetalUtils.isLeftToRight(tabPane);
161         int selectedIndex = tabPane.getSelectedIndex();
162         int bottom = h - 1;
163         int right = w - 1;
164 
165         //
166         // Paint Gap
167         //
168 
169         if (shouldFillGap( currentRun, tabIndex, x, y ) ) {
170             g.translate( x, y );
171 
172             if ( leftToRight ) {
173                 g.setColor( getColorForGap( currentRun, x, y + 1 ) );
174                 g.fillRect( 1, 0, 5, 3 );
175                 g.fillRect( 1, 3, 2, 2 );
176             } else {
177                 g.setColor( getColorForGap( currentRun, x + w - 1, y + 1 ) );
178                 g.fillRect( right - 5, 0, 5, 3 );
179                 g.fillRect( right - 2, 3, 2, 2 );
180             }
181 
182             g.translate( -x, -y );
183         }
184 
185         g.translate( x, y );
186 
187         //
188         // Paint Border
189         //
190 
191         if (ocean && isSelected) {
192             g.setColor(oceanSelectedBorderColor);
193         }
194         else {
195             g.setColor( darkShadow );
196         }
197 
198         if ( leftToRight ) {
199 
200             // Paint slant
201             g.drawLine( 1, 5, 6, 0 );
202 
203             // Paint top
204             g.drawLine( 6, 0, right, 0 );
205 
206             // Paint right
207             if ( tabIndex==lastIndex ) {
208                 // last tab in run
209                 g.drawLine( right, 1, right, bottom );
210             }
211 
212             if (ocean && tabIndex - 1 == selectedIndex &&
213                                 currentRun == getRunForTab(
214                                 tabPane.getTabCount(), selectedIndex)) {
215                 g.setColor(oceanSelectedBorderColor);
216             }
217 
218             // Paint left
219             if ( tabIndex != tabRuns[ runCount - 1 ] ) {
220                 // not the first tab in the last run
221                 if (ocean && isSelected) {
222                     g.drawLine(0, 6, 0, bottom);
223                     g.setColor(darkShadow);
224                     g.drawLine(0, 0, 0, 5);
225                 }
226                 else {
227                     g.drawLine( 0, 0, 0, bottom );
228                 }
229             } else {
230                 // the first tab in the last run
231                 g.drawLine( 0, 6, 0, bottom );
232             }
233         } else {
234 
235             // Paint slant
236             g.drawLine( right - 1, 5, right - 6, 0 );
237 
238             // Paint top
239             g.drawLine( right - 6, 0, 0, 0 );
240 
241             // Paint left
242             if ( tabIndex==lastIndex ) {
243                 // last tab in run
244                 g.drawLine( 0, 1, 0, bottom );
245             }
246 
247             // Paint right
248             if (ocean && tabIndex - 1 == selectedIndex &&
249                                 currentRun == getRunForTab(
250                                 tabPane.getTabCount(), selectedIndex)) {
251                 g.setColor(oceanSelectedBorderColor);
252                 g.drawLine(right, 0, right, bottom);
253             }
254             else if (ocean && isSelected) {
255                 g.drawLine(right, 6, right, bottom);
256                 if (tabIndex != 0) {
257                     g.setColor(darkShadow);
258                     g.drawLine(right, 0, right, 5);
259                 }
260             }
261             else {
262                 if ( tabIndex != tabRuns[ runCount - 1 ] ) {
263                     // not the first tab in the last run
264                     g.drawLine( right, 0, right, bottom );
265                 } else {
266                     // the first tab in the last run
267                     g.drawLine( right, 6, right, bottom );
268                 }
269             }
270         }
271 
272         //
273         // Paint Highlight
274         //
275 
276         g.setColor( isSelected ? selectHighlight : highlight );
277 
278         if ( leftToRight ) {
279 
280             // Paint slant
281             g.drawLine( 1, 6, 6, 1 );
282 
283             // Paint top
284             g.drawLine( 6, 1, (tabIndex == lastIndex) ? right - 1 : right, 1 );
285 
286             // Paint left
287             g.drawLine( 1, 6, 1, bottom );
288 
289             // paint highlight in the gap on tab behind this one
290             // on the left end (where they all line up)
291             if ( tabIndex==firstIndex && tabIndex!=tabRuns[runCount - 1] ) {
292                 //  first tab in run but not first tab in last run
293                 if (tabPane.getSelectedIndex()==tabRuns[currentRun+1]) {
294                     // tab in front of selected tab
295                     g.setColor( selectHighlight );
296                 }
297                 else {
298                     // tab in front of normal tab
299                     g.setColor( highlight );
300                 }
301                 g.drawLine( 1, 0, 1, 4 );
302             }
303         } else {
304 
305             // Paint slant
306             g.drawLine( right - 1, 6, right - 6, 1 );
307 
308             // Paint top
309             g.drawLine( right - 6, 1, 1, 1 );
310 
311             // Paint left
312             if ( tabIndex==lastIndex ) {
313                 // last tab in run
314                 g.drawLine( 1, 1, 1, bottom );
315             } else {
316                 g.drawLine( 0, 1, 0, bottom );
317             }
318         }
319 
320         g.translate( -x, -y );
321     }
322 
323     /**
324      * Returns {@code true} if the gap should be filled.
325      *
326      * @param currentRun the current run
327      * @param tabIndex the tab index
328      * @param x an X coordinate
329      * @param y an Y coordinate
330      * @return {@code true} if the gap should be filled
331      */
shouldFillGap( int currentRun, int tabIndex, int x, int y )332     protected boolean shouldFillGap( int currentRun, int tabIndex, int x, int y ) {
333         boolean result = false;
334 
335         if (!tabsOpaque) {
336             return false;
337         }
338 
339         if ( currentRun == runCount - 2 ) {  // If it's the second to last row.
340             Rectangle lastTabBounds = getTabBounds( tabPane, tabPane.getTabCount() - 1 );
341             Rectangle tabBounds = getTabBounds( tabPane, tabIndex );
342             if (MetalUtils.isLeftToRight(tabPane)) {
343                 int lastTabRight = lastTabBounds.x + lastTabBounds.width - 1;
344 
345                 // is the right edge of the last tab to the right
346                 // of the left edge of the current tab?
347                 if ( lastTabRight > tabBounds.x + 2 ) {
348                     return true;
349                 }
350             } else {
351                 int lastTabLeft = lastTabBounds.x;
352                 int currentTabRight = tabBounds.x + tabBounds.width - 1;
353 
354                 // is the left edge of the last tab to the left
355                 // of the right edge of the current tab?
356                 if ( lastTabLeft < currentTabRight - 2 ) {
357                     return true;
358                 }
359             }
360         } else {
361             // fill in gap for all other rows except last row
362             result = currentRun != runCount - 1;
363         }
364 
365         return result;
366     }
367 
368     /**
369      * Returns the color of the gap.
370      *
371      * @param currentRun the current run
372      * @param x an X coordinate
373      * @param y an Y coordinate
374      * @return the color of the gap
375      */
getColorForGap( int currentRun, int x, int y )376     protected Color getColorForGap( int currentRun, int x, int y ) {
377         final int shadowWidth = 4;
378         int selectedIndex = tabPane.getSelectedIndex();
379         int startIndex = tabRuns[ currentRun + 1 ];
380         int endIndex = lastTabInRun( tabPane.getTabCount(), currentRun + 1 );
381         int tabOverGap = -1;
382         // Check each tab in the row that is 'on top' of this row
383         for ( int i = startIndex; i <= endIndex; ++i ) {
384             Rectangle tabBounds = getTabBounds( tabPane, i );
385             int tabLeft = tabBounds.x;
386             int tabRight = (tabBounds.x + tabBounds.width) - 1;
387             // Check to see if this tab is over the gap
388             if ( MetalUtils.isLeftToRight(tabPane) ) {
389                 if ( tabLeft <= x && tabRight - shadowWidth > x ) {
390                     return selectedIndex == i ? selectColor : getUnselectedBackgroundAt( i );
391                 }
392             }
393             else {
394                 if ( tabLeft + shadowWidth < x && tabRight >= x ) {
395                     return selectedIndex == i ? selectColor : getUnselectedBackgroundAt( i );
396                 }
397             }
398         }
399 
400         return tabPane.getBackground();
401     }
402 
403     /**
404      * Paints the left tab border.
405      *
406      * @param tabIndex a tab index
407      * @param g an instance of {@code Graphics}
408      * @param x an X coordinate
409      * @param y an Y coordinate
410      * @param w a width
411      * @param h a height
412      * @param btm bottom
413      * @param rght right
414      * @param isSelected a selection
415      */
paintLeftTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )416     protected void paintLeftTabBorder( int tabIndex, Graphics g,
417                                        int x, int y, int w, int h,
418                                        int btm, int rght,
419                                        boolean isSelected ) {
420         int tabCount = tabPane.getTabCount();
421         int currentRun = getRunForTab( tabCount, tabIndex );
422         int lastIndex = lastTabInRun( tabCount, currentRun );
423         int firstIndex = tabRuns[ currentRun ];
424 
425         g.translate( x, y );
426 
427         int bottom = h - 1;
428         int right = w - 1;
429 
430         //
431         // Paint part of the tab above
432         //
433 
434         if ( tabIndex != firstIndex && tabsOpaque ) {
435             g.setColor( tabPane.getSelectedIndex() == tabIndex - 1 ?
436                         selectColor :
437                         getUnselectedBackgroundAt( tabIndex - 1 ) );
438             g.fillRect( 2, 0, 4, 3 );
439             g.drawLine( 2, 3, 2, 3 );
440         }
441 
442 
443         //
444         // Paint Highlight
445         //
446 
447         if (ocean) {
448             g.setColor(isSelected ? selectHighlight :
449                        MetalLookAndFeel.getWhite());
450         }
451         else {
452             g.setColor( isSelected ? selectHighlight : highlight );
453         }
454 
455         // Paint slant
456         g.drawLine( 1, 6, 6, 1 );
457 
458         // Paint left
459         g.drawLine( 1, 6, 1, bottom );
460 
461         // Paint top
462         g.drawLine( 6, 1, right, 1 );
463 
464         if ( tabIndex != firstIndex ) {
465             if (tabPane.getSelectedIndex() == tabIndex - 1) {
466                 g.setColor(selectHighlight);
467             } else {
468                 g.setColor(ocean ? MetalLookAndFeel.getWhite() : highlight);
469             }
470 
471             g.drawLine( 1, 0, 1, 4 );
472         }
473 
474         //
475         // Paint Border
476         //
477 
478         if (ocean) {
479             if (isSelected) {
480                 g.setColor(oceanSelectedBorderColor);
481             }
482             else {
483                 g.setColor( darkShadow );
484             }
485         }
486         else {
487             g.setColor( darkShadow );
488         }
489 
490         // Paint slant
491         g.drawLine( 1, 5, 6, 0 );
492 
493         // Paint top
494         g.drawLine( 6, 0, right, 0 );
495 
496         // Paint bottom
497         if ( tabIndex == lastIndex ) {
498             g.drawLine( 0, bottom, right, bottom );
499         }
500 
501         // Paint left
502         if (ocean) {
503             if (tabPane.getSelectedIndex() == tabIndex - 1) {
504                 g.drawLine(0, 5, 0, bottom);
505                 g.setColor(oceanSelectedBorderColor);
506                 g.drawLine(0, 0, 0, 5);
507             }
508             else if (isSelected) {
509                 g.drawLine( 0, 6, 0, bottom );
510                 if (tabIndex != 0) {
511                     g.setColor(darkShadow);
512                     g.drawLine(0, 0, 0, 5);
513                 }
514             }
515             else if ( tabIndex != firstIndex ) {
516                 g.drawLine( 0, 0, 0, bottom );
517             } else {
518                 g.drawLine( 0, 6, 0, bottom );
519             }
520         }
521         else { // metal
522             if ( tabIndex != firstIndex ) {
523                 g.drawLine( 0, 0, 0, bottom );
524             } else {
525                 g.drawLine( 0, 6, 0, bottom );
526             }
527         }
528 
529         g.translate( -x, -y );
530     }
531 
532 
533     /**
534      * Paints the bottom tab border.
535      *
536      * @param tabIndex a tab index
537      * @param g an instance of {@code Graphics}
538      * @param x an X coordinate
539      * @param y an Y coordinate
540      * @param w a width
541      * @param h a height
542      * @param btm bottom
543      * @param rght right
544      * @param isSelected a selection
545      */
paintBottomTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )546     protected void paintBottomTabBorder( int tabIndex, Graphics g,
547                                          int x, int y, int w, int h,
548                                          int btm, int rght,
549                                          boolean isSelected ) {
550         int tabCount = tabPane.getTabCount();
551         int currentRun = getRunForTab( tabCount, tabIndex );
552         int lastIndex = lastTabInRun( tabCount, currentRun );
553         int firstIndex = tabRuns[ currentRun ];
554         boolean leftToRight = MetalUtils.isLeftToRight(tabPane);
555 
556         int bottom = h - 1;
557         int right = w - 1;
558 
559         //
560         // Paint Gap
561         //
562 
563         if ( shouldFillGap( currentRun, tabIndex, x, y ) ) {
564             g.translate( x, y );
565 
566             if ( leftToRight ) {
567                 g.setColor( getColorForGap( currentRun, x, y ) );
568                 g.fillRect( 1, bottom - 4, 3, 5 );
569                 g.fillRect( 4, bottom - 1, 2, 2 );
570             } else {
571                 g.setColor( getColorForGap( currentRun, x + w - 1, y ) );
572                 g.fillRect( right - 3, bottom - 3, 3, 4 );
573                 g.fillRect( right - 5, bottom - 1, 2, 2 );
574                 g.drawLine( right - 1, bottom - 4, right - 1, bottom - 4 );
575             }
576 
577             g.translate( -x, -y );
578         }
579 
580         g.translate( x, y );
581 
582 
583         //
584         // Paint Border
585         //
586 
587         if (ocean && isSelected) {
588             g.setColor(oceanSelectedBorderColor);
589         }
590         else {
591             g.setColor( darkShadow );
592         }
593 
594         if ( leftToRight ) {
595 
596             // Paint slant
597             g.drawLine( 1, bottom - 5, 6, bottom );
598 
599             // Paint bottom
600             g.drawLine( 6, bottom, right, bottom );
601 
602             // Paint right
603             if ( tabIndex == lastIndex ) {
604                 g.drawLine( right, 0, right, bottom );
605             }
606 
607             // Paint left
608             if (ocean && isSelected) {
609                 g.drawLine(0, 0, 0, bottom - 6);
610                 if ((currentRun == 0 && tabIndex != 0) ||
611                     (currentRun > 0 && tabIndex != tabRuns[currentRun - 1])) {
612                     g.setColor(darkShadow);
613                     g.drawLine(0, bottom - 5, 0, bottom);
614                 }
615             }
616             else {
617                 if (ocean && tabIndex == tabPane.getSelectedIndex() + 1) {
618                     g.setColor(oceanSelectedBorderColor);
619                 }
620                 if ( tabIndex != tabRuns[ runCount - 1 ] ) {
621                     g.drawLine( 0, 0, 0, bottom );
622                 } else {
623                     g.drawLine( 0, 0, 0, bottom - 6 );
624                 }
625             }
626         } else {
627 
628             // Paint slant
629             g.drawLine( right - 1, bottom - 5, right - 6, bottom );
630 
631             // Paint bottom
632             g.drawLine( right - 6, bottom, 0, bottom );
633 
634             // Paint left
635             if ( tabIndex==lastIndex ) {
636                 // last tab in run
637                 g.drawLine( 0, 0, 0, bottom );
638             }
639 
640             // Paint right
641             if (ocean && tabIndex == tabPane.getSelectedIndex() + 1) {
642                 g.setColor(oceanSelectedBorderColor);
643                 g.drawLine(right, 0, right, bottom);
644             }
645             else if (ocean && isSelected) {
646                 g.drawLine(right, 0, right, bottom - 6);
647                 if (tabIndex != firstIndex) {
648                     g.setColor(darkShadow);
649                     g.drawLine(right, bottom - 5, right, bottom);
650                 }
651             }
652             else if ( tabIndex != tabRuns[ runCount - 1 ] ) {
653                 // not the first tab in the last run
654                 g.drawLine( right, 0, right, bottom );
655             } else {
656                 // the first tab in the last run
657                 g.drawLine( right, 0, right, bottom - 6 );
658             }
659         }
660 
661         //
662         // Paint Highlight
663         //
664 
665         g.setColor( isSelected ? selectHighlight : highlight );
666 
667         if ( leftToRight ) {
668 
669             // Paint slant
670             g.drawLine( 1, bottom - 6, 6, bottom - 1 );
671 
672             // Paint left
673             g.drawLine( 1, 0, 1, bottom - 6 );
674 
675             // paint highlight in the gap on tab behind this one
676             // on the left end (where they all line up)
677             if ( tabIndex==firstIndex && tabIndex!=tabRuns[runCount - 1] ) {
678                 //  first tab in run but not first tab in last run
679                 if (tabPane.getSelectedIndex()==tabRuns[currentRun+1]) {
680                     // tab in front of selected tab
681                     g.setColor( selectHighlight );
682                 }
683                 else {
684                     // tab in front of normal tab
685                     g.setColor( highlight );
686                 }
687                 g.drawLine( 1, bottom - 4, 1, bottom );
688             }
689         } else {
690 
691             // Paint left
692             if ( tabIndex==lastIndex ) {
693                 // last tab in run
694                 g.drawLine( 1, 0, 1, bottom - 1 );
695             } else {
696                 g.drawLine( 0, 0, 0, bottom - 1 );
697             }
698         }
699 
700         g.translate( -x, -y );
701     }
702 
703     /**
704      * Paints the right tab border.
705      *
706      * @param tabIndex a tab index
707      * @param g an instance of {@code Graphics}
708      * @param x an X coordinate
709      * @param y an Y coordinate
710      * @param w a width
711      * @param h a height
712      * @param btm bottom
713      * @param rght right
714      * @param isSelected a selection
715      */
paintRightTabBorder( int tabIndex, Graphics g, int x, int y, int w, int h, int btm, int rght, boolean isSelected )716     protected void paintRightTabBorder( int tabIndex, Graphics g,
717                                         int x, int y, int w, int h,
718                                         int btm, int rght,
719                                         boolean isSelected ) {
720         int tabCount = tabPane.getTabCount();
721         int currentRun = getRunForTab( tabCount, tabIndex );
722         int lastIndex = lastTabInRun( tabCount, currentRun );
723         int firstIndex = tabRuns[ currentRun ];
724 
725         g.translate( x, y );
726 
727         int bottom = h - 1;
728         int right = w - 1;
729 
730         //
731         // Paint part of the tab above
732         //
733 
734         if ( tabIndex != firstIndex && tabsOpaque ) {
735             g.setColor( tabPane.getSelectedIndex() == tabIndex - 1 ?
736                         selectColor :
737                         getUnselectedBackgroundAt( tabIndex - 1 ) );
738             g.fillRect( right - 5, 0, 5, 3 );
739             g.fillRect( right - 2, 3, 2, 2 );
740         }
741 
742 
743         //
744         // Paint Highlight
745         //
746 
747         g.setColor( isSelected ? selectHighlight : highlight );
748 
749         // Paint slant
750         g.drawLine( right - 6, 1, right - 1, 6 );
751 
752         // Paint top
753         g.drawLine( 0, 1, right - 6, 1 );
754 
755         // Paint left
756         if ( !isSelected ) {
757             g.drawLine( 0, 1, 0, bottom );
758         }
759 
760 
761         //
762         // Paint Border
763         //
764 
765         if (ocean && isSelected) {
766             g.setColor(oceanSelectedBorderColor);
767         }
768         else {
769             g.setColor( darkShadow );
770         }
771 
772         // Paint bottom
773         if ( tabIndex == lastIndex ) {
774             g.drawLine( 0, bottom, right, bottom );
775         }
776 
777         // Paint slant
778         if (ocean && tabPane.getSelectedIndex() == tabIndex - 1) {
779             g.setColor(oceanSelectedBorderColor);
780         }
781         g.drawLine( right - 6, 0, right, 6 );
782 
783         // Paint top
784         g.drawLine( 0, 0, right - 6, 0 );
785 
786         // Paint right
787         if (ocean && isSelected) {
788             g.drawLine(right, 6, right, bottom);
789             if (tabIndex != firstIndex) {
790                 g.setColor(darkShadow);
791                 g.drawLine(right, 0, right, 5);
792             }
793         }
794         else if (ocean && tabPane.getSelectedIndex() == tabIndex - 1) {
795             g.setColor(oceanSelectedBorderColor);
796             g.drawLine(right, 0, right, 6);
797             g.setColor(darkShadow);
798             g.drawLine(right, 6, right, bottom);
799         }
800         else if ( tabIndex != firstIndex ) {
801             g.drawLine( right, 0, right, bottom );
802         } else {
803             g.drawLine( right, 6, right, bottom );
804         }
805 
806         g.translate( -x, -y );
807     }
808 
update( Graphics g, JComponent c )809     public void update( Graphics g, JComponent c ) {
810         if ( c.isOpaque() ) {
811             g.setColor( tabAreaBackground );
812             g.fillRect( 0, 0, c.getWidth(),c.getHeight() );
813         }
814         paint( g, c );
815     }
816 
paintTabBackground( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected )817     protected void paintTabBackground( Graphics g, int tabPlacement,
818                                        int tabIndex, int x, int y, int w, int h, boolean isSelected ) {
819         int slantWidth = h / 2;
820         if ( isSelected ) {
821             g.setColor( selectColor );
822         } else {
823             g.setColor( getUnselectedBackgroundAt( tabIndex ) );
824         }
825 
826         if (MetalUtils.isLeftToRight(tabPane)) {
827             switch ( tabPlacement ) {
828                 case LEFT:
829                     g.fillRect( x + 5, y + 1, w - 5, h - 1);
830                     g.fillRect( x + 2, y + 4, 3, h - 4 );
831                     break;
832                 case BOTTOM:
833                     g.fillRect( x + 2, y, w - 2, h - 4 );
834                     g.fillRect( x + 5, y + (h - 1) - 3, w - 5, 3 );
835                     break;
836                 case RIGHT:
837                     g.fillRect( x, y + 2, w - 4, h - 2);
838                     g.fillRect( x + (w - 1) - 3, y + 5, 3, h - 5 );
839                     break;
840                 case TOP:
841                 default:
842                     g.fillRect( x + 4, y + 2, (w - 1) - 3, (h - 1) - 1 );
843                     g.fillRect( x + 2, y + 5, 2, h - 5 );
844             }
845         } else {
846             switch ( tabPlacement ) {
847                 case LEFT:
848                     g.fillRect( x + 5, y + 1, w - 5, h - 1);
849                     g.fillRect( x + 2, y + 4, 3, h - 4 );
850                     break;
851                 case BOTTOM:
852                     g.fillRect( x, y, w - 5, h - 1 );
853                     g.fillRect( x + (w - 1) - 4, y, 4, h - 5);
854                     g.fillRect( x + (w - 1) - 4, y + (h - 1) - 4, 2, 2);
855                     break;
856                 case RIGHT:
857                     g.fillRect( x + 1, y + 1, w - 5, h - 1);
858                     g.fillRect( x + (w - 1) - 3, y + 5, 3, h - 5 );
859                     break;
860                 case TOP:
861                 default:
862                     g.fillRect( x, y + 2, (w - 1) - 3, (h - 1) - 1 );
863                     g.fillRect( x + (w - 1) - 3, y + 5, 3, h - 3 );
864             }
865         }
866     }
867 
868     /**
869      * Overridden to do nothing for the Java L&amp;F.
870      */
getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected )871     protected int getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected ) {
872         return 0;
873     }
874 
875 
876     /**
877      * Overridden to do nothing for the Java L&amp;F.
878      */
getTabLabelShiftY( int tabPlacement, int tabIndex, boolean isSelected )879     protected int getTabLabelShiftY( int tabPlacement, int tabIndex, boolean isSelected ) {
880         return 0;
881     }
882 
883     /**
884      * {@inheritDoc}
885      *
886      * @since 1.6
887      */
getBaselineOffset()888     protected int getBaselineOffset() {
889         return 0;
890     }
891 
paint( Graphics g, JComponent c )892     public void paint( Graphics g, JComponent c ) {
893         int tabPlacement = tabPane.getTabPlacement();
894 
895         Insets insets = c.getInsets(); Dimension size = c.getSize();
896 
897         // Paint the background for the tab area
898         if ( tabPane.isOpaque() ) {
899             Color background = c.getBackground();
900             if (background instanceof UIResource && tabAreaBackground != null) {
901                 g.setColor(tabAreaBackground);
902             }
903             else {
904                 g.setColor(background);
905             }
906             switch ( tabPlacement ) {
907             case LEFT:
908                 g.fillRect( insets.left, insets.top,
909                             calculateTabAreaWidth( tabPlacement, runCount, maxTabWidth ),
910                             size.height - insets.bottom - insets.top );
911                 break;
912             case BOTTOM:
913                 int totalTabHeight = calculateTabAreaHeight( tabPlacement, runCount, maxTabHeight );
914                 g.fillRect( insets.left, size.height - insets.bottom - totalTabHeight,
915                             size.width - insets.left - insets.right,
916                             totalTabHeight );
917                 break;
918             case RIGHT:
919                 int totalTabWidth = calculateTabAreaWidth( tabPlacement, runCount, maxTabWidth );
920                 g.fillRect( size.width - insets.right - totalTabWidth,
921                             insets.top, totalTabWidth,
922                             size.height - insets.top - insets.bottom );
923                 break;
924             case TOP:
925             default:
926                 g.fillRect( insets.left, insets.top,
927                             size.width - insets.right - insets.left,
928                             calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight) );
929                 paintHighlightBelowTab();
930             }
931         }
932 
933         super.paint( g, c );
934     }
935 
936     /**
937      * Paints highlights below tab.
938      */
paintHighlightBelowTab( )939     protected void paintHighlightBelowTab( ) {
940 
941     }
942 
943 
paintFocusIndicator(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect, boolean isSelected)944     protected void paintFocusIndicator(Graphics g, int tabPlacement,
945                                        Rectangle[] rects, int tabIndex,
946                                        Rectangle iconRect, Rectangle textRect,
947                                        boolean isSelected) {
948         if ( tabPane.hasFocus() && isSelected ) {
949             Rectangle tabRect = rects[tabIndex];
950             boolean lastInRun = isLastInRun( tabIndex );
951             g.setColor( focus );
952             g.translate( tabRect.x, tabRect.y );
953             int right = tabRect.width - 1;
954             int bottom = tabRect.height - 1;
955             boolean leftToRight = MetalUtils.isLeftToRight(tabPane);
956             switch ( tabPlacement ) {
957             case RIGHT:
958                 g.drawLine( right - 6,2 , right - 2,6 );         // slant
959                 g.drawLine( 1,2 , right - 6,2 );                 // top
960                 g.drawLine( right - 2,6 , right - 2,bottom );    // right
961                 g.drawLine( 1,2 , 1,bottom );                    // left
962                 g.drawLine( 1,bottom , right - 2,bottom );       // bottom
963                 break;
964             case BOTTOM:
965                 if ( leftToRight ) {
966                     g.drawLine( 2, bottom - 6, 6, bottom - 2 );   // slant
967                     g.drawLine( 6, bottom - 2,
968                                 right, bottom - 2 );              // bottom
969                     g.drawLine( 2, 0, 2, bottom - 6 );            // left
970                     g.drawLine( 2, 0, right, 0 );                 // top
971                     g.drawLine( right, 0, right, bottom - 2 );    // right
972                 } else {
973                     g.drawLine( right - 2, bottom - 6,
974                                 right - 6, bottom - 2 );          // slant
975                     g.drawLine( right - 2, 0,
976                                 right - 2, bottom - 6 );          // right
977                     if ( lastInRun ) {
978                         // last tab in run
979                         g.drawLine( 2, bottom - 2,
980                                     right - 6, bottom - 2 );      // bottom
981                         g.drawLine( 2, 0, right - 2, 0 );         // top
982                         g.drawLine( 2, 0, 2, bottom - 2 );        // left
983                     } else {
984                         g.drawLine( 1, bottom - 2,
985                                     right - 6, bottom - 2 );      // bottom
986                         g.drawLine( 1, 0, right - 2, 0 );         // top
987                         g.drawLine( 1, 0, 1, bottom - 2 );        // left
988                     }
989                 }
990                 break;
991             case LEFT:
992                 g.drawLine( 2, 6, 6, 2 );                         // slant
993                 g.drawLine( 2, 6, 2, bottom - 1);                 // left
994                 g.drawLine( 6, 2, right, 2 );                     // top
995                 g.drawLine( right, 2, right, bottom - 1 );        // right
996                 g.drawLine( 2, bottom - 1,
997                             right, bottom - 1 );                  // bottom
998                 break;
999             case TOP:
1000              default:
1001                     if ( leftToRight ) {
1002                         g.drawLine( 2, 6, 6, 2 );                     // slant
1003                         g.drawLine( 2, 6, 2, bottom - 1);             // left
1004                         g.drawLine( 6, 2, right, 2 );                 // top
1005                         g.drawLine( right, 2, right, bottom - 1 );    // right
1006                         g.drawLine( 2, bottom - 1,
1007                                     right, bottom - 1 );              // bottom
1008                     }
1009                     else {
1010                         g.drawLine( right - 2, 6, right - 6, 2 );     // slant
1011                         g.drawLine( right - 2, 6,
1012                                     right - 2, bottom - 1);           // right
1013                         if ( lastInRun ) {
1014                             // last tab in run
1015                             g.drawLine( right - 6, 2, 2, 2 );         // top
1016                             g.drawLine( 2, 2, 2, bottom - 1 );        // left
1017                             g.drawLine( right - 2, bottom - 1,
1018                                         2, bottom - 1 );              // bottom
1019                         }
1020                         else {
1021                             g.drawLine( right - 6, 2, 1, 2 );         // top
1022                             g.drawLine( 1, 2, 1, bottom - 1 );        // left
1023                             g.drawLine( right - 2, bottom - 1,
1024                                         1, bottom - 1 );              // bottom
1025                         }
1026                     }
1027             }
1028             g.translate( -tabRect.x, -tabRect.y );
1029         }
1030     }
1031 
paintContentBorderTopEdge( Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h )1032     protected void paintContentBorderTopEdge( Graphics g, int tabPlacement,
1033                                               int selectedIndex,
1034                                               int x, int y, int w, int h ) {
1035         boolean leftToRight = MetalUtils.isLeftToRight(tabPane);
1036         int right = x + w - 1;
1037         Rectangle selRect = selectedIndex < 0? null :
1038                                getTabBounds(selectedIndex, calcRect);
1039         if (ocean) {
1040             g.setColor(oceanSelectedBorderColor);
1041         }
1042         else {
1043             g.setColor(selectHighlight);
1044         }
1045 
1046         // Draw unbroken line if tabs are not on TOP, OR
1047         // selected tab is not in run adjacent to content, OR
1048         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1049         //
1050          if (tabPlacement != TOP || selectedIndex < 0 ||
1051             (selRect.y + selRect.height + 1 < y) ||
1052             (selRect.x < x || selRect.x > x + w)) {
1053             g.drawLine(x, y, x+w-2, y);
1054             if (ocean && tabPlacement == TOP) {
1055                 g.setColor(MetalLookAndFeel.getWhite());
1056                 g.drawLine(x, y + 1, x+w-2, y + 1);
1057             }
1058         } else {
1059             // Break line to show visual connection to selected tab
1060             boolean lastInRun = isLastInRun(selectedIndex);
1061 
1062             if ( leftToRight || lastInRun ) {
1063                 g.drawLine(x, y, selRect.x + 1, y);
1064             } else {
1065                 g.drawLine(x, y, selRect.x, y);
1066             }
1067 
1068             if (selRect.x + selRect.width < right - 1) {
1069                 if ( leftToRight && !lastInRun ) {
1070                     g.drawLine(selRect.x + selRect.width, y, right - 1, y);
1071                 } else {
1072                     g.drawLine(selRect.x + selRect.width - 1, y, right - 1, y);
1073                 }
1074             } else {
1075                 g.setColor(shadow);
1076                 g.drawLine(x+w-2, y, x+w-2, y);
1077             }
1078 
1079             if (ocean) {
1080                 g.setColor(MetalLookAndFeel.getWhite());
1081 
1082                 if ( leftToRight || lastInRun ) {
1083                     g.drawLine(x, y + 1, selRect.x + 1, y + 1);
1084                 } else {
1085                     g.drawLine(x, y + 1, selRect.x, y + 1);
1086                 }
1087 
1088                 if (selRect.x + selRect.width < right - 1) {
1089                     if ( leftToRight && !lastInRun ) {
1090                         g.drawLine(selRect.x + selRect.width, y + 1,
1091                                    right - 1, y + 1);
1092                     } else {
1093                         g.drawLine(selRect.x + selRect.width - 1, y + 1,
1094                                    right - 1, y + 1);
1095                     }
1096                 } else {
1097                     g.setColor(shadow);
1098                     g.drawLine(x+w-2, y + 1, x+w-2, y + 1);
1099                 }
1100             }
1101         }
1102     }
1103 
1104     protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
1105                                                 int selectedIndex,
1106                                                 int x, int y, int w, int h) {
1107         boolean leftToRight = MetalUtils.isLeftToRight(tabPane);
1108         int bottom = y + h - 1;
1109         int right = x + w - 1;
1110         Rectangle selRect = selectedIndex < 0? null :
1111                                getTabBounds(selectedIndex, calcRect);
1112 
1113         g.setColor(darkShadow);
1114 
1115         // Draw unbroken line if tabs are not on BOTTOM, OR
1116         // selected tab is not in run adjacent to content, OR
1117         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1118         //
1119         if (tabPlacement != BOTTOM || selectedIndex < 0 ||
1120              (selRect.y - 1 > h) ||
1121              (selRect.x < x || selRect.x > x + w)) {
1122             if (ocean && tabPlacement == BOTTOM) {
1123                 g.setColor(oceanSelectedBorderColor);
1124             }
1125             g.drawLine(x, y+h-1, x+w-1, y+h-1);
1126         } else {
1127             // Break line to show visual connection to selected tab
1128             boolean lastInRun = isLastInRun(selectedIndex);
1129 
1130             if (ocean) {
1131                 g.setColor(oceanSelectedBorderColor);
1132             }
1133 
1134             if ( leftToRight || lastInRun ) {
1135                 g.drawLine(x, bottom, selRect.x, bottom);
1136             } else {
1137                 g.drawLine(x, bottom, selRect.x - 1, bottom);
1138             }
1139 
1140             if (selRect.x + selRect.width < x + w - 2) {
1141                 if ( leftToRight && !lastInRun ) {
1142                     g.drawLine(selRect.x + selRect.width, bottom,
1143                                                    right, bottom);
1144                 } else {
1145                     g.drawLine(selRect.x + selRect.width - 1, bottom,
1146                                                        right, bottom);
1147                 }
1148             }
1149         }
1150     }
1151 
1152     protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
1153                                               int selectedIndex,
1154                                               int x, int y, int w, int h) {
1155         Rectangle selRect = selectedIndex < 0? null :
1156                                getTabBounds(selectedIndex, calcRect);
1157         if (ocean) {
1158             g.setColor(oceanSelectedBorderColor);
1159         }
1160         else {
1161             g.setColor(selectHighlight);
1162         }
1163 
1164         // Draw unbroken line if tabs are not on LEFT, OR
1165         // selected tab is not in run adjacent to content, OR
1166         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1167         //
1168         if (tabPlacement != LEFT || selectedIndex < 0 ||
1169             (selRect.x + selRect.width + 1 < x) ||
1170             (selRect.y < y || selRect.y > y + h)) {
1171             g.drawLine(x, y + 1, x, y+h-2);
1172             if (ocean && tabPlacement == LEFT) {
1173                 g.setColor(MetalLookAndFeel.getWhite());
1174                 g.drawLine(x + 1, y, x + 1, y + h - 2);
1175             }
1176         } else {
1177             // Break line to show visual connection to selected tab
1178             g.drawLine(x, y, x, selRect.y + 1);
1179             if (selRect.y + selRect.height < y + h - 2) {
1180               g.drawLine(x, selRect.y + selRect.height + 1,
1181                          x, y+h+2);
1182             }
1183             if (ocean) {
1184                 g.setColor(MetalLookAndFeel.getWhite());
1185                 g.drawLine(x + 1, y + 1, x + 1, selRect.y + 1);
1186                 if (selRect.y + selRect.height < y + h - 2) {
1187                     g.drawLine(x + 1, selRect.y + selRect.height + 1,
1188                                x + 1, y+h+2);
1189                 }
1190             }
1191         }
1192     }
1193 
1194     protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
1195                                                int selectedIndex,
1196                                                int x, int y, int w, int h) {
1197         Rectangle selRect = selectedIndex < 0? null :
1198                                getTabBounds(selectedIndex, calcRect);
1199 
1200         g.setColor(darkShadow);
1201         // Draw unbroken line if tabs are not on RIGHT, OR
1202         // selected tab is not in run adjacent to content, OR
1203         // selected tab is not visible (SCROLL_TAB_LAYOUT)
1204         //
1205         if (tabPlacement != RIGHT || selectedIndex < 0 ||
1206              (selRect.x - 1 > w) ||
1207              (selRect.y < y || selRect.y > y + h)) {
1208             if (ocean && tabPlacement == RIGHT) {
1209                 g.setColor(oceanSelectedBorderColor);
1210             }
1211             g.drawLine(x+w-1, y, x+w-1, y+h-1);
1212         } else {
1213             // Break line to show visual connection to selected tab
1214             if (ocean) {
1215                 g.setColor(oceanSelectedBorderColor);
1216             }
1217             g.drawLine(x+w-1, y, x+w-1, selRect.y);
1218 
1219             if (selRect.y + selRect.height < y + h - 2) {
1220                 g.drawLine(x+w-1, selRect.y + selRect.height,
1221                            x+w-1, y+h-2);
1222             }
1223         }
1224     }
1225 
1226     protected int calculateMaxTabHeight( int tabPlacement ) {
1227         FontMetrics metrics = getFontMetrics();
1228         int height = metrics.getHeight();
1229         boolean tallerIcons = false;
1230 
1231         for ( int i = 0; i < tabPane.getTabCount(); ++i ) {
1232             Icon icon = tabPane.getIconAt( i );
1233             if ( icon != null ) {
1234                 if ( icon.getIconHeight() > height ) {
1235                     tallerIcons = true;
1236                     break;
1237                 }
1238             }
1239         }
1240         return super.calculateMaxTabHeight( tabPlacement ) -
1241                   (tallerIcons ? (tabInsets.top + tabInsets.bottom) : 0);
1242     }
1243 
1244 
1245     protected int getTabRunOverlay( int tabPlacement ) {
1246         // Tab runs laid out vertically should overlap
1247         // at least as much as the largest slant
1248         if ( tabPlacement == LEFT || tabPlacement == RIGHT ) {
1249             int maxTabHeight = calculateMaxTabHeight(tabPlacement);
1250             return maxTabHeight / 2;
1251         }
1252         return 0;
1253     }
1254 
1255     /**
1256      * Returns {@code true} if tab runs should be rotated.
1257      *
1258      * @param tabPlacement a tab placement
1259      * @param selectedRun a selected run
1260      * @return {@code true} if tab runs should be rotated.
1261      */
1262     protected boolean shouldRotateTabRuns( int tabPlacement, int selectedRun ) {
1263         return false;
1264     }
1265 
1266     // Don't pad last run
1267     protected boolean shouldPadTabRun( int tabPlacement, int run ) {
1268         return runCount > 1 && run < runCount - 1;
1269     }
1270 
1271     private boolean isLastInRun( int tabIndex ) {
1272         int run = getRunForTab( tabPane.getTabCount(), tabIndex );
1273         int lastIndex = lastTabInRun( tabPane.getTabCount(), run );
1274         return tabIndex == lastIndex;
1275     }
1276 
1277     /**
1278      * Returns the color to use for the specified tab.
1279      */
1280     private Color getUnselectedBackgroundAt(int index) {
1281         Color color = tabPane.getBackgroundAt(index);
1282         if (color instanceof UIResource) {
1283             if (unselectedBackground != null) {
1284                 return unselectedBackground;
1285             }
1286         }
1287         return color;
1288     }
1289 
1290     /**
1291      * Returns the tab index of JTabbedPane the mouse is currently over
1292      */
1293     int getRolloverTabIndex() {
1294         return getRolloverTab();
1295     }
1296 
1297     /**
1298      * This class should be treated as a &quot;protected&quot; inner class.
1299      * Instantiate it only within subclasses of {@code MetalTabbedPaneUI}.
1300      */
1301     public class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {
1302 
1303         /**
1304          * Constructs {@code TabbedPaneLayout}.
1305          */
1306         public TabbedPaneLayout() {
1307             MetalTabbedPaneUI.this.super();
1308         }
1309 
1310         protected void normalizeTabRuns( int tabPlacement, int tabCount,
1311                                      int start, int max ) {
1312             // Only normalize the runs for top & bottom;  normalizing
1313             // doesn't look right for Metal's vertical tabs
1314             // because the last run isn't padded and it looks odd to have
1315             // fat tabs in the first vertical runs, but slimmer ones in the
1316             // last (this effect isn't noticeable for horizontal tabs).
1317             if ( tabPlacement == TOP || tabPlacement == BOTTOM ) {
1318                 super.normalizeTabRuns( tabPlacement, tabCount, start, max );
1319             }
1320         }
1321 
1322         // Don't rotate runs!
1323         protected void rotateTabRuns( int tabPlacement, int selectedRun ) {
1324         }
1325 
1326         // Don't pad selected tab
1327         protected void padSelectedTab( int tabPlacement, int selectedIndex ) {
1328         }
1329     }
1330 
1331 }
1332