KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > swing > tabcontrol > plaf > ScrollingTabLayoutModel


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.swing.tabcontrol.plaf;
20
21 import org.netbeans.swing.tabcontrol.TabDataModel;
22
23 import javax.swing.*;
24 import java.awt.*;
25 import java.util.Arrays JavaDoc;
26
27 /*
28  * ScrollingTabLayoutModel.java
29  *
30  * Created on December 5, 2003, 5:16 PM
31  */

32
33 /**
34  * Layout model which manages an offset into a set of scrollable tabs, and
35  * recalculates its layout on a change. Also handles adding extra pixels to the
36  * selected tab if necessary. Basics of how it works:
37  * <p>
38  * Wrapppers a DefaultTabLayoutModel, which can simply calculate tab widths and
39  * 0 based positions. Listens to the data model for changes, and sets a flag
40  * when a change happens to mark the cached widths and positions as dirty. On
41  * any call to fetch sizes, first checks if the cached values are good,
42  * recalculates if needed, and returns the result.
43  *
44  * @author Tim Boudreau
45  */

46 public final class ScrollingTabLayoutModel implements TabLayoutModel {
47     /**
48      * The index of the first clipped, visible tab, or -1 if the first tab
49      * should not be clippped
50      */

51     private int offset = -1;
52     /**
53      * The wrapped DefaultTabLayoutModel which will give us pure numbers for the
54      * desired width of tabs
55      */

56     private TabLayoutModel wrapped;
57     /**
58      * Flag indicating that any call to get a value should trigger recalculation
59      * of the cached values
60      */

61     private boolean changed = true;
62     /**
63      * The tabDataModel, which we occasionally need to get data from
64      */

65     TabDataModel mdl;
66     /**
67      * The selection model we will get the current selection from when we need
68      * to ensure it is visible
69      */

70     SingleSelectionModel sel;
71     /**
72      * Holds the value of the tab that should be made visible if makeVisible is
73      * called before the component has a valid (>0) width. If not -1, a call to
74      * setWidth() will trigger a call to makeVisible with this value.
75      */

76     private int makeVisibleTab = -1;
77     /**
78      * Integer count of pixels that should be added to the width of the selected
79      * tab. They will be subtracted from the surrounding tabs
80      */

81     int pixelsToAddToSelection = 0;
82     /**
83      * Stores the value of whether the final tab is clipped. Recalculated in
84      * <code>change()</code>
85      */

86     private boolean lastTabClipped = false;
87     /**
88      * Cached index of the first visible tab
89      */

90     private int firstVisibleTab = -1;
91     /**
92      * Cached index of the last visible tab
93      */

94     private int lastVisibleTab = -1;
95     /**
96      * The last known width for which values were calculated
97      */

98     private int width = -1;
99     /**
100      * Cache of the widths of tabs that *are* onscreen. This will always have a
101      * length of (lastVisibleTab + 1) - firstVisibleTab.
102      */

103     private int[] widths = null;
104
105     /**
106      * Creates a new instance of ScrollingTabLayoutModel
107      */

108     public ScrollingTabLayoutModel(TabLayoutModel wrapped,
109                                    SingleSelectionModel sel, TabDataModel mdl) {
110         this.wrapped = wrapped;
111         this.mdl = mdl;
112         this.sel = sel;
113     }
114
115     public ScrollingTabLayoutModel(TabLayoutModel wrapped,
116                                    SingleSelectionModel sel, TabDataModel mdl,
117                                    int minimumXposition) {
118         this(wrapped, sel, mdl);
119         this.minimumXposition = minimumXposition;
120     }
121
122     public void setMinimumXposition (int x) {
123         this.minimumXposition = x;
124         setChanged(true);
125     }
126
127     /**
128      * Some UIs will want to make the selected tab a little wider than the
129      * rest.
130      * @param i
131      */

132     public void setPixelsToAddToSelection (int i) {
133         pixelsToAddToSelection = i;
134         setChanged (true);
135     }
136
137     private int minimumXposition = 0;
138
139     /**
140      * External operations on the selection or data model can invalidate cached
141      * widths. The UI will listen for such changes and call this method if the
142      * data we have cached is probably no good anymore.
143      */

144     public void clearCachedData() {
145         setChanged(true);
146     }
147
148     /**
149      * Convenience getter for the "wrapped" model which will give us "pure"
150      * numbers regarding the widths of tabs
151      */

152     private TabLayoutModel getWrapped() {
153         return wrapped;
154     }
155
156     /**
157      * Get the offset - the number of tabs that are scrolled over. The default
158      * value is -1, which means no tabs are scrolled off to the left. 0 means
159      * the first tab is visible but clipped...and so forth
160      */

161     public int getOffset() {
162         if (mdl.size() <= 1) {
163             return -1;
164         }
165         return offset;
166     }
167
168     /**
169      * Called to recalculate cached values the first time a value that needs to
170      * be calculated is requested, after some change that invalidates the cached
171      * values
172      */

173     private void change() {
174         if (mdl.size() == 0) {
175             //no tabs, do nothing
176
widths = new int[0];
177             updateActions();
178             setChanged(false);
179             return;
180         }
181         //Create an array that will hold precalculated widths until something
182
//changes
183
if (widths == null || widths.length != mdl.size()) {
184             widths = new int[mdl.size()];
185             //Fill our array with 0's - any tabs not visible should get 0 width
186
}
187         Arrays.fill(widths, 0);
188
189         if (widths.length == 1) {
190             //there's only one tab, get rid of any offset - otherwise there's
191
//no way to ever make the close button show because it won't be
192
//able to be scrolled
193
offset = -1;
194         }
195         
196         //Handle throws case where we don't really even have enough room to
197
//display one tab, by centering the clipped selected tag on what little
198
//space we have. The UI will make sure it looks clipped.
199
if (width < getMinimumLeftClippedWidth()) {
200             int toBeShown = makeVisibleTab != -1 ?
201                     makeVisibleTab : sel.getSelectedIndex();
202             if (toBeShown != -1) {
203                 widths[toBeShown] = width;
204             } else {
205                 widths[0] = width;
206             }
207             firstVisibleTab = toBeShown;
208             lastVisibleTab = toBeShown;
209             setChanged(false);
210             return;
211         }
212         
213         //init an index to the current position while looping
214
int x = minimumXposition;
215         //Find the starting point, the first visible tab
216
int start = offset >= 0 ? offset : 0;
217         //Holds a count of pixels to redistribute among other tabs, if we don't
218
//quite have room to fit the last tab, so we'll stretch the one next
219
//to it, but we don't want to make it huge
220
int toRedistribute = -1;
221         //Reset stored value for the last visible tab, returned from
222
//getLastVisibleTab()
223
lastVisibleTab = -1;
224         //Reset stored value for first visible tab, returned from
225
//getFirstVisibleTab()
226
firstVisibleTab = start;
227         //Reset the lastTabClipped flag returned by isLastTabClipped()
228
lastTabClipped = false;
229
230         //Special case - if the last tab the starting tab and there's not enough room for
231
//it, show as much of it as possible
232
if (start == mdl.size() - 1 && width < getWrapped().getW(start) + getMinimumLeftClippedWidth()) {
233             lastVisibleTab = start;
234             if (start != 0) {
235                 firstVisibleTab = start - 1;
236                 widths[start] = width - getMinimumLeftClippedWidth();
237                 widths[start - 1] = getMinimumLeftClippedWidth();
238                 lastTabClipped = width - getMinimumLeftClippedWidth() < getWrapped().getW(start);
239             } else {
240                 firstVisibleTab = start;
241                 widths[start] = width;
242                 lastTabClipped = width < getWrapped().getW(start);
243             }
244             updateActions();
245             //set the changed flag so we won't recalculate all this again until
246
//the next time something warrants it
247
setChanged(false);
248             
249             return;
250         }
251
252         for (int i = start; i < widths.length; i++) {
253             int w;
254             if (i == offset) {
255                 //If it's the first tab and it's an offset, it will use the
256
//fixed width
257
w = getMinimumLeftClippedWidth();
258             } else {
259                 //Get a dynamic width from the underlying model, which tells us
260
//how wide that tab wants to be
261
w = getWrapped().getW(i);
262             }
263             //See if we've overshot the space available for tabs. If we have,
264
//we'll need to display this tab as right-clipped
265
if (x + w > width) {
266                 if (width - x < getMinimumRightClippedWidth() && i != start) {
267                     //There's not enough space to fit the current tab. Add all
268
//the extra space to the previous one (we'll redistribute
269
//it later - this just makes the algorithm work even if
270
//you comment out the redistribution code)
271
widths[i - 1] += (width - x) - 1;
272                     //Now we know how many extra pixels we'll have to redistribute
273
toRedistribute = (width - x);
274                     //Decrement the last visible tab so it will show a correct
275
//value
276
lastVisibleTab = i - 1;
277                     //Set the width of the tab that wouldn't fit to 0
278
widths[i] = 0;
279                 } else {
280                     //Okay, there's enough space for this last tab as a clipped
281
//tab. Truncate it at the last possible pixel a tab can
282
//occupy
283
widths[i] = (width - x) - 1;
284                     //set this to the last visible tab
285
lastVisibleTab = i;
286                 }
287                 //Set the clipped flag - the UI will use this to decide what
288
//border to give the last tab
289
lastTabClipped = true;
290                 //We're done looping - this tab will be clipped, so it's the last
291
break;
292             }
293             //Okay, we're just iterating through a tab in the middle. Set its
294
//width to whatever its measurements are and move on
295
widths[i] = w;
296             x += w;
297             //make sure the last visible tab is really set correctly if there
298
//is no right clipped tab
299
if (i == widths.length - 1) {
300                 lastVisibleTab = widths.length - 1;
301             }
302         }
303         
304         //Some UIs want to make the selected tab bigger. So try to do that here.
305
//Get the selection from the selection model
306
int selected = sel.getSelectedIndex();
307         //See if we have to add some pixels to the selected tab, but ignore if
308
//it's the first or last clipped tabs
309
if (pixelsToAddToSelection != 0 && selected > start
310                 && selected < lastVisibleTab) {
311             //Add the pixels to the selected index
312
widths[selected] += pixelsToAddToSelection;
313             //Get the average number of pixels per tab to remove. If a small
314
//number, it may round to 0. Note we are intentionally dividing
315
//by the number of tabs-1 because the selected tab doesn't count.
316
int perTab = pixelsToAddToSelection
317                     - 1 / (lastVisibleTab - start);
318             //In case it does round to 0, keep an exact count
319
int pixels = pixelsToAddToSelection - 1;
320             //Iterate all the tabs, skipping the selected one
321
for (int i = start; i <= lastVisibleTab; i++) {
322                 if (i != selected) {
323                     //if it rounded to 0, we'll just subtract 2 until we get
324
//there - this will work most of the time and be harmless
325
//the rest
326
if (perTab == 0) {
327                         //remove 2 pixels from the tab width
328
widths[i] -= 2;
329                         pixels -= 2;
330                         if (pixels <= 0) {
331                             //if we'return out of pixels, stop
332
break;
333                         }
334                     } else {
335                         //Okay, we have an exact (+/- rounding errors) number of
336
//pixels to remove. Remove them,
337
widths[i] -= perTab;
338                         //Subtract from our exact count, it will avoid rounding
339
//errors showing up
340
pixels -= perTab;
341                         //if we'return out of pixels, stop
342
if (pixels <= 0) {
343                             break;
344                         }
345                     }
346                 }
347             }
348         }
349         
350         //Now, do we have some spare pixels in the last tab that we need to redistribute
351
//so we don't have a huge last tab? Only do this if there are > 2 tabs,
352
//or there's really no point - both are clipped
353
if (toRedistribute != -1 && lastVisibleTab != start
354                 && lastVisibleTab != start + 1) {
355             //Similar algorithm as above
356
int perTab = toRedistribute / ((lastVisibleTab + 1) - start);
357             for (int i = start; i < lastVisibleTab; i++) {
358                 if (perTab != 0) {
359                     widths[i] += perTab;
360                     widths[lastVisibleTab] -= perTab;
361                 } else {
362                     int use = toRedistribute > 2 ? 2 : toRedistribute;
363                     widths[i] += use;
364                     widths[lastVisibleTab] -= use;
365                     toRedistribute -= use;
366                     if (toRedistribute <= 0) {
367                         //out of pixels, quit
368
break;
369                     }
370                 }
371             }
372         }
373         updateActions();
374         //set the changed flag so we won't recalculate all this again until
375
//the next time something warrants it
376
setChanged(false);
377     }
378
379     private void setChanged(boolean val) {
380         if (changed != val) {
381             changed = val;
382         }
383     }
384
385     /**
386      * Some look and feel specs require that the selected tab be wider. This
387      * method sets the number of pixels to add to its width. It is important
388      * that the underlying layout model's padX property include enough padding
389      * that 1-2 pixels may be stolen without causing overlap problems. The
390      * default is 0.
391      */

392     public int getPixelsToAddToSelection() {
393         return pixelsToAddToSelection;
394     }
395
396     /**
397      * Returns true if the last tab displayed is clipped and should therefore be
398      * painted as a clipped tab
399      */

400     public boolean isLastTabClipped() {
401         if (width < getMinimumLeftClippedWidth()) {
402             return true;
403         }
404         return lastTabClipped;
405     }
406
407     /**
408      * Make a tab visible, according to the rules of the spec. Returns whether
409      * or not a repaint of the entire control is required. The width of the tab
410      * view is passed to this method, so that it can tell if the width has
411      * changed (in which case it needs to recalculate tab bounds), or if it can
412      * use the existing cached values.
413      * <p>
414      * This method will not trigger a repaint - it just adjusts the cached withs
415      * and positions of tabs so that the next repaint will paint correctly. It
416      * may be called as part of a more complex operation which would not want to
417      * trigger spurious repaints - but the return value should be noted, and if
418      * the return value is true, the caller should repaint the tab displayer
419      * whenever it is done doing what it is doing.
420      */

421     public boolean makeVisible (int index, int width) {
422         if (width < 0) {
423             setWidth (width);
424             makeVisibleTab = index;
425             return false;
426         }
427
428         boolean resized = width != this.width || recentlyResized;
429         recentlyResized = false;
430
431         //First, make sure we have an accurate first/last visible tab
432
setWidth(width);
433
434         if (index == -1) {
435             return false;
436         }
437
438         //Special case a single tab model - the index should always be 0
439
if (mdl.size() == 1) {
440             setOffset (-1);
441             return changed;
442         }
443
444         //Special case two tabs in a very small area - try to show them both
445
if (mdl.size() == 2) {
446             int totalWidth = getWrapped().getW(0) + getWrapped().getW(1);
447             if (totalWidth > width) {
448                 setOffset (0);
449                 return changed;
450             }
451         }
452
453         if (changed) {
454             change();
455         }
456
457         //Special case index 0 - it will always get -1
458
if (index == 0) {
459             int off = setOffset(-1);
460             return off != -1;
461         }
462         int widthForRequestedTab = getWrapped().getW(index);
463
464         //Special case a single tab which is wider than the entire
465
//tab displayer area
466
if (widthForRequestedTab > width) {
467             //It will be left clipped, but what can you do...
468
setOffset (index-1);
469             return changed;
470         }
471
472         //If it's the last tab and it's already not clipped, don't
473
//do anything
474
if (index == mdl.size() - 1 && !isLastTabClipped() && !resized) {
475             return false;
476         }
477
478         int newOffset = -2;
479
480         int currW = 0;
481         boolean isOffBack = false;
482         boolean result = changed;
483         boolean switchForward = false;
484         //If it's after the last tab, we'll find it's width, then count
485
//backward until we're out of tabs or out of space
486
if (index >= getLastVisibleTab(width)) {
487             int selIdx = sel.getSelectedIndex();
488             switchForward = index >= selIdx;
489             
490             //Find the width of this tab, and count back
491
currW = getWrapped().getW(index);
492             if (index == selIdx) {
493                 currW += pixelsToAddToSelection;
494             }
495             int firstTab = index;
496             //Count backward from the requested tab until we're out of space
497
do {
498                 firstTab--;
499                 if (firstTab > -1) {
500                     if (firstTab == selIdx) {
501                         currW += pixelsToAddToSelection;
502                     }
503                     int wid = getWrapped().getW(firstTab);
504                     currW += wid;
505                 }
506             } while (currW <= width && firstTab >= -1);
507             newOffset = firstTab;
508             if( currW <= width || switchForward ) {
509                 newOffset++;
510                 if( getOffset() == -1 && newOffset == -1 )
511                     newOffset = 0;
512             }
513         } else if (index <= getFirstVisibleTab(width)) {
514             isOffBack = true;
515             newOffset = index-1;
516         }
517
518         if (resized || !isOffBack || index == mdl.size() && getFirstVisibleTab(width) == index) {
519             if (newOffset != -2) {
520                 setOffset (newOffset);
521             }
522             result = ensureAvailableSpaceUsed(false);
523             
524         } else {
525             if (newOffset != -2) {
526                 int old = offset;
527                 int nue = setOffset (Math.min (mdl.size(), newOffset));
528                 result = old != nue;
529             }
530         }
531         return result;
532     }
533     
534     boolean ensureAvailableSpaceUsed(boolean useCached) {
535         if (mdl.size() == 0) {
536             return false;
537         }
538         boolean result = false;
539         if (changed && !useCached) {
540             result = true;
541             change();
542         }
543         int last = mdl.size() -1;
544         int lastTab = useCached ? getCachedLastVisibleTab() : getLastVisibleTab(width);
545         if (lastTab == last || lastTab == mdl.size() && last > -1) { //one has been removed
546
int off = offset;
547             int availableWidth = width - (getX(last) + getW(last));
548
549             while (availableWidth > 0 && off > -1) {
550                 availableWidth -= getWrapped().getW(off);
551                 if (availableWidth > 0) {
552                     off--;
553                 }
554             }
555             setOffset (off);
556             if (changed) {
557                 result = true;
558                 change();
559             }
560         }
561         return result;
562     }
563
564     /**
565      * Probably these should be made into constructor arguments. The minimum
566      * space to be used for a right-clipped tab
567      */

568     int getMinimumRightClippedWidth() {
569         return 40;
570     }
571
572     /**
573      * Probably these should be made into constructor arguments. The minimum
574      * space to be used for a left-clipped tab
575      */

576     int getMinimumLeftClippedWidth() {
577         return 40;
578     }
579
580     /**
581      * Sets the current cached width the model thinks it has for displaying
582      * tabs. This is used to trigger a recalculation if it differs from the
583      * previously passed value
584      */

585     public void setWidth(int width) {
586         if (this.width != width) {
587             recentlyResized = true;
588             //see if someone called makeVisible before the component was shown -
589
//we'll want to do that now
590
if (width < this.width) {
591                 //Ensure that the current selection stays visible in a resize
592
makeVisibleTab = sel.getSelectedIndex();
593             }
594             boolean needMakeVisible = (width > 0 && this.width < 0
595                     && makeVisibleTab != -1);
596             this.width = width - minimumXposition;
597             setChanged(width > getMinimumLeftClippedWidth());
598             if (changed && needMakeVisible
599                     && width > getMinimumLeftClippedWidth()) {
600                 makeVisible(makeVisibleTab, width);
601                 makeVisibleTab = -1;
602             }
603         }
604     }
605
606     private boolean recentlyResized = true;
607
608     /**
609      * Set the offset - the number of tabs that should be hidden to the left.
610      * The default is -1 - tab 0 is showing. If set to 0, tab 0 still shows but
611      * is clipped, and so forth.
612      */

613     public int setOffset(int i) {
614         int prevOffset = offset;
615         if (mdl.size() == 1) {
616             if (offset > -1) {
617                 offset = -1;
618                 setChanged(true);
619             }
620             return prevOffset;
621         }
622
623         if (mdl.size() == 2 && width < getMinimumLeftClippedWidth()
624                 + getMinimumRightClippedWidth()) {
625             offset = -1;
626             setChanged(false);
627             return prevOffset;
628         }
629
630         if (i < -1) {
631             //repeated action calls can do this
632
i = -1;
633         }
634         if (i != offset) {
635             setChanged(true);
636             offset = i;
637         }
638         return prevOffset;
639     }
640
641     /**
642      * Returns the index of the first tab that is visible (may be clipped - if
643      * it == getOffset() then it is
644      */

645     public int getFirstVisibleTab(int width) {
646         setWidth(width);
647         if (mdl.size() == 0) {
648             return -1;
649         }
650         if (width < getMinimumLeftClippedWidth()) {
651             int first = makeVisibleTab == -1 ?
652                     sel.getSelectedIndex() : makeVisibleTab;
653             return first;
654         }
655         if (changed) {
656             change();
657         }
658         return firstVisibleTab;
659     }
660
661     /**
662      * Return the number of tabs currently visible
663      */

664     public int countVisibleTabs(int width) {
665         return (getLastVisibleTab(width) + 1)
666                 - getFirstVisibleTab(width);
667     }
668
669     /**
670      * Returns the last visible tab, which may or may not be clipped
671      */

672     public int getLastVisibleTab(int width) {
673         setWidth(width);
674         if (mdl.size() == 0) {
675             return -1;
676         }
677         if (width < getMinimumLeftClippedWidth()) {
678             int first = makeVisibleTab == -1 ?
679                     sel.getSelectedIndex() : makeVisibleTab;
680             return first;
681         }
682         if (changed) {
683             change();
684         }
685         return lastVisibleTab;
686     }
687     
688     /**
689      * Used when components are deleted, so that if the user scrolls to close
690      * some tabs, and the selection is offscreen, we don't infuriatingly
691      * re-scroll away from the end tabs.
692      */

693     int getCachedLastVisibleTab() {
694         return lastVisibleTab;
695     }
696     
697     /**
698      * Used when components are deleted, so that if the user scrolls to close
699      * some tabs, and the selection is offscreen, we don't infuriatingly
700      * re-scroll away from the end tabs.
701      */

702     int getCachedFirstVisibleTab() {
703         return firstVisibleTab;
704     }
705
706     public int dropIndexOfPoint(int x, int y) {
707         if (changed) {
708             change();
709         }
710         int first = getFirstVisibleTab(width);
711         int last = getLastVisibleTab(width);
712         int pos = 0; //XXX - may not be 0 with insets
713
for (int i = first; i <= last; i++) {
714             int lastPos = pos;
715             pos += getW(i);
716             int h = getH(i);
717             int ay = getY(i);
718             if (y < 0 || y > ay + h) {
719                 return -1;
720             }
721             if (i == last && x > lastPos + (getW(i) / 2)) {
722                 return last + 1;
723             }
724             if (x >= lastPos && x <= pos) {
725                 return i;
726             }
727         }
728         return -1;
729     }
730
731     public void setPadding(Dimension d) {
732         getWrapped().setPadding(d);
733         setChanged (true);
734     }
735
736     public int getH(int index) {
737         if (changed) {
738             change();
739         }
740         try {
741             return getWrapped().getH(index);
742         } catch (IndexOutOfBoundsException JavaDoc e) {
743             //The tab was just removed, and the selection model was notified,
744
//by the data model, but not everything else has been notified yet
745
return 0;
746         }
747     }
748
749     /**
750      * Returns a cached width, after checking the changed flag and calling
751      * change() if recalculation is needed
752      */

753     public int getW(int index) {
754         //widths can be null on OS-X if component is instantiated with
755
//0 size (some bug with reloading winsys) and has never been painted
756
if (changed || widths == null || index > widths.length) {
757             change();
758         }
759         if (index >= widths.length) {
760             //If a tab has just been removed, there may be a request to
761
//repaint it
762
return 0;
763         }
764         return widths[index];
765     }
766
767     public int getX(int index) {
768         if (changed) {
769             change();
770         }
771         int result = minimumXposition;
772         for (int i = 0; i < index; i++) {
773             result += getW(i);
774         }
775         return result;
776     }
777
778     public int getY(int index) {
779         if (changed) {
780             change();
781         }
782         return getWrapped().getY(index);
783     }
784
785     public int indexOfPoint(int x, int y) {
786         if (changed) {
787             change();
788         }
789         int pos = minimumXposition;
790         int lastPos;
791         for (int i = offset == -1 ? 0 : offset; i < mdl.size(); i++) {
792             lastPos = pos;
793             int w = getW(i);
794             pos += w;
795             if (w == 0) {
796                 break;
797             }
798             int h = getH(i);
799             int ay = getY(i);
800             if (y < 0 || y > ay + h) {
801                 return -1;
802             }
803             if (x > lastPos && x < pos) {
804                 return i;
805             }
806         }
807         return -1;
808     }
809
810
811     private Action fAction = null;
812     private Action bAction = null;
813
814     /**
815      * Returns an Action that the control buttons can call to scroll forward
816      */

817     public Action getForwardAction() {
818         if (fAction == null) {
819             fAction = new ForwardAction();
820         }
821         return fAction;
822     }
823
824     /**
825      * Returns an Action that the control buttons can call to scroll backward
826      */

827     public Action getBackwardAction() {
828         if (bAction == null) {
829             bAction = new BackwardAction();
830         }
831         return bAction;
832     }
833
834     /**
835      * Update the enabled state of the button actions if the state of the layout
836      * has changed in a way that affects them
837      */

838     private void updateActions() {
839         if (width <= getMinimumLeftClippedWidth()) {
840             bAction.setEnabled(false);
841             fAction.setEnabled(false);
842         }
843         if (bAction != null) {
844             bAction.setEnabled(mdl.size() > 1 && offset > -1);
845         }
846         if (fAction != null) {
847             fAction.setEnabled(isLastTabClipped() && mdl.size() > 2
848                     && (lastVisibleTab-firstVisibleTab > 1 //special case when a tab is too wide
849
|| lastVisibleTab < mdl.size()-1));
850         }
851     }
852
853     /**
854      * An action which will scroll forward
855      */

856     private class ForwardAction extends AbstractAction {
857         public void actionPerformed(java.awt.event.ActionEvent JavaDoc e) {
858             setOffset(getOffset() + 1);
859             Component jc = (Component) getValue("control"); //NOI18N
860
//Use a convenient hack to get the control to paint
861
if (jc != null) {
862                 jc.repaint();
863             }
864         }
865     }
866
867     /**
868      * An action which will scroll backward
869      */

870     private class BackwardAction extends AbstractAction {
871         public void actionPerformed(java.awt.event.ActionEvent JavaDoc e) {
872             setOffset(getOffset() - 1);
873             //Use a convenient hack to get the control to paint
874
Component jc = (Component) getValue("control"); //NOI18N
875
if (jc != null) {
876                 jc.repaint();
877             }
878         }
879     }
880 }
881
Popular Tags