Source Code Cross Referenced for ScrollingTabLayoutModel.java in  » IDE-Netbeans » library » org » netbeans » swing » tabcontrol » plaf » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » IDE Netbeans » library » org.netbeans.swing.tabcontrol.plaf 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*
002:         * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003:         *
004:         * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005:         *
006:         * The contents of this file are subject to the terms of either the GNU
007:         * General Public License Version 2 only ("GPL") or the Common
008:         * Development and Distribution License("CDDL") (collectively, the
009:         * "License"). You may not use this file except in compliance with the
010:         * License. You can obtain a copy of the License at
011:         * http://www.netbeans.org/cddl-gplv2.html
012:         * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013:         * specific language governing permissions and limitations under the
014:         * License.  When distributing the software, include this License Header
015:         * Notice in each file and include the License file at
016:         * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
017:         * particular file as subject to the "Classpath" exception as provided
018:         * by Sun in the GPL Version 2 section of the License file that
019:         * accompanied this code. If applicable, add the following below the
020:         * License Header, with the fields enclosed by brackets [] replaced by
021:         * your own identifying information:
022:         * "Portions Copyrighted [year] [name of copyright owner]"
023:         *
024:         * Contributor(s):
025:         *
026:         * The Original Software is NetBeans. The Initial Developer of the Original
027:         * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028:         * Microsystems, Inc. All Rights Reserved.
029:         *
030:         * If you wish your version of this file to be governed by only the CDDL
031:         * or only the GPL Version 2, indicate your decision by adding
032:         * "[Contributor] elects to include this software in this distribution
033:         * under the [CDDL or GPL Version 2] license." If you do not indicate a
034:         * single choice of license, a recipient has the option to distribute
035:         * your version of this file under either the CDDL, the GPL Version 2 or
036:         * to extend the choice of license to its licensees as provided above.
037:         * However, if you add GPL Version 2 code and therefore, elected the GPL
038:         * Version 2 license, then the option applies only if the new code is
039:         * made subject to such option by the copyright holder.
040:         */
041:        package org.netbeans.swing.tabcontrol.plaf;
042:
043:        import org.netbeans.swing.tabcontrol.TabDataModel;
044:
045:        import javax.swing.*;
046:        import java.awt.*;
047:        import java.util.Arrays;
048:
049:        /*
050:         * ScrollingTabLayoutModel.java
051:         *
052:         * Created on December 5, 2003, 5:16 PM
053:         */
054:
055:        /**
056:         * Layout model which manages an offset into a set of scrollable tabs, and
057:         * recalculates its layout on a change.  Also handles adding extra pixels to the
058:         * selected tab if necessary.  Basics of how it works:
059:         * <p>
060:         * Wrapppers a DefaultTabLayoutModel, which can simply calculate tab widths and
061:         * 0 based positions.  Listens to the data model for changes, and sets a flag
062:         * when a change happens to mark the cached widths and positions as dirty. On
063:         * any call to fetch sizes, first checks if the cached values are good,
064:         * recalculates if needed, and returns the result.
065:         *
066:         * @author Tim Boudreau
067:         */
068:        public final class ScrollingTabLayoutModel implements  TabLayoutModel {
069:            /**
070:             * The index of the first clipped, visible tab, or -1 if the first tab
071:             * should not be clippped
072:             */
073:            private int offset = -1;
074:            /**
075:             * The wrapped DefaultTabLayoutModel which will give us pure numbers for the
076:             * desired width of tabs
077:             */
078:            private TabLayoutModel wrapped;
079:            /**
080:             * Flag indicating that any call to get a value should trigger recalculation
081:             * of the cached values
082:             */
083:            private boolean changed = true;
084:            /**
085:             * The tabDataModel, which we occasionally need to get data from
086:             */
087:            TabDataModel mdl;
088:            /**
089:             * The selection model we will get the current selection from when we need
090:             * to ensure it is visible
091:             */
092:            SingleSelectionModel sel;
093:            /**
094:             * Holds the value of the tab that should be made visible if makeVisible is
095:             * called before the component has a valid (>0) width.  If not -1, a call to
096:             * setWidth() will trigger a call to makeVisible with this value.
097:             */
098:            private int makeVisibleTab = -1;
099:            /**
100:             * Integer count of pixels that should be added to the width of the selected
101:             * tab.  They will be subtracted from the surrounding tabs
102:             */
103:            int pixelsToAddToSelection = 0;
104:            /**
105:             * Stores the value of whether the final tab is clipped.  Recalculated in
106:             * <code>change()</code>
107:             */
108:            private boolean lastTabClipped = false;
109:            /**
110:             * Cached index of the first visible tab
111:             */
112:            private int firstVisibleTab = -1;
113:            /**
114:             * Cached index of the last visible tab
115:             */
116:            private int lastVisibleTab = -1;
117:            /**
118:             * The last known width for which values were calculated
119:             */
120:            private int width = -1;
121:            /**
122:             * Cache of the widths of tabs that *are* onscreen.  This will always have a
123:             * length of (lastVisibleTab + 1) - firstVisibleTab.
124:             */
125:            private int[] widths = null;
126:
127:            /**
128:             * Creates a new instance of ScrollingTabLayoutModel
129:             */
130:            public ScrollingTabLayoutModel(TabLayoutModel wrapped,
131:                    SingleSelectionModel sel, TabDataModel mdl) {
132:                this .wrapped = wrapped;
133:                this .mdl = mdl;
134:                this .sel = sel;
135:            }
136:
137:            public ScrollingTabLayoutModel(TabLayoutModel wrapped,
138:                    SingleSelectionModel sel, TabDataModel mdl,
139:                    int minimumXposition) {
140:                this (wrapped, sel, mdl);
141:                this .minimumXposition = minimumXposition;
142:            }
143:
144:            public void setMinimumXposition(int x) {
145:                this .minimumXposition = x;
146:                setChanged(true);
147:            }
148:
149:            /**
150:             * Some UIs will want to make the selected tab a little wider than the
151:             * rest.
152:             * @param i
153:             */
154:            public void setPixelsToAddToSelection(int i) {
155:                pixelsToAddToSelection = i;
156:                setChanged(true);
157:            }
158:
159:            private int minimumXposition = 0;
160:
161:            /**
162:             * External operations on the selection or data model can invalidate cached
163:             * widths.  The UI will listen for such changes and call this method if the
164:             * data we have cached is probably no good anymore.
165:             */
166:            public void clearCachedData() {
167:                setChanged(true);
168:            }
169:
170:            /**
171:             * Convenience getter for the "wrapped" model which will give us "pure"
172:             * numbers regarding the widths of tabs
173:             */
174:            private TabLayoutModel getWrapped() {
175:                return wrapped;
176:            }
177:
178:            /**
179:             * Get the offset - the number of tabs that are scrolled over.  The default
180:             * value is -1, which means no tabs are scrolled off to the left.  0 means
181:             * the first tab is visible but clipped...and so forth
182:             */
183:            public int getOffset() {
184:                if (mdl.size() <= 1) {
185:                    return -1;
186:                }
187:                return offset;
188:            }
189:
190:            /**
191:             * Called to recalculate cached values the first time a value that needs to
192:             * be calculated is requested, after some change that invalidates the cached
193:             * values
194:             */
195:            private void change() {
196:                if (mdl.size() == 0) {
197:                    //no tabs, do nothing
198:                    widths = new int[0];
199:                    updateActions();
200:                    setChanged(false);
201:                    return;
202:                }
203:                //Create an array that will hold precalculated widths until something
204:                //changes
205:                if (widths == null || widths.length != mdl.size()) {
206:                    widths = new int[mdl.size()];
207:                    //Fill our array with 0's - any tabs not visible should get 0 width
208:                }
209:                Arrays.fill(widths, 0);
210:
211:                if (widths.length == 1) {
212:                    //there's only one tab, get rid of any offset - otherwise there's
213:                    //no way to ever make the close button show because it won't be
214:                    //able to be scrolled
215:                    offset = -1;
216:                }
217:
218:                //Handle throws case where we don't really even have enough room to
219:                //display one tab, by centering the clipped selected tag on what little
220:                //space we have.  The UI will make sure it looks clipped.
221:                if (width < getMinimumLeftClippedWidth()) {
222:                    int toBeShown = makeVisibleTab != -1 ? makeVisibleTab : sel
223:                            .getSelectedIndex();
224:                    if (toBeShown != -1) {
225:                        widths[toBeShown] = width;
226:                    } else {
227:                        widths[0] = width;
228:                    }
229:                    firstVisibleTab = toBeShown;
230:                    lastVisibleTab = toBeShown;
231:                    setChanged(false);
232:                    return;
233:                }
234:
235:                //init an index to the current position while looping
236:                int x = minimumXposition;
237:                //Find the starting point, the first visible tab
238:                int start = offset >= 0 ? offset : 0;
239:                //Holds a count of pixels to redistribute among other tabs, if we don't
240:                //quite have room to fit the last tab, so we'll stretch the one next
241:                //to it, but we don't want to make it huge
242:                int toRedistribute = -1;
243:                //Reset stored value for the last visible tab, returned from 
244:                //getLastVisibleTab()
245:                lastVisibleTab = -1;
246:                //Reset stored value for first visible tab, returned from
247:                //getFirstVisibleTab()
248:                firstVisibleTab = start;
249:                //Reset the lastTabClipped flag returned by isLastTabClipped()
250:                lastTabClipped = false;
251:
252:                //Special case - if the last tab the starting tab and there's not enough room for
253:                //it, show as much of it as possible
254:                if (start == mdl.size() - 1
255:                        && width < getWrapped().getW(start)
256:                                + getMinimumLeftClippedWidth()) {
257:                    lastVisibleTab = start;
258:                    if (start != 0) {
259:                        firstVisibleTab = start - 1;
260:                        widths[start] = width - getMinimumLeftClippedWidth();
261:                        widths[start - 1] = getMinimumLeftClippedWidth();
262:                        lastTabClipped = width - getMinimumLeftClippedWidth() < getWrapped()
263:                                .getW(start);
264:                    } else {
265:                        firstVisibleTab = start;
266:                        widths[start] = width;
267:                        lastTabClipped = width < getWrapped().getW(start);
268:                    }
269:                    updateActions();
270:                    //set the changed flag so we won't recalculate all this again until
271:                    //the next time something warrants it
272:                    setChanged(false);
273:
274:                    return;
275:                }
276:
277:                for (int i = start; i < widths.length; i++) {
278:                    int w;
279:                    if (i == offset) {
280:                        //If it's the first tab and it's an offset, it will use the
281:                        //fixed width
282:                        w = getMinimumLeftClippedWidth();
283:                    } else {
284:                        //Get a dynamic width from the underlying model, which tells us
285:                        //how wide that tab wants to be
286:                        w = getWrapped().getW(i);
287:                    }
288:                    //See if we've overshot the space available for tabs.  If we have,
289:                    //we'll need to display this tab as right-clipped
290:                    if (x + w > width) {
291:                        if (width - x < getMinimumRightClippedWidth()
292:                                && i != start) {
293:                            //There's not enough space to fit the current tab.  Add all
294:                            //the extra space to the previous one (we'll redistribute
295:                            //it later - this just makes the algorithm work even if
296:                            //you comment out the redistribution code)
297:                            widths[i - 1] += (width - x) - 1;
298:                            //Now we know how many extra pixels we'll have to redistribute
299:                            toRedistribute = (width - x);
300:                            //Decrement the last visible tab so it will show a correct
301:                            //value
302:                            lastVisibleTab = i - 1;
303:                            //Set the width of the tab that wouldn't fit to 0
304:                            widths[i] = 0;
305:                        } else {
306:                            //Okay, there's enough space for this last tab as a clipped
307:                            //tab.  Truncate it at the last possible pixel a tab can
308:                            //occupy
309:                            widths[i] = (width - x) - 1;
310:                            //set this to the last visible tab
311:                            lastVisibleTab = i;
312:                        }
313:                        //Set the clipped flag - the UI will use this to decide what
314:                        //border to give the last tab
315:                        lastTabClipped = true;
316:                        //We're done looping - this tab will be clipped, so it's the last
317:                        break;
318:                    }
319:                    //Okay, we're just iterating through a tab in the middle.  Set its
320:                    //width to whatever its measurements are and move on
321:                    widths[i] = w;
322:                    x += w;
323:                    //make sure the last visible tab is really set correctly if there
324:                    //is no right clipped tab
325:                    if (i == widths.length - 1) {
326:                        lastVisibleTab = widths.length - 1;
327:                    }
328:                }
329:
330:                //Some UIs want to make the selected tab bigger.  So try to do that here.
331:                //Get the selection from the selection model
332:                int selected = sel.getSelectedIndex();
333:                //See if we have to add some pixels to the selected tab, but ignore if
334:                //it's the first or last clipped tabs
335:                if (pixelsToAddToSelection != 0 && selected > start
336:                        && selected < lastVisibleTab) {
337:                    //Add the pixels to the selected index
338:                    widths[selected] += pixelsToAddToSelection;
339:                    //Get the average number of pixels per tab to remove.  If a small
340:                    //number, it may round to 0.  Note we are intentionally dividing
341:                    //by the number of tabs-1 because the selected tab doesn't count.
342:                    int perTab = pixelsToAddToSelection - 1
343:                            / (lastVisibleTab - start);
344:                    //In case it does round to 0, keep an exact count
345:                    int pixels = pixelsToAddToSelection - 1;
346:                    //Iterate all the tabs, skipping the selected one
347:                    for (int i = start; i <= lastVisibleTab; i++) {
348:                        if (i != selected) {
349:                            //if it rounded to 0, we'll just subtract 2 until we get
350:                            //there - this will work most of the time and be harmless
351:                            //the rest
352:                            if (perTab == 0) {
353:                                //remove 2 pixels from the tab width
354:                                widths[i] -= 2;
355:                                pixels -= 2;
356:                                if (pixels <= 0) {
357:                                    //if we'return out of pixels, stop
358:                                    break;
359:                                }
360:                            } else {
361:                                //Okay, we have an exact (+/- rounding errors) number of
362:                                //pixels to remove.  Remove them,
363:                                widths[i] -= perTab;
364:                                //Subtract from our exact count, it will avoid rounding
365:                                //errors showing up
366:                                pixels -= perTab;
367:                                //if we'return out of pixels, stop
368:                                if (pixels <= 0) {
369:                                    break;
370:                                }
371:                            }
372:                        }
373:                    }
374:                }
375:
376:                //Now, do we have some spare pixels in the last tab that we need to redistribute
377:                //so we don't have a huge last tab?  Only do this if there are > 2 tabs,
378:                //or there's really no point - both are clipped
379:                if (toRedistribute != -1 && lastVisibleTab != start
380:                        && lastVisibleTab != start + 1) {
381:                    //Similar algorithm as above
382:                    int perTab = toRedistribute
383:                            / ((lastVisibleTab + 1) - start);
384:                    for (int i = start; i < lastVisibleTab; i++) {
385:                        if (perTab != 0) {
386:                            widths[i] += perTab;
387:                            widths[lastVisibleTab] -= perTab;
388:                        } else {
389:                            int use = toRedistribute > 2 ? 2 : toRedistribute;
390:                            widths[i] += use;
391:                            widths[lastVisibleTab] -= use;
392:                            toRedistribute -= use;
393:                            if (toRedistribute <= 0) {
394:                                //out of pixels, quit
395:                                break;
396:                            }
397:                        }
398:                    }
399:                }
400:                updateActions();
401:                //set the changed flag so we won't recalculate all this again until
402:                //the next time something warrants it
403:                setChanged(false);
404:            }
405:
406:            private void setChanged(boolean val) {
407:                if (changed != val) {
408:                    changed = val;
409:                }
410:            }
411:
412:            /**
413:             * Some look and feel specs require that the selected tab be wider.  This
414:             * method sets the number of pixels to add to its width.  It is important
415:             * that the underlying layout model's padX property include enough padding
416:             * that 1-2 pixels may be stolen without causing overlap problems.  The
417:             * default is 0.
418:             */
419:            public int getPixelsToAddToSelection() {
420:                return pixelsToAddToSelection;
421:            }
422:
423:            /**
424:             * Returns true if the last tab displayed is clipped and should therefore be
425:             * painted as a clipped tab
426:             */
427:            public boolean isLastTabClipped() {
428:                if (width < getMinimumLeftClippedWidth()) {
429:                    return true;
430:                }
431:                return lastTabClipped;
432:            }
433:
434:            /**
435:             * Make a tab visible, according to the rules of the spec.  Returns whether
436:             * or not a repaint of the entire control is required.  The width of the tab
437:             * view is passed to this method, so that it can tell if the width has
438:             * changed (in which case it needs to recalculate tab bounds), or if it can
439:             * use the existing cached values.
440:             * <p>
441:             * This method will not trigger a repaint - it just adjusts the cached withs
442:             * and positions of tabs so that the next repaint will paint correctly. It
443:             * may be called as part of a more complex operation which would not want to
444:             * trigger spurious repaints - but the return value should be noted, and if
445:             * the return value is true, the caller should repaint the tab displayer
446:             * whenever it is done doing what it is doing.
447:             */
448:            public boolean makeVisible(int index, int width) {
449:                if (width < 0) {
450:                    setWidth(width);
451:                    makeVisibleTab = index;
452:                    return false;
453:                }
454:
455:                boolean resized = width != this .width || recentlyResized;
456:                recentlyResized = false;
457:
458:                //First, make sure we have an accurate first/last visible tab
459:                setWidth(width);
460:
461:                if (index == -1) {
462:                    return false;
463:                }
464:
465:                //Special case a single tab model - the index should always be 0
466:                if (mdl.size() == 1) {
467:                    setOffset(-1);
468:                    return changed;
469:                }
470:
471:                //Special case two tabs in a very small area - try to show them both
472:                if (mdl.size() == 2) {
473:                    int totalWidth = getWrapped().getW(0)
474:                            + getWrapped().getW(1);
475:                    if (totalWidth > width) {
476:                        setOffset(0);
477:                        return changed;
478:                    }
479:                }
480:
481:                if (changed) {
482:                    change();
483:                }
484:
485:                //Special case index 0 - it will always get -1
486:                if (index == 0) {
487:                    int off = setOffset(-1);
488:                    return off != -1;
489:                }
490:                int widthForRequestedTab = getWrapped().getW(index);
491:
492:                //Special case a single tab which is wider than the entire
493:                //tab displayer area
494:                if (widthForRequestedTab > width) {
495:                    //It will be left clipped, but what can you do...
496:                    setOffset(index - 1);
497:                    return changed;
498:                }
499:
500:                //If it's the last tab and it's already not clipped, don't
501:                //do anything
502:                if (index == mdl.size() - 1 && !isLastTabClipped() && !resized) {
503:                    return false;
504:                }
505:
506:                int newOffset = -2;
507:
508:                int currW = 0;
509:                boolean isOffBack = false;
510:                boolean result = changed;
511:                boolean switchForward = false;
512:                //If it's after the last tab, we'll find it's width, then count
513:                //backward until we're out of tabs or out of space
514:                if (index >= getLastVisibleTab(width)) {
515:                    int selIdx = sel.getSelectedIndex();
516:                    switchForward = index >= selIdx;
517:
518:                    //Find the width of this tab, and count back
519:                    currW = getWrapped().getW(index);
520:                    if (index == selIdx) {
521:                        currW += pixelsToAddToSelection;
522:                    }
523:                    int firstTab = index;
524:                    //Count backward from the requested tab until we're out of space
525:                    do {
526:                        firstTab--;
527:                        if (firstTab > -1) {
528:                            if (firstTab == selIdx) {
529:                                currW += pixelsToAddToSelection;
530:                            }
531:                            int wid = getWrapped().getW(firstTab);
532:                            currW += wid;
533:                        }
534:                    } while (currW <= width && firstTab >= -1);
535:                    newOffset = firstTab;
536:                    if (currW <= width || switchForward) {
537:                        newOffset++;
538:                        if (getOffset() == -1 && newOffset == -1)
539:                            newOffset = 0;
540:                    }
541:                } else if (index <= getFirstVisibleTab(width)) {
542:                    isOffBack = true;
543:                    newOffset = index - 1;
544:                }
545:
546:                if (resized || !isOffBack || index == mdl.size()
547:                        && getFirstVisibleTab(width) == index) {
548:                    if (newOffset != -2) {
549:                        setOffset(newOffset);
550:                    }
551:                    result = ensureAvailableSpaceUsed(false);
552:
553:                } else {
554:                    if (newOffset != -2) {
555:                        int old = offset;
556:                        int nue = setOffset(Math.min(mdl.size(), newOffset));
557:                        result = old != nue;
558:                    }
559:                }
560:                return result;
561:            }
562:
563:            boolean ensureAvailableSpaceUsed(boolean useCached) {
564:                if (mdl.size() == 0) {
565:                    return false;
566:                }
567:                boolean result = false;
568:                if (changed && !useCached) {
569:                    result = true;
570:                    change();
571:                }
572:                int last = mdl.size() - 1;
573:                int lastTab = useCached ? getCachedLastVisibleTab()
574:                        : getLastVisibleTab(width);
575:                if (lastTab == last || lastTab == mdl.size() && last > -1) { //one has been removed
576:                    int off = offset;
577:                    int availableWidth = width - (getX(last) + getW(last));
578:
579:                    while (availableWidth > 0 && off > -1) {
580:                        availableWidth -= getWrapped().getW(off);
581:                        if (availableWidth > 0) {
582:                            off--;
583:                        }
584:                    }
585:                    setOffset(off);
586:                    if (changed) {
587:                        result = true;
588:                        change();
589:                    }
590:                }
591:                return result;
592:            }
593:
594:            /**
595:             * Probably these should be made into constructor arguments.  The minimum
596:             * space to be used for a right-clipped tab
597:             */
598:            int getMinimumRightClippedWidth() {
599:                return 40;
600:            }
601:
602:            /**
603:             * Probably these should be made into constructor arguments.  The minimum
604:             * space to be used for a left-clipped tab
605:             */
606:            int getMinimumLeftClippedWidth() {
607:                return 40;
608:            }
609:
610:            /**
611:             * Sets the current cached width the model thinks it has for displaying
612:             * tabs.  This is used to trigger a recalculation if it differs from the
613:             * previously passed value
614:             */
615:            public void setWidth(int width) {
616:                if (this .width != width) {
617:                    recentlyResized = true;
618:                    //see if someone called makeVisible before the component was shown -
619:                    //we'll want to do that now
620:                    if (width < this .width) {
621:                        //Ensure that the current selection stays visible in a resize
622:                        makeVisibleTab = sel.getSelectedIndex();
623:                    }
624:                    boolean needMakeVisible = (width > 0 && this .width < 0 && makeVisibleTab != -1);
625:                    this .width = width - minimumXposition;
626:                    setChanged(width > getMinimumLeftClippedWidth());
627:                    if (changed && needMakeVisible
628:                            && width > getMinimumLeftClippedWidth()) {
629:                        makeVisible(makeVisibleTab, width);
630:                        makeVisibleTab = -1;
631:                    }
632:                }
633:            }
634:
635:            private boolean recentlyResized = true;
636:
637:            /**
638:             * Set the offset - the number of tabs that should be hidden to the left.
639:             * The default is -1 - tab 0 is showing.  If set to 0, tab 0 still shows but
640:             * is clipped, and so forth.
641:             */
642:            public int setOffset(int i) {
643:                int prevOffset = offset;
644:                if (mdl.size() == 1) {
645:                    if (offset > -1) {
646:                        offset = -1;
647:                        setChanged(true);
648:                    }
649:                    return prevOffset;
650:                }
651:
652:                if (mdl.size() == 2
653:                        && width < getMinimumLeftClippedWidth()
654:                                + getMinimumRightClippedWidth()) {
655:                    offset = -1;
656:                    setChanged(false);
657:                    return prevOffset;
658:                }
659:
660:                if (i < -1) {
661:                    //repeated action calls can do this
662:                    i = -1;
663:                }
664:                if (i != offset) {
665:                    setChanged(true);
666:                    offset = i;
667:                }
668:                return prevOffset;
669:            }
670:
671:            /**
672:             * Returns the index of the first tab that is visible (may be clipped - if
673:             * it == getOffset() then it is
674:             */
675:            public int getFirstVisibleTab(int width) {
676:                setWidth(width);
677:                if (mdl.size() == 0) {
678:                    return -1;
679:                }
680:                if (width < getMinimumLeftClippedWidth()) {
681:                    int first = makeVisibleTab == -1 ? sel.getSelectedIndex()
682:                            : makeVisibleTab;
683:                    return first;
684:                }
685:                if (changed) {
686:                    change();
687:                }
688:                return firstVisibleTab;
689:            }
690:
691:            /**
692:             * Return the number of tabs currently visible
693:             */
694:            public int countVisibleTabs(int width) {
695:                return (getLastVisibleTab(width) + 1)
696:                        - getFirstVisibleTab(width);
697:            }
698:
699:            /**
700:             * Returns the last visible tab, which may or may not be clipped
701:             */
702:            public int getLastVisibleTab(int width) {
703:                setWidth(width);
704:                if (mdl.size() == 0) {
705:                    return -1;
706:                }
707:                if (width < getMinimumLeftClippedWidth()) {
708:                    int first = makeVisibleTab == -1 ? sel.getSelectedIndex()
709:                            : makeVisibleTab;
710:                    return first;
711:                }
712:                if (changed) {
713:                    change();
714:                }
715:                return lastVisibleTab;
716:            }
717:
718:            /**
719:             * Used when components are deleted, so that if the user scrolls to close
720:             * some tabs, and the selection is offscreen, we don't infuriatingly
721:             * re-scroll away from the end tabs.
722:             */
723:            int getCachedLastVisibleTab() {
724:                return lastVisibleTab;
725:            }
726:
727:            /**
728:             * Used when components are deleted, so that if the user scrolls to close
729:             * some tabs, and the selection is offscreen, we don't infuriatingly
730:             * re-scroll away from the end tabs.
731:             */
732:            int getCachedFirstVisibleTab() {
733:                return firstVisibleTab;
734:            }
735:
736:            public int dropIndexOfPoint(int x, int y) {
737:                if (changed) {
738:                    change();
739:                }
740:                int first = getFirstVisibleTab(width);
741:                int last = getLastVisibleTab(width);
742:                int pos = 0; //XXX - may not be 0 with insets
743:                for (int i = first; i <= last; i++) {
744:                    int lastPos = pos;
745:                    pos += getW(i);
746:                    int h = getH(i);
747:                    int ay = getY(i);
748:                    if (y < 0 || y > ay + h) {
749:                        return -1;
750:                    }
751:                    if (i == last && x > lastPos + (getW(i) / 2)) {
752:                        return last + 1;
753:                    }
754:                    if (x >= lastPos && x <= pos) {
755:                        return i;
756:                    }
757:                }
758:                return -1;
759:            }
760:
761:            public void setPadding(Dimension d) {
762:                getWrapped().setPadding(d);
763:                setChanged(true);
764:            }
765:
766:            public int getH(int index) {
767:                if (changed) {
768:                    change();
769:                }
770:                try {
771:                    return getWrapped().getH(index);
772:                } catch (IndexOutOfBoundsException e) {
773:                    //The tab was just removed, and the selection model was notified,
774:                    //by the data model, but not everything else has been notified yet
775:                    return 0;
776:                }
777:            }
778:
779:            /**
780:             * Returns a cached width, after checking the changed flag and calling
781:             * change() if recalculation is needed
782:             */
783:            public int getW(int index) {
784:                //widths can be null on OS-X if component is instantiated with
785:                //0 size (some bug with reloading winsys) and has never been painted
786:                if (changed || widths == null || index > widths.length) {
787:                    change();
788:                }
789:                if (index >= widths.length) {
790:                    //If a tab has just been removed, there may be a request to 
791:                    //repaint it
792:                    return 0;
793:                }
794:                return widths[index];
795:            }
796:
797:            public int getX(int index) {
798:                if (changed) {
799:                    change();
800:                }
801:                int result = minimumXposition;
802:                for (int i = 0; i < index; i++) {
803:                    result += getW(i);
804:                }
805:                return result;
806:            }
807:
808:            public int getY(int index) {
809:                if (changed) {
810:                    change();
811:                }
812:                return getWrapped().getY(index);
813:            }
814:
815:            public int indexOfPoint(int x, int y) {
816:                if (changed) {
817:                    change();
818:                }
819:                int pos = minimumXposition;
820:                int lastPos;
821:                for (int i = offset == -1 ? 0 : offset; i < mdl.size(); i++) {
822:                    lastPos = pos;
823:                    int w = getW(i);
824:                    pos += w;
825:                    if (w == 0) {
826:                        break;
827:                    }
828:                    int h = getH(i);
829:                    int ay = getY(i);
830:                    if (y < 0 || y > ay + h) {
831:                        return -1;
832:                    }
833:                    if (x > lastPos && x < pos) {
834:                        return i;
835:                    }
836:                }
837:                return -1;
838:            }
839:
840:            private Action fAction = null;
841:            private Action bAction = null;
842:
843:            /**
844:             * Returns an Action that the control buttons can call to scroll forward
845:             */
846:            public Action getForwardAction() {
847:                if (fAction == null) {
848:                    fAction = new ForwardAction();
849:                }
850:                return fAction;
851:            }
852:
853:            /**
854:             * Returns an Action that the control buttons can call to scroll backward
855:             */
856:            public Action getBackwardAction() {
857:                if (bAction == null) {
858:                    bAction = new BackwardAction();
859:                }
860:                return bAction;
861:            }
862:
863:            /**
864:             * Update the enabled state of the button actions if the state of the layout
865:             * has changed in a way that affects them
866:             */
867:            private void updateActions() {
868:                if (width <= getMinimumLeftClippedWidth()) {
869:                    bAction.setEnabled(false);
870:                    fAction.setEnabled(false);
871:                }
872:                if (bAction != null) {
873:                    bAction.setEnabled(mdl.size() > 1 && offset > -1);
874:                }
875:                if (fAction != null) {
876:                    fAction.setEnabled(isLastTabClipped() && mdl.size() > 2
877:                            && (lastVisibleTab - firstVisibleTab > 1 //special case when a tab is too wide
878:                            || lastVisibleTab < mdl.size() - 1));
879:                }
880:            }
881:
882:            /**
883:             * An action which will scroll forward
884:             */
885:            private class ForwardAction extends AbstractAction {
886:                public void actionPerformed(java.awt.event.ActionEvent e) {
887:                    setOffset(getOffset() + 1);
888:                    Component jc = (Component) getValue("control"); //NOI18N
889:                    //Use a convenient hack to get the control to paint
890:                    if (jc != null) {
891:                        jc.repaint();
892:                    }
893:                }
894:            }
895:
896:            /**
897:             * An action which will scroll backward
898:             */
899:            private class BackwardAction extends AbstractAction {
900:                public void actionPerformed(java.awt.event.ActionEvent e) {
901:                    setOffset(getOffset() - 1);
902:                    //Use a convenient hack to get the control to paint
903:                    Component jc = (Component) getValue("control"); //NOI18N
904:                    if (jc != null) {
905:                        jc.repaint();
906:                    }
907:                }
908:            }
909:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.