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™
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 */
89 public static ComponentUI createUI( JComponent x ) {
90 return new MetalTabbedPaneUI();
91 }
92
93 protected LayoutManager createLayoutManager() {
94 if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
95 return super.createLayoutManager();
96 }
97 return new TabbedPaneLayout();
98 }
99
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
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 */
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 */
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 */
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 */
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 */
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 */
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
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
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&F.
870 */
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&F.
878 */
879 protected int getTabLabelShiftY( int tabPlacement, int tabIndex, boolean isSelected ) {
880 return 0;
881 }
882
883 /**
884 * {@inheritDoc}
885 *
886 * @since 1.6
887 */
888 protected int getBaselineOffset() {
889 return 0;
890 }
891
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 */
939 protected void paintHighlightBelowTab( ) {
940
941 }
942
943
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
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 "protected" 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 }
--- EOF ---