KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > plaf > basic > BasicTabbedPaneUI


1 /*
2  * @(#)BasicTabbedPaneUI.java 1.153 05/03/03
3  *
4  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.swing.plaf.basic;
9
10 import com.sun.java.swing.SwingUtilities2;
11
12 import javax.swing.*;
13 import javax.swing.event.*;
14 import javax.swing.plaf.*;
15 import javax.swing.text.View JavaDoc;
16
17 import java.awt.*;
18 import java.awt.event.*;
19 import java.beans.PropertyChangeListener JavaDoc;
20 import java.beans.PropertyChangeEvent JavaDoc;
21 import java.util.Vector JavaDoc;
22 import java.util.Hashtable JavaDoc;
23
24 import sun.swing.DefaultLookup;
25 import sun.swing.UIAction;
26
27 /**
28  * A Basic L&F implementation of TabbedPaneUI.
29  *
30  * @version 1.87 06/08/99
31  * @author Amy Fowler
32  * @author Philip Milne
33  * @author Steve Wilson
34  * @author Tom Santos
35  * @author Dave Moore
36  */

37 public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants {
38
39
40 // Instance variables initialized at installation
41

42     protected JTabbedPane tabPane;
43
44     protected Color highlight;
45     protected Color lightHighlight;
46     protected Color shadow;
47     protected Color darkShadow;
48     protected Color focus;
49     private Color selectedColor;
50
51     protected int textIconGap;
52
53     protected int tabRunOverlay;
54
55     protected Insets tabInsets;
56     protected Insets selectedTabPadInsets;
57     protected Insets tabAreaInsets;
58     protected Insets contentBorderInsets;
59     private boolean tabsOverlapBorder;
60     private boolean tabsOpaque = true;
61     private boolean contentOpaque = true;
62
63     /**
64      * As of Java 2 platform v1.3 this previously undocumented field is no
65      * longer used.
66      * Key bindings are now defined by the LookAndFeel, please refer to
67      * the key bindings specification for further details.
68      *
69      * @deprecated As of Java 2 platform v1.3.
70      */

71     @Deprecated JavaDoc
72     protected KeyStroke upKey;
73     /**
74      * As of Java 2 platform v1.3 this previously undocumented field is no
75      * longer used.
76      * Key bindings are now defined by the LookAndFeel, please refer to
77      * the key bindings specification for further details.
78      *
79      * @deprecated As of Java 2 platform v1.3.
80      */

81     @Deprecated JavaDoc
82     protected KeyStroke downKey;
83     /**
84      * As of Java 2 platform v1.3 this previously undocumented field is no
85      * longer used.
86      * Key bindings are now defined by the LookAndFeel, please refer to
87      * the key bindings specification for further details.
88      *
89      * @deprecated As of Java 2 platform v1.3.
90      */

91     @Deprecated JavaDoc
92     protected KeyStroke leftKey;
93     /**
94      * As of Java 2 platform v1.3 this previously undocumented field is no
95      * longer used.
96      * Key bindings are now defined by the LookAndFeel, please refer to
97      * the key bindings specification for further details.
98      *
99      * @deprecated As of Java 2 platform v1.3.
100      */

101     @Deprecated JavaDoc
102     protected KeyStroke rightKey;
103
104
105 // Transient variables (recalculated each time TabbedPane is layed out)
106

107     protected int tabRuns[] = new int[10];
108     protected int runCount = 0;
109     protected int selectedRun = -1;
110     protected Rectangle rects[] = new Rectangle[0];
111     protected int maxTabHeight;
112     protected int maxTabWidth;
113
114 // Listeners
115

116     protected ChangeListener JavaDoc tabChangeListener;
117     protected PropertyChangeListener JavaDoc propertyChangeListener;
118     protected MouseListener mouseListener;
119     protected FocusListener focusListener;
120
121 // Private instance data
122

123     private Insets currentPadInsets = new Insets(0,0,0,0);
124     private Insets currentTabAreaInsets = new Insets(0,0,0,0);
125
126     private Component visibleComponent;
127     // PENDING(api): See comment for ContainerHandler
128
private Vector JavaDoc htmlViews;
129
130     private Hashtable JavaDoc mnemonicToIndexMap;
131
132     /**
133      * InputMap used for mnemonics. Only non-null if the JTabbedPane has
134      * mnemonics associated with it. Lazily created in initMnemonics.
135      */

136     private InputMap mnemonicInputMap;
137
138     // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
139
private ScrollableTabSupport tabScroller;
140
141     /**
142      * A rectangle used for general layout calculations in order
143      * to avoid constructing many new Rectangles on the fly.
144      */

145     protected transient Rectangle calcRect = new Rectangle(0,0,0,0);
146
147     /**
148      * Tab that has focus.
149      */

150     private int focusIndex;
151
152     /**
153      * Combined listeners.
154      */

155     private Handler handler;
156
157     /**
158      * Index of the tab the mouse is over.
159      */

160     private int rolloverTabIndex;
161
162     /**
163      * This is set to true when a component is added/removed from the tab
164      * pane and set to false when layout happens. If true it indicates that
165      * tabRuns is not valid and shouldn't be used.
166      */

167     private boolean isRunsDirty;
168
169 // UI creation
170

171     public static ComponentUI createUI(JComponent c) {
172         return new BasicTabbedPaneUI JavaDoc();
173     }
174
175     static void loadActionMap(LazyActionMap JavaDoc map) {
176         map.put(new Actions(Actions.NEXT));
177         map.put(new Actions(Actions.PREVIOUS));
178         map.put(new Actions(Actions.RIGHT));
179         map.put(new Actions(Actions.LEFT));
180         map.put(new Actions(Actions.UP));
181         map.put(new Actions(Actions.DOWN));
182         map.put(new Actions(Actions.PAGE_UP));
183         map.put(new Actions(Actions.PAGE_DOWN));
184         map.put(new Actions(Actions.REQUEST_FOCUS));
185         map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE));
186         map.put(new Actions(Actions.SET_SELECTED));
187         map.put(new Actions(Actions.SELECT_FOCUSED));
188         map.put(new Actions(Actions.SCROLL_FORWARD));
189         map.put(new Actions(Actions.SCROLL_BACKWARD));
190     }
191
192 // UI Installation/De-installation
193

194     public void installUI(JComponent c) {
195         this.tabPane = (JTabbedPane)c;
196
197         rolloverTabIndex = -1;
198         focusIndex = -1;
199         c.setLayout(createLayoutManager());
200         installComponents();
201         installDefaults();
202         installListeners();
203         installKeyboardActions();
204     }
205
206     public void uninstallUI(JComponent c) {
207         uninstallKeyboardActions();
208         uninstallListeners();
209         uninstallDefaults();
210         uninstallComponents();
211         c.setLayout(null);
212  
213         this.tabPane = null;
214     }
215
216     /**
217      * Invoked by <code>installUI</code> to create
218      * a layout manager object to manage
219      * the <code>JTabbedPane</code>.
220      *
221      * @return a layout manager object
222      *
223      * @see TabbedPaneLayout
224      * @see javax.swing.JTabbedPane#getTabLayoutPolicy
225      */

226     protected LayoutManager createLayoutManager() {
227         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
228             return new TabbedPaneScrollLayout();
229         } else { /* WRAP_TAB_LAYOUT */
230             return new TabbedPaneLayout();
231         }
232     }
233
234     /* In an attempt to preserve backward compatibility for programs
235      * which have extended BasicTabbedPaneUI to do their own layout, the
236      * UI uses the installed layoutManager (and not tabLayoutPolicy) to
237      * determine if scrollTabLayout is enabled.
238      */

239     private boolean scrollableTabLayoutEnabled() {
240         return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
241     }
242
243     /**
244      * Creates and installs any required subcomponents for the JTabbedPane.
245      * Invoked by installUI.
246      *
247      * @since 1.4
248      */

249     protected void installComponents() {
250         if (scrollableTabLayoutEnabled()) {
251             if (tabScroller == null) {
252                 tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
253                 tabPane.add(tabScroller.viewport);
254             }
255         }
256     }
257
258     /**
259      * Creates and returns a JButton that will provide the user
260      * with a way to scroll the tabs in a particular direction. The
261      * returned JButton must be instance of UIResource.
262      *
263      * @param direction One of the SwingConstants constants:
264      * SOUTH, NORTH, EAST or WEST
265      * @return Widget for user to
266      * @see javax.swing.JTabbedPane#setTabPlacement
267      * @see javax.swing.SwingConstants
268      * @throws IllegalArgumentException if direction is not one of
269      * NORTH, SOUTH, EAST or WEST
270      * @since 1.5
271      */

272     protected JButton createScrollButton(int direction) {
273         if (direction != SOUTH && direction != NORTH && direction != EAST &&
274                                   direction != WEST) {
275             throw new IllegalArgumentException JavaDoc("Direction must be one of: " +
276                                                "SOUTH, NORTH, EAST or WEST");
277         }
278         return new ScrollableTabButton(direction);
279     }
280
281     /**
282      * Removes any installed subcomponents from the JTabbedPane.
283      * Invoked by uninstallUI.
284      *
285      * @since 1.4
286      */

287     protected void uninstallComponents() {
288         if (scrollableTabLayoutEnabled()) {
289             tabPane.remove(tabScroller.viewport);
290             tabPane.remove(tabScroller.scrollForwardButton);
291             tabPane.remove(tabScroller.scrollBackwardButton);
292             tabScroller = null;
293         }
294     }
295
296     protected void installDefaults() {
297         LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
298                                     "TabbedPane.foreground", "TabbedPane.font");
299         highlight = UIManager.getColor("TabbedPane.light");
300         lightHighlight = UIManager.getColor("TabbedPane.highlight");
301         shadow = UIManager.getColor("TabbedPane.shadow");
302         darkShadow = UIManager.getColor("TabbedPane.darkShadow");
303         focus = UIManager.getColor("TabbedPane.focus");
304         selectedColor = UIManager.getColor("TabbedPane.selected");
305
306         textIconGap = UIManager.getInt("TabbedPane.textIconGap");
307         tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
308         selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
309         tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
310     tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder");
311         contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
312         tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
313         tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
314         contentOpaque = UIManager.getBoolean("TabbedPane.contentOpaque");
315         Object JavaDoc opaque = UIManager.get("TabbedPane.opaque");
316         if (opaque == null) {
317             opaque = Boolean.FALSE;
318         }
319         LookAndFeel.installProperty(tabPane, "opaque", opaque);
320     }
321
322     protected void uninstallDefaults() {
323         highlight = null;
324         lightHighlight = null;
325         shadow = null;
326         darkShadow = null;
327         focus = null;
328         tabInsets = null;
329         selectedTabPadInsets = null;
330         tabAreaInsets = null;
331         contentBorderInsets = null;
332     }
333
334     protected void installListeners() {
335         if ((propertyChangeListener = createPropertyChangeListener()) != null) {
336             tabPane.addPropertyChangeListener(propertyChangeListener);
337         }
338         if ((tabChangeListener = createChangeListener()) != null) {
339             tabPane.addChangeListener(tabChangeListener);
340         }
341         if ((mouseListener = createMouseListener()) != null) {
342             tabPane.addMouseListener(mouseListener);
343         }
344         tabPane.addMouseMotionListener(getHandler());
345         if ((focusListener = createFocusListener()) != null) {
346             tabPane.addFocusListener(focusListener);
347         }
348         tabPane.addContainerListener(getHandler());
349         if (tabPane.getTabCount()>0) {
350             htmlViews = createHTMLVector();
351         }
352     }
353
354     protected void uninstallListeners() {
355         if (mouseListener != null) {
356             tabPane.removeMouseListener(mouseListener);
357             mouseListener = null;
358         }
359         tabPane.removeMouseMotionListener(getHandler());
360         if (focusListener != null) {
361             tabPane.removeFocusListener(focusListener);
362             focusListener = null;
363         }
364             
365         tabPane.removeContainerListener(getHandler());
366         if (htmlViews!=null) {
367             htmlViews.removeAllElements();
368             htmlViews = null;
369         }
370         if (tabChangeListener != null) {
371             tabPane.removeChangeListener(tabChangeListener);
372             tabChangeListener = null;
373         }
374         if (propertyChangeListener != null) {
375             tabPane.removePropertyChangeListener(propertyChangeListener);
376             propertyChangeListener = null;
377         }
378         handler = null;
379     }
380
381     protected MouseListener createMouseListener() {
382         return getHandler();
383     }
384
385     protected FocusListener createFocusListener() {
386         return getHandler();
387     }
388
389     protected ChangeListener JavaDoc createChangeListener() {
390         return getHandler();
391     }
392
393     protected PropertyChangeListener JavaDoc createPropertyChangeListener() {
394         return getHandler();
395     }
396
397     private Handler getHandler() {
398         if (handler == null) {
399             handler = new Handler();
400         }
401         return handler;
402     }
403
404     protected void installKeyboardActions() {
405         InputMap km = getInputMap(JComponent.
406                                   WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
407
408         SwingUtilities.replaceUIInputMap(tabPane, JComponent.
409                                          WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
410                                          km);
411         km = getInputMap(JComponent.WHEN_FOCUSED);
412         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
413
414         LazyActionMap.installLazyActionMap(tabPane, BasicTabbedPaneUI JavaDoc.class,
415                                            "TabbedPane.actionMap");
416     }
417
418     InputMap getInputMap(int condition) {
419         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
420             return (InputMap)DefaultLookup.get(tabPane, this,
421                                                "TabbedPane.ancestorInputMap");
422         }
423         else if (condition == JComponent.WHEN_FOCUSED) {
424             return (InputMap)DefaultLookup.get(tabPane, this,
425                                                "TabbedPane.focusInputMap");
426         }
427         return null;
428     }
429
430     protected void uninstallKeyboardActions() {
431         SwingUtilities.replaceUIActionMap(tabPane, null);
432         SwingUtilities.replaceUIInputMap(tabPane, JComponent.
433                                          WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
434                                          null);
435         SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED,
436                                          null);
437     }
438
439     /**
440      * Reloads the mnemonics. This should be invoked when a memonic changes,
441      * when the title of a mnemonic changes, or when tabs are added/removed.
442      */

443     private void updateMnemonics() {
444         resetMnemonics();
445         for (int counter = tabPane.getTabCount() - 1; counter >= 0;
446              counter--) {
447             int mnemonic = tabPane.getMnemonicAt(counter);
448
449             if (mnemonic > 0) {
450                 addMnemonic(counter, mnemonic);
451             }
452         }
453     }
454
455     /**
456      * Resets the mnemonics bindings to an empty state.
457      */

458     private void resetMnemonics() {
459         if (mnemonicToIndexMap != null) {
460             mnemonicToIndexMap.clear();
461             mnemonicInputMap.clear();
462         }
463     }
464
465     /**
466      * Adds the specified mnemonic at the specified index.
467      */

468     private void addMnemonic(int index, int mnemonic) {
469         if (mnemonicToIndexMap == null) {
470             initMnemonics();
471         }
472         mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, Event.ALT_MASK),
473                              "setSelectedIndex");
474         mnemonicToIndexMap.put(new Integer JavaDoc(mnemonic), new Integer JavaDoc(index));
475     }
476
477     /**
478      * Installs the state needed for mnemonics.
479      */

480     private void initMnemonics() {
481         mnemonicToIndexMap = new Hashtable JavaDoc();
482         mnemonicInputMap = new ComponentInputMapUIResource(tabPane);
483         mnemonicInputMap.setParent(SwingUtilities.getUIInputMap(tabPane,
484                               JComponent.WHEN_IN_FOCUSED_WINDOW));
485         SwingUtilities.replaceUIInputMap(tabPane,
486                               JComponent.WHEN_IN_FOCUSED_WINDOW,
487                                          mnemonicInputMap);
488     }
489
490     /**
491      * Sets the tab the mouse is over by location. This is a cover method
492      * for <code>setRolloverTab(tabForCoordinate(x, y, false))</code>.
493      */

494     private void setRolloverTab(int x, int y) {
495         // NOTE:
496
// This calls in with false otherwise it could trigger a validate,
497
// which should NOT happen if the user is only dragging the
498
// mouse around.
499
setRolloverTab(tabForCoordinate(tabPane, x, y, false));
500     }
501
502     /**
503      * Sets the tab the mouse is currently over to <code>index</code>.
504      * <code>index</code> will be -1 if the mouse is no longer over any
505      * tab. No checking is done to ensure the passed in index identifies a
506      * valid tab.
507      *
508      * @param index Index of the tab the mouse is over.
509      * @since 1.5
510      */

511     protected void setRolloverTab(int index) {
512         rolloverTabIndex = index;
513     }
514
515     /**
516      * Returns the tab the mouse is currently over, or -1 if the mouse is no
517      * longer over any tab.
518      *
519      * @param index Index of the tab the mouse is over.
520      * @since 1.5
521      */

522     protected int getRolloverTab() {
523         return rolloverTabIndex;
524     }
525
526     public Dimension getMinimumSize(JComponent c) {
527         // Default to LayoutManager's minimumLayoutSize
528
return null;
529     }
530     
531     public Dimension getMaximumSize(JComponent c) {
532         // Default to LayoutManager's maximumLayoutSize
533
return null;
534     }
535
536 // UI Rendering
537

538     public void paint(Graphics g, JComponent c) {
539         int selectedIndex = tabPane.getSelectedIndex();
540         int tabPlacement = tabPane.getTabPlacement();
541
542         ensureCurrentLayout();
543
544         // Paint content border and tab area
545
if (tabsOverlapBorder) {
546         paintContentBorder(g, tabPlacement, selectedIndex);
547     }
548         // If scrollable tabs are enabled, the tab area will be
549
// painted by the scrollable tab panel instead.
550
//
551
if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
552
paintTabArea(g, tabPlacement, selectedIndex);
553         }
554     if (!tabsOverlapBorder) {
555         paintContentBorder(g, tabPlacement, selectedIndex);
556     }
557     }
558
559     /**
560      * Paints the tabs in the tab area.
561      * Invoked by paint().
562      * The graphics parameter must be a valid <code>Graphics</code>
563      * object. Tab placement may be either:
564      * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
565      * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
566      * The selected index must be a valid tabbed pane tab index (0 to
567      * tab count - 1, inclusive) or -1 if no tab is currently selected.
568      * The handling of invalid parameters is unspecified.
569      *
570      * @param g the graphics object to use for rendering
571      * @param tabPlacement the placement for the tabs within the JTabbedPane
572      * @param selectedIndex the tab index of the selected component
573      *
574      * @since 1.4
575      */

576     protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
577         int tabCount = tabPane.getTabCount();
578
579         Rectangle iconRect = new Rectangle(),
580                   textRect = new Rectangle();
581         Rectangle clipRect = g.getClipBounds();
582
583         // Paint tabRuns of tabs from back to front
584
for (int i = runCount - 1; i >= 0; i--) {
585             int start = tabRuns[i];
586             int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
587             int end = (next != 0? next - 1: tabCount - 1);
588             for (int j = start; j <= end; j++) {
589                 if (j != selectedIndex && rects[j].intersects(clipRect)) {
590                     paintTab(g, tabPlacement, rects, j, iconRect, textRect);
591                 }
592             }
593         }
594
595         // Paint selected tab if its in the front run
596
// since it may overlap other tabs
597
if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
598             paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
599         }
600     }
601
602     protected void paintTab(Graphics g, int tabPlacement,
603                             Rectangle[] rects, int tabIndex,
604                             Rectangle iconRect, Rectangle textRect) {
605         Rectangle tabRect = rects[tabIndex];
606         int selectedIndex = tabPane.getSelectedIndex();
607         boolean isSelected = selectedIndex == tabIndex;
608         Graphics2D g2 = null;
609         Polygon cropShape = null;
610         Shape save = null;
611         int cropx = 0;
612         int cropy = 0;
613
614         if (scrollableTabLayoutEnabled()) {
615             if (g instanceof Graphics2D) {
616                 g2 = (Graphics2D)g;
617
618                 // Render visual for cropped tab edge...
619
Rectangle viewRect = tabScroller.viewport.getViewRect();
620                 int cropline;
621                 switch(tabPlacement) {
622                   case LEFT:
623                   case RIGHT:
624                     cropline = viewRect.y + viewRect.height;
625                     if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
626                         cropShape = createCroppedTabClip(tabPlacement, tabRect, cropline);
627                         cropx = tabRect.x;
628                         cropy = cropline-1;
629                     }
630                     break;
631                   case TOP:
632                   case BOTTOM:
633                   default:
634                     cropline = viewRect.x + viewRect.width;
635                     if ((tabRect.x < cropline) && (tabRect.x + tabRect.width > cropline)) {
636                         cropShape = createCroppedTabClip(tabPlacement, tabRect, cropline);
637                         cropx = cropline-1;
638                         cropy = tabRect.y;
639                     }
640                 }
641                 if (cropShape != null) {
642                     save = g2.getClip();
643                     g2.clip(cropShape);
644                 }
645             }
646         }
647
648         if (tabsOpaque || tabPane.isOpaque()) {
649             paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
650                     tabRect.width, tabRect.height, isSelected);
651         }
652
653         paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
654                        tabRect.width, tabRect.height, isSelected);
655         
656         String JavaDoc title = tabPane.getTitleAt(tabIndex);
657         Font font = tabPane.getFont();
658         FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
659         Icon icon = getIconForTab(tabIndex);
660
661         layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
662                     tabRect, iconRect, textRect, isSelected);
663
664         paintText(g, tabPlacement, font, metrics,
665                   tabIndex, title, textRect, isSelected);
666
667         paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
668
669         paintFocusIndicator(g, tabPlacement, rects, tabIndex,
670                   iconRect, textRect, isSelected);
671
672         if (cropShape != null) {
673             paintCroppedTabEdge(g, tabPlacement, tabIndex, isSelected, cropx, cropy);
674             g2.setClip(save);
675         }
676     }
677
678
679     /* This method will create and return a polygon shape for the given tab rectangle
680      * which has been cropped at the specified cropline with a torn edge visual.
681      * e.g. A "File" tab which has cropped been cropped just after the "i":
682      * -------------
683      * | ..... |
684      * | . |
685      * | ... . |
686      * | . . |
687      * | . . |
688      * | . . |
689      * --------------
690      *
691      * The x, y arrays below define the pattern used to create a "torn" edge
692      * segment which is repeated to fill the edge of the tab.
693      * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by
694      * line segments which are defined by coordinates obtained by
695      * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i]
696      * to (tab.y).
697      * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by
698      * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i]
699      * to (tab.x).
700      */

701     private int xCropLen[] = {1,1,0,0,1,1,2,2};
702     private int yCropLen[] = {0,3,3,6,6,9,9,12};
703     private static final int CROP_SEGMENT = 12;
704
705     private Polygon createCroppedTabClip(int tabPlacement, Rectangle tabRect, int cropline) {
706         int rlen = 0;
707         int start = 0;
708         int end = 0;
709         int ostart = 0;
710
711         switch(tabPlacement) {
712           case LEFT:
713           case RIGHT:
714               rlen = tabRect.width;
715               start = tabRect.x;
716               end = tabRect.x + tabRect.width;
717               ostart = tabRect.y;
718               break;
719           case TOP:
720           case BOTTOM:
721           default:
722              rlen = tabRect.height;
723              start = tabRect.y;
724              end = tabRect.y + tabRect.height;
725              ostart = tabRect.x;
726         }
727         int rcnt = rlen/CROP_SEGMENT;
728         if (rlen%CROP_SEGMENT > 0) {
729             rcnt++;
730         }
731         int npts = 2 + (rcnt*8);
732         int xp[] = new int[npts];
733         int yp[] = new int[npts];
734         int pcnt = 0;
735  
736         xp[pcnt] = ostart;
737         yp[pcnt++] = end;
738         xp[pcnt] = ostart;
739         yp[pcnt++] = start;
740         for(int i = 0; i < rcnt; i++) {
741             for(int j = 0; j < xCropLen.length; j++) {
742                 xp[pcnt] = cropline - xCropLen[j];
743                 yp[pcnt] = start + (i*CROP_SEGMENT) + yCropLen[j];
744                 if (yp[pcnt] >= end) {
745                     yp[pcnt] = end;
746                     pcnt++;
747                     break;
748                 }
749                 pcnt++;
750             }
751         }
752         if (tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM) {
753            return new Polygon(xp, yp, pcnt);
754
755         } else { // LEFT or RIGHT
756
return new Polygon(yp, xp, pcnt);
757         }
758     }
759
760     /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
761      * indicating the tab is cropped in the viewport display
762      */

763     private void paintCroppedTabEdge(Graphics g, int tabPlacement, int tabIndex,
764                                      boolean isSelected,
765                                      int x, int y) {
766         switch(tabPlacement) {
767           case LEFT:
768           case RIGHT:
769             int xx = x;
770             g.setColor(shadow);
771             while(xx <= x+rects[tabIndex].width) {
772                 for (int i=0; i < xCropLen.length; i+=2) {
773                     g.drawLine(xx+yCropLen[i],y-xCropLen[i],
774                                xx+yCropLen[i+1]-1,y-xCropLen[i+1]);
775                 }
776                 xx+=CROP_SEGMENT;
777             }
778             break;
779           case TOP:
780           case BOTTOM:
781           default:
782             int yy = y;
783             g.setColor(shadow);
784             while(yy <= y+rects[tabIndex].height) {
785                 for (int i=0; i < xCropLen.length; i+=2) {
786                     g.drawLine(x-xCropLen[i],yy+yCropLen[i],
787                                x-xCropLen[i+1],yy+yCropLen[i+1]-1);
788                 }
789                 yy+=CROP_SEGMENT;
790             }
791         }
792     }
793
794     protected void layoutLabel(int tabPlacement,
795                                FontMetrics metrics, int tabIndex,
796                                String JavaDoc title, Icon icon,
797                                Rectangle tabRect, Rectangle iconRect,
798                                Rectangle textRect, boolean isSelected ) {
799         textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
800
801         View JavaDoc v = getTextViewForTab(tabIndex);
802         if (v != null) {
803             tabPane.putClientProperty("html", v);
804         }
805
806         SwingUtilities.layoutCompoundLabel((JComponent) tabPane,
807                                            metrics, title, icon,
808                                            SwingUtilities.CENTER,
809                                            SwingUtilities.CENTER,
810                                            SwingUtilities.CENTER,
811                                            SwingUtilities.TRAILING,
812                                            tabRect,
813                                            iconRect,
814                                            textRect,
815                                            textIconGap);
816
817         tabPane.putClientProperty("html", null);
818
819         int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
820         int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
821         iconRect.x += xNudge;
822         iconRect.y += yNudge;
823         textRect.x += xNudge;
824         textRect.y += yNudge;
825     }
826
827     protected void paintIcon(Graphics g, int tabPlacement,
828                              int tabIndex, Icon icon, Rectangle iconRect,
829                              boolean isSelected ) {
830         if (icon != null) {
831             icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
832         }
833     }
834
835     protected void paintText(Graphics g, int tabPlacement,
836                              Font font, FontMetrics metrics, int tabIndex,
837                              String JavaDoc title, Rectangle textRect,
838                              boolean isSelected) {
839
840         g.setFont(font);
841
842         View JavaDoc v = getTextViewForTab(tabIndex);
843         if (v != null) {
844             // html
845
v.paint(g, textRect);
846         } else {
847             // plain text
848
int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
849
850             if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
851                 Color fg = tabPane.getForegroundAt(tabIndex);
852                 if (isSelected && (fg instanceof UIResource)) {
853                     Color selectedFG = UIManager.getColor(
854                                   "TabbedPane.selectedForeground");
855                     if (selectedFG != null) {
856                         fg = selectedFG;
857                     }
858                 }
859                 g.setColor(fg);
860                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
861                              title, mnemIndex,
862                              textRect.x, textRect.y + metrics.getAscent());
863                 
864             } else { // tab disabled
865
g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
866                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
867                              title, mnemIndex,
868                              textRect.x, textRect.y + metrics.getAscent());
869                 g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
870                 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
871                              title, mnemIndex,
872                              textRect.x - 1, textRect.y + metrics.getAscent() - 1);
873
874             }
875         }
876     }
877
878
879     protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
880         Rectangle tabRect = rects[tabIndex];
881      &nb