KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jgoodies > looks > plastic > PlasticTabbedPaneUI


1 /*
2  * Copyright (c) 2001-2005 JGoodies Karsten Lentzsch. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * o Redistributions of source code must retain the above copyright notice,
8  * this list of conditions and the following disclaimer.
9  *
10  * o Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * o Neither the name of JGoodies Karsten Lentzsch nor the names of
15  * its contributors may be used to endorse or promote products derived
16  * from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */

30
31 package com.jgoodies.looks.plastic;
32
33 import java.awt.Color JavaDoc;
34 import java.awt.Component JavaDoc;
35 import java.awt.Container JavaDoc;
36 import java.awt.Dimension JavaDoc;
37 import java.awt.Font JavaDoc;
38 import java.awt.FontMetrics JavaDoc;
39 import java.awt.Graphics JavaDoc;
40 import java.awt.Graphics2D JavaDoc;
41 import java.awt.Insets JavaDoc;
42 import java.awt.LayoutManager JavaDoc;
43 import java.awt.Point JavaDoc;
44 import java.awt.Polygon JavaDoc;
45 import java.awt.Rectangle JavaDoc;
46 import java.awt.Shape JavaDoc;
47 import java.awt.event.ActionEvent JavaDoc;
48 import java.awt.event.ActionListener JavaDoc;
49 import java.awt.event.MouseEvent JavaDoc;
50 import java.beans.PropertyChangeEvent JavaDoc;
51 import java.beans.PropertyChangeListener JavaDoc;
52
53 import javax.swing.AbstractAction JavaDoc;
54 import javax.swing.Action JavaDoc;
55 import javax.swing.ActionMap JavaDoc;
56 import javax.swing.Icon JavaDoc;
57 import javax.swing.JButton JavaDoc;
58 import javax.swing.JComponent JavaDoc;
59 import javax.swing.JPanel JavaDoc;
60 import javax.swing.JTabbedPane JavaDoc;
61 import javax.swing.JViewport JavaDoc;
62 import javax.swing.SwingConstants JavaDoc;
63 import javax.swing.SwingUtilities JavaDoc;
64 import javax.swing.UIManager JavaDoc;
65 import javax.swing.event.ChangeEvent JavaDoc;
66 import javax.swing.event.ChangeListener JavaDoc;
67 import javax.swing.plaf.ComponentUI JavaDoc;
68 import javax.swing.plaf.UIResource JavaDoc;
69 import javax.swing.plaf.basic.BasicTabbedPaneUI JavaDoc;
70 import javax.swing.plaf.metal.MetalTabbedPaneUI JavaDoc;
71 import javax.swing.text.View JavaDoc;
72
73 import com.jgoodies.looks.LookUtils;
74 import com.jgoodies.looks.Options;
75
76 /**
77  * The JGoodies Plastic Look&Feel implementation of
78  * <code>TabbedPaneUI</code>. It differs from its superclass
79  * <code>MetalTabbedPaneUI</code> in that it paints new tab shapes,
80  * provides two options, and supports ClearLook.
81  * <p>
82  * You can enable or disable icons in tabs globally via
83  * com.jgoodies.looks.Options.setTabIconsEnabled(boolean).
84  * <p>
85  * To disable the content border set
86  * <pre>
87  * JTabbedPane tabbedPane = new JTabbedPane();
88  * tabbedPane.putClientProperty(Option.NO_CONTENT_BORDER_KEY, Boolean.TRUE);
89  * </pre>
90  * To paint embedded tabs use
91  * <pre>
92  * JTabbedPane tabbedPane = new JTabbedPane();
93  * tabbedPane.putClientProperty(Option.EMBEDDED_TABS_KEY, Boolean.TRUE);
94  * </pre>
95  * <p>
96  * There's a special mode that helps you detect content borders in
97  * heavily wrapped component hierarchies - such as the NetBeans IDE.
98  * In this marked mode the content border is painted as a Magenta line.
99  * You can enable this mode by setting the System property
100  * <tt>markContentBorders</tt> to <tt>true</tt>; in a command line:
101  * <pre>
102  * java -DmarkContentBorders=true
103  * </pre>
104  *
105  * @author Karsten Lentzsch
106  * @author Torge Husfeldt
107  * @author Andrej Golovnin
108  * @version $Revision: 1.3 $
109  *
110  * @see Options
111  */

112 public final class PlasticTabbedPaneUI extends MetalTabbedPaneUI JavaDoc {
113
114
115     // State ******************************************************************
116

117     /**
118      * Describes if tabs are painted with or without icons.
119      */

120     private static boolean isTabIconsEnabled = Options.isTabIconsEnabled();
121     
122     /**
123      * Describes if we paint no content border or not; is false by default.
124      * You can disable the content border by setting the client property
125      * Options.NO_CONTENT_BORDER_KEY to Boolean.TRUE;
126      */

127     private Boolean JavaDoc noContentBorder;
128
129     /**
130      * Describes if we paint tabs in an embedded style that is with
131      * less decoration; this is false by default.
132      * You can enable the embedded tabs style by setting the client property
133      * Options.EMBEDDED_TABS_KEY to Boolean.TRUE.
134      */

135     private Boolean JavaDoc embeddedTabs;
136
137     /**
138      * Holds the renderer that is used to render the tabs.
139      */

140     private AbstractRenderer renderer;
141
142
143     /** For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT */
144     private ScrollableTabSupport tabScroller;
145     
146     /**
147      * Creates the <code>PlasticTabbedPaneUI</code>.
148      *
149      * @see javax.swing.plaf.ComponentUI#createUI(JComponent)
150      */

151     public static ComponentUI JavaDoc createUI(JComponent JavaDoc tabPane) {
152         return new PlasticTabbedPaneUI();
153     }
154
155     /**
156      * Installs the UI.
157      *
158      * @see javax.swing.plaf.ComponentUI#installUI(JComponent)
159      */

160     public void installUI(JComponent JavaDoc c) {
161         super.installUI(c);
162         embeddedTabs = (Boolean JavaDoc) c.getClientProperty(Options.EMBEDDED_TABS_KEY);
163         noContentBorder = (Boolean JavaDoc) c.getClientProperty(Options.NO_CONTENT_BORDER_KEY);
164         renderer = createRenderer(tabPane);
165     }
166
167     /**
168      * Uninstalls the UI.
169      * @see javax.swing.plaf.ComponentUI#uninstallUI(JComponent)
170      */

171     public void uninstallUI(JComponent JavaDoc c) {
172         renderer = null;
173         super.uninstallUI(c);
174     }
175
176     /**
177      * Creates and installs any required subcomponents for the JTabbedPane.
178      * Invoked by installUI.
179      * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installComponents()
180      */

181     protected void installComponents() {
182         if (scrollableTabLayoutEnabled()) {
183             if (tabScroller == null) {
184                 tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
185                 tabPane.add(tabScroller.viewport);
186             }
187         }
188     }
189     
190     /**
191      * Removes any installed subcomponents from the JTabbedPane.
192      * Invoked by uninstallUI.
193      * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallComponents()
194      */

195     protected void uninstallComponents() {
196         if (scrollableTabLayoutEnabled()) {
197             tabPane.remove(tabScroller.viewport);
198             tabPane.remove(tabScroller.scrollForwardButton);
199             tabPane.remove(tabScroller.scrollBackwardButton);
200             tabScroller = null;
201         }
202     }
203     
204     protected void installListeners() {
205         super.installListeners();
206         // if the layout policy is the SCROLL_TAB_LAYOUT, the super class
207
// will install the mouse listener on tabPane instead of
208
// tabScroller#tabPanel and there is no way to prevent this.
209
// That's why the mouse listener must be removed from tabPane and
210
// added to tabScroller#tabPanel when the scroll tab layout is enabled.
211
// This applies only to JDK 1.4!!!
212
if ((mouseListener != null) && (LookUtils.IS_JAVA_1_4)) {
213             if (scrollableTabLayoutEnabled()) {
214                 tabPane.removeMouseListener(mouseListener);
215                 tabScroller.tabPanel.addMouseListener(mouseListener);
216             }
217         }
218     }
219     
220     protected void uninstallListeners() {
221         if ((mouseListener != null) && (LookUtils.IS_JAVA_1_4)) {
222             if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
223
tabScroller.tabPanel.removeMouseListener(mouseListener);
224             } else { // WRAP_TAB_LAYOUT
225
tabPane.removeMouseListener(mouseListener);
226             }
227             mouseListener = null;
228         }
229         super.uninstallListeners();
230     }
231     
232     protected void installKeyboardActions() {
233         super.installKeyboardActions();
234         // if the layout policy is the SCROLL_TAB_LAYOUT, then replace
235
// the forward and backward actions, installed in the action map
236
// in the supper class, by our own.
237
if (scrollableTabLayoutEnabled()) {
238             Action JavaDoc forwardAction = new ScrollTabsForwardAction();
239             Action JavaDoc backwardAction = new ScrollTabsBackwardAction();
240             ActionMap JavaDoc am = SwingUtilities.getUIActionMap(tabPane);
241             am.put("scrollTabsForwardAction", forwardAction);
242             am.put("scrollTabsBackwardAction", backwardAction);
243             tabScroller.scrollForwardButton.setAction(forwardAction);
244             tabScroller.scrollBackwardButton.setAction(backwardAction);
245         }
246     }
247     
248     /**
249      * Checks and answers if content border will be painted.
250      * This is controlled by the component's client property
251      * Options.NO_CONTENT_BORDER or Options.EMBEDDED.
252      */

253     private boolean hasNoContentBorder() {
254         return Boolean.TRUE.equals(noContentBorder);
255     }
256
257     /**
258      * Checks and answers if tabs are painted with minimal decoration.
259      */

260     private boolean hasEmbeddedTabs() {
261         return Boolean.TRUE.equals(embeddedTabs);
262     }
263
264     /**
265      * Creates the renderer used to lay out and paint the tabs.
266      * @param tabbedPane the UIs component
267      * @return AbstractRenderer the renderer that will be used to paint
268      */

269     private AbstractRenderer createRenderer(JTabbedPane JavaDoc tabbedPane) {
270         return hasEmbeddedTabs()
271                 ? AbstractRenderer.createEmbeddedRenderer(tabbedPane)
272                 : AbstractRenderer.createRenderer(tabPane);
273     }
274
275     /**
276      * Creates and answer a handler that listens to property changes.
277      * Unlike the superclass BasicTabbedPane, the PlasticTabbedPaneUI
278      * uses an extended Handler.
279      */

280     protected PropertyChangeListener JavaDoc createPropertyChangeListener() {
281         return new MyPropertyChangeHandler();
282     }
283
284     protected ChangeListener JavaDoc createChangeListener() {
285         return new TabSelectionHandler();
286     }
287     
288     /*
289      * Private helper method for the next three methods.
290      */

291     private void doLayout() {
292         tabPane.revalidate();
293         tabPane.repaint();
294     }
295
296      /**
297       * Updates the renderer and layout. This message is sent by
298       * my PropertyChangeHandler whenever the tab placement changes.
299       */

300      private void tabPlacementChanged() {
301          renderer = createRenderer(tabPane);
302          if (scrollableTabLayoutEnabled()) {
303              tabScroller.createButtons();
304          }
305          doLayout();
306      }
307
308     /**
309      * Updates the embedded tabs property. This message is sent by
310      * my PropertyChangeHandler whenever the embedded tabs property changes.
311      */

312     private void embeddedTabsPropertyChanged(Boolean JavaDoc newValue) {
313         embeddedTabs = newValue;
314         renderer = createRenderer(tabPane);
315         doLayout();
316     }
317
318      /**
319       * Updates the no content border property. This message is sent
320       * by my PropertyChangeHandler whenever the noContentBorder
321       * property changes.
322       */

323      private void noContentBorderPropertyChanged(Boolean JavaDoc newValue) {
324          noContentBorder = newValue;
325          tabPane.repaint();
326      }
327
328      public void paint(Graphics JavaDoc g, JComponent JavaDoc c) {
329          int selectedIndex = tabPane.getSelectedIndex();
330          int tabPlacement = tabPane.getTabPlacement();
331
332          ensureCurrentLayout();
333
334          // Paint tab area
335
// If scrollable tabs are enabled, the tab area will be
336
// painted by the scrollable tab panel instead.
337
//
338
if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
339
paintTabArea(g, tabPlacement, selectedIndex);
340          }
341      
342          // Paint content border
343
paintContentBorder(g, tabPlacement, selectedIndex);
344      }
345      
346      protected void paintTab(Graphics JavaDoc g, int tabPlacement, Rectangle JavaDoc[] rects,
347              int tabIndex, Rectangle JavaDoc iconRect, Rectangle JavaDoc textRect) {
348          Rectangle JavaDoc tabRect = rects[tabIndex];
349          int selectedIndex = tabPane.getSelectedIndex();
350          boolean isSelected = selectedIndex == tabIndex;
351          Graphics2D JavaDoc g2 = null;
352          Polygon JavaDoc cropShape = null;
353          Shape JavaDoc save = null;
354          int cropx = 0;
355          int cropy = 0;
356
357          if (scrollableTabLayoutEnabled()) {
358              if (g instanceof Graphics2D JavaDoc) {
359                  g2 = (Graphics2D JavaDoc) g;
360
361                  // Render visual for cropped tab edge...
362
Rectangle JavaDoc viewRect = tabScroller.viewport.getViewRect();
363                  int cropline;
364                  switch (tabPlacement) {
365                  case LEFT:
366                  case RIGHT:
367                      cropline = viewRect.y + viewRect.height;
368                      if ((tabRect.y < cropline)
369                              && (tabRect.y + tabRect.height > cropline)) {
370                          cropShape = createCroppedTabClip(tabPlacement, tabRect,
371                                  cropline);
372                          cropx = tabRect.x;
373                          cropy = cropline - 1;
374                      }
375                      break;
376                  case TOP:
377                  case BOTTOM:
378                  default:
379                      cropline = viewRect.x + viewRect.width;
380                      if ((tabRect.x < cropline)
381                              && (tabRect.x + tabRect.width > cropline)) {
382                          cropShape = createCroppedTabClip(tabPlacement, tabRect,
383                                  cropline);
384                          cropx = cropline - 1;
385                          cropy = tabRect.y;
386                      }
387                  }
388                  if (cropShape != null) {
389                      save = g2.getClip();
390                      g2.clip(cropShape);
391                  }
392              }
393          }
394
395          paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
396                  tabRect.width, tabRect.height, isSelected);
397
398          paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
399                  tabRect.width, tabRect.height, isSelected);
400
401          String JavaDoc title = tabPane.getTitleAt(tabIndex);
402          Font JavaDoc font = tabPane.getFont();
403          FontMetrics JavaDoc metrics = g.getFontMetrics(font);
404          Icon JavaDoc icon = getIconForTab(tabIndex);
405
406          layoutLabel(tabPlacement, metrics, tabIndex, title, icon, tabRect,
407                  iconRect, textRect, isSelected);
408
409          paintText(g, tabPlacement, font, metrics, tabIndex, title, textRect,
410                  isSelected);
411
412          paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
413
414          paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect,
415                  textRect, isSelected);
416
417          if (cropShape != null) {
418              paintCroppedTabEdge(g, tabPlacement, tabIndex, isSelected, cropx,
419                      cropy);
420              g2.setClip(save);
421          }
422      }
423
424      /*
425       * This method will create and return a polygon shape for the given tab
426       * rectangle which has been cropped at the specified cropline with a torn
427       * edge visual. e.g. A "File" tab which has cropped been cropped just after
428       * the "i":
429       * -------------
430       * | ..... |
431       * | . |
432       * | ... . |
433       * | . . |
434       * | . . |
435       * | . . |
436       * --------------
437       *
438       * The x, y arrays below define the pattern used to create a "torn" edge
439       * segment which is repeated to fill the edge of the tab. For tabs placed on
440       * TOP and BOTTOM, this righthand torn edge is created by line segments
441       * which are defined by coordinates obtained by subtracting xCropLen[i] from
442       * (tab.x + tab.width) and adding yCroplen[i] to (tab.y). For tabs placed on
443       * LEFT or RIGHT, the bottom torn edge is created by subtracting xCropLen[i]
444       * from (tab.y + tab.height) and adding yCropLen[i] to (tab.x).
445       */

446      private int xCropLen[] = { 1, 1, 0, 0, 1, 1, 2, 2 };
447
448      private int yCropLen[] = { 0, 3, 3, 6, 6, 9, 9, 12 };
449
450      private static final int CROP_SEGMENT = 12;
451
452      private Polygon JavaDoc createCroppedTabClip(int tabPlacement, Rectangle JavaDoc tabRect,
453              int cropline) {
454          int rlen = 0;
455          int start = 0;
456          int end = 0;
457          int ostart = 0;
458
459          switch (tabPlacement) {
460              case LEFT:
461              case RIGHT:
462                  rlen = tabRect.width;
463                  start = tabRect.x;
464                  end = tabRect.x + tabRect.width;
465                  ostart = tabRect.y;
466                  break;
467              case TOP:
468              case BOTTOM:
469              default:
470                  rlen = tabRect.height;
471                  start = tabRect.y;
472                  end = tabRect.y + tabRect.height;
473                  ostart = tabRect.x;
474          }
475          int rcnt = rlen / CROP_SEGMENT;
476          if (rlen % CROP_SEGMENT > 0) {
477              rcnt++;
478          }
479          int npts = 2 + (rcnt * 8);
480          int xp[] = new int[npts];
481          int yp[] = new int[npts];
482          int pcnt = 0;
483
484          xp[pcnt] = ostart;
485          yp[pcnt++] = end;
486          xp[pcnt] = ostart;
487          yp[pcnt++] = start;
488          for (int i = 0; i < rcnt; i++) {
489              for (int j = 0; j < xCropLen.length; j++) {
490                  xp[pcnt] = cropline - xCropLen[j];
491                  yp[pcnt] = start + (i * CROP_SEGMENT) + yCropLen[j];
492                  if (yp[pcnt] >= end) {
493                      yp[pcnt] = end;
494                      pcnt++;
495                      break;
496                  }
497                  pcnt++;
498              }
499          }
500          if (tabPlacement == SwingConstants.TOP
501                  || tabPlacement == SwingConstants.BOTTOM) {
502              return new Polygon JavaDoc(xp, yp, pcnt);
503
504          }
505          //LEFT or RIGHT
506
return new Polygon JavaDoc(yp, xp, pcnt);
507      }
508
509      /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
510       * indicating the tab is cropped in the viewport display
511       */

512      private void paintCroppedTabEdge(Graphics JavaDoc g, int tabPlacement,
513              int tabIndex, boolean isSelected, int x, int y) {
514          switch (tabPlacement) {
515              case LEFT:
516              case RIGHT:
517                  int xx = x;
518                  g.setColor(shadow);
519                  while (xx <= x + rects[tabIndex].width) {
520                      for (int i = 0; i < xCropLen.length; i += 2) {
521                          g.drawLine(xx + yCropLen[i], y - xCropLen[i], xx
522                                  + yCropLen[i + 1] - 1, y - xCropLen[i + 1]);
523                      }
524                      xx += CROP_SEGMENT;
525                  }
526                  break;
527              case TOP:
528              case BOTTOM:
529              default:
530                  int yy = y;
531                  g.setColor(shadow);
532                  while (yy <= y + rects[tabIndex].height) {
533                      for (int i = 0; i < xCropLen.length; i += 2) {
534                          g.drawLine(x - xCropLen[i], yy + yCropLen[i], x
535                                  - xCropLen[i + 1], yy + yCropLen[i + 1] - 1);
536                      }
537                      yy += CROP_SEGMENT;
538                  }
539          }
540      }
541
542      private void ensureCurrentLayout() {
543          if (!tabPane.isValid()) {
544              tabPane.validate();
545          }
546          /* If tabPane doesn't have a peer yet, the validate() call will
547           * silently fail. We handle that by forcing a layout if tabPane
548           * is still invalid. See bug 4237677.
549           */

550          if (!tabPane.isValid()) {
551              TabbedPaneLayout layout = (TabbedPaneLayout) tabPane.getLayout();
552              layout.calculateLayoutInfo();
553          }
554      }
555      
556      /**
557       * Returns the tab index which intersects the specified point
558       * in the JTabbedPane's coordinate space.
559       */

560      public int tabForCoordinate(JTabbedPane JavaDoc pane, int x, int y) {
561          ensureCurrentLayout();
562          Point JavaDoc p = new Point JavaDoc(x, y);
563      
564          if (scrollableTabLayoutEnabled()) {
565              translatePointToTabPanel(x, y, p);
566              Rectangle JavaDoc viewRect = tabScroller.viewport.getViewRect();
567              if (!viewRect.contains(p)) {
568                  return -1;
569              }
570          }
571          int tabCount = tabPane.getTabCount();
572          for (int i = 0; i < tabCount; i++) {
573              if (rects[i].contains(p.x, p.y)) {
574                  return i;
575              }
576          }
577          return -1;
578      }
579
580      protected Rectangle JavaDoc getTabBounds(int tabIndex, Rectangle JavaDoc dest) {
581          dest.width = rects[tabIndex].width;
582          dest.height = rects[tabIndex].height;
583          if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
584
// Need to translate coordinates based on viewport location &
585
// view position
586
Point JavaDoc vpp = tabScroller.viewport.getLocation();
587              Point JavaDoc viewp = tabScroller.viewport.getViewPosition();
588              dest.x = rects[tabIndex].x + vpp.x - viewp.x;
589              dest.y = rects[tabIndex].y + vpp.y - viewp.y;
590          } else { // WRAP_TAB_LAYOUT
591
dest.x = rects[tabIndex].x;
592              dest.y = rects[tabIndex].y;
593          }
594          return dest;
595      }
596      
597      /**
598       * Returns the index of the tab closest to the passed in location, note
599       * that the returned tab may not contain the location x,y.
600       */

601      private int getClosestTab(int x, int y) {
602          int min = 0;
603          int tabCount = Math.min(rects.length, tabPane.getTabCount());
604          int max = tabCount;
605          int tabPlacement = tabPane.getTabPlacement();
606          boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
607          int want = (useX) ? x : y;
608
609          while (min != max) {
610              int current = (max + min) / 2;
611              int minLoc;
612              int maxLoc;
613
614              if (useX) {
615                  minLoc = rects[current].x;
616                  maxLoc = minLoc + rects[current].width;
617              } else {
618                  minLoc = rects[current].y;
619                  maxLoc = minLoc + rects[current].height;
620              }
621              if (want < minLoc) {
622                  max = current;
623                  if (min == max) {
624                      return Math.max(0, current - 1);
625                  }
626              } else if (want >= maxLoc) {
627                  min = current;
628                  if (max - min <= 1) {
629                      return Math.max(current + 1, tabCount - 1);
630                  }
631              } else {
632                  return current;
633              }
634          }
635          return min;
636      }
637      
638      /**
639       * Returns a point which is translated from the specified point in the
640       * JTabbedPane's coordinate space to the coordinate space of the
641       * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY.
642       */

643      private Point JavaDoc translatePointToTabPanel(int srcx, int srcy, Point JavaDoc dest) {
644          Point JavaDoc vpp = tabScroller.viewport.getLocation();
645          Point JavaDoc viewp = tabScroller.viewport.getViewPosition();
646          dest.x = srcx - vpp.x + viewp.x;
647          dest.y = srcy - vpp.y + viewp.y;
648          return dest;
649      }
650      
651     protected void paintTabArea(Graphics JavaDoc g, int tabPlacement, int selectedIndex) {
652         int tabCount = tabPane.getTabCount();
653
654         Rectangle JavaDoc iconRect = new Rectangle JavaDoc(),
655                   textRect = new Rectangle JavaDoc();
656         Rectangle JavaDoc clipRect = g.getClipBounds();
657
658         // Paint tabRuns of tabs from back to front
659
for (int i = runCount - 1; i >= 0; i--) {
660             int start = tabRuns[i];
661             int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
662             int end = (next != 0? next - 1: tabCount - 1);
663             for (int j = end; j >= start; j--) {
664                 if (j != selectedIndex && rects[j].intersects(clipRect)) {
665                     paintTab(g, tabPlacement, rects, j, iconRect, textRect);
666                 }
667             }
668         }
669
670         // Paint selected tab if its in the front run
671
// since it may overlap other tabs
672
if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
673             paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
674         }
675     }
676     
677     /*
678      * Copied here from super(super)class to avoid labels being centered on
679      * vertical tab runs if they consist of icon and text
680      */

681     protected void layoutLabel(
682         int tabPlacement,
683         FontMetrics JavaDoc metrics,
684         int tabIndex,
685         String JavaDoc title,
686         Icon JavaDoc icon,
687         Rectangle JavaDoc tabRect,
688         Rectangle JavaDoc iconRect,
689         Rectangle JavaDoc textRect,
690         boolean isSelected) {
691         textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
692         //fix of issue #4
693
View JavaDoc v = getTextViewForTab(tabIndex);
694         if (v != null) {
695             tabPane.putClientProperty("html", v);
696         }
697
698         Rectangle JavaDoc calcRectangle = new Rectangle JavaDoc(tabRect);
699         if (isSelected) {
700             Insets JavaDoc calcInsets = getSelectedTabPadInsets(tabPlacement);
701             calcRectangle.x += calcInsets.left;
702             calcRectangle.y += calcInsets.top;
703             calcRectangle.width -= calcInsets.left + calcInsets.right ;
704             calcRectangle.height -= calcInsets.bottom + calcInsets.top;
705         }
706         int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
707         int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
708         if ((tabPlacement == RIGHT || tabPlacement == LEFT) && icon != null && title != null && !title.equals("")) {
709             SwingUtilities.layoutCompoundLabel(
710                 tabPane,
711                 metrics,
712                 title,
713                 icon,
714                 SwingConstants.CENTER,
715                 SwingConstants.LEFT,
716                 SwingConstants.CENTER,
717                 SwingConstants.TRAILING,
718                 calcRectangle,
719                 iconRect,
720                 textRect,
721                 textIconGap);
722             xNudge += 4;
723         } else {
724             SwingUtilities.layoutCompoundLabel(
725                 tabPane,
726                 metrics,
727                 title,
728                 icon,
729                 SwingConstants.CENTER,
730                 SwingConstants.CENTER,
731                 SwingConstants.CENTER,
732                 SwingConstants.TRAILING,
733                 calcRectangle,
734                 iconRect,
735                 textRect,
736                 textIconGap);
737                 iconRect.y += calcRectangle.height %2;
738         }
739
740         //fix of issue #4
741
tabPane.putClientProperty("html", null);
742         
743         iconRect.x += xNudge;
744         iconRect.y += yNudge;
745         textRect.x += xNudge;
746         textRect.y += yNudge;
747     }
748
749     /**
750      * Answers the icon for the tab with the specified index.
751      * In case, we have globally switched of the use tab icons,
752      * we answer <code>null</code> if and only if we have a title.
753      */

754     protected Icon JavaDoc getIconForTab(int tabIndex) {
755         String JavaDoc title = tabPane.getTitleAt(tabIndex);
756         boolean hasTitle = (title != null) && (title.length() > 0);
757         return !isTabIconsEnabled && hasTitle
758                     ? null
759                     : super.getIconForTab(tabIndex);
760     }
761
762     /**
763      * Creates the layout manager used to set the tab's bounds.
764      */

765     protected LayoutManager JavaDoc createLayoutManager() {
766         if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
767             return new TabbedPaneScrollLayout();
768         }
769         /* WRAP_TAB_LAYOUT */
770         return new TabbedPaneLayout();
771     }
772
773     /* In an attempt to preserve backward compatibility for programs
774      * which have extended BasicTabbedPaneUI to do their own layout, the
775      * UI uses the installed layoutManager (and not tabLayoutPolicy) to
776      * determine if scrollTabLayout is enabled.
777      */

778     private boolean scrollableTabLayoutEnabled() {
779         return tabPane.getLayout() instanceof TabbedPaneScrollLayout;
780     }
781     
782     protected boolean isTabInFirstRun(int tabIndex) {
783         return getRunForTab(tabPane.getTabCount(), tabIndex) == 0;
784     }
785
786     protected void paintContentBorder(Graphics JavaDoc g, int tabPlacement, int selectedIndex) {
787         int width = tabPane.getWidth();
788         int height = tabPane.getHeight();
789         Insets JavaDoc insets = tabPane.getInsets();
790
791         int x = insets.left;
792         int y = insets.top;
793         int w = width - insets.right - insets.left;
794         int h = height - insets.top - insets.bottom;
795
796         switch (tabPlacement) {
797             case LEFT :
798                 x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
799                 w -= (x - insets.left);
800                 break;
801             case RIGHT :
802                 w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
803                 break;
804             case BOTTOM :
805                 h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
806                 break;
807             case TOP :
808        &n