Clean up 'guitabtooltip' patch
[MacVim.git] / src / MacVim / PSMTabBarControl / source / PSMTabBarControl.m
blob2c7e45c7ff9a201c3e97cc510e2a62221940a883
1 //
2 //  PSMTabBarControl.m
3 //  PSMTabBarControl
4 //
5 //  Created by John Pannell on 10/13/05.
6 //  Copyright 2005 Positive Spin Media. All rights reserved.
7 //
9 #import "PSMTabBarControl.h"
10 #import "PSMTabBarCell.h"
11 #import "PSMOverflowPopUpButton.h"
12 #import "PSMRolloverButton.h"
13 #import "PSMTabStyle.h"
14 #import "PSMMetalTabStyle.h"
15 #import "PSMAquaTabStyle.h"
16 #import "PSMUnifiedTabStyle.h"
17 #import "PSMTabDragAssistant.h"
19 @interface PSMTabBarControl (Private)
20 // characteristics
21 - (float)availableCellWidth;
22 - (NSRect)genericCellRect;
24     // constructor/destructor
25 - (void)initAddedProperties;
26 - (void)dealloc;
28     // accessors
29 - (NSEvent *)lastMouseDownEvent;
30 - (void)setLastMouseDownEvent:(NSEvent *)event;
32     // contents
33 - (void)addTabViewItem:(NSTabViewItem *)item;
34 - (void)removeTabForCell:(PSMTabBarCell *)cell;
36     // draw
37 - (void)update;
39     // actions
40 - (void)overflowMenuAction:(id)sender;
41 - (void)closeTabClick:(id)sender;
42 - (void)tabClick:(id)sender;
43 - (void)tabNothing:(id)sender;
44 - (void)frameDidChange:(NSNotification *)notification;
45 - (void)windowDidMove:(NSNotification *)aNotification;
46 - (void)windowStatusDidChange:(NSNotification *)notification;
48     // NSTabView delegate
49 - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem;
50 - (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem;
51 - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem;
52 - (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView;
54     // archiving
55 - (void)encodeWithCoder:(NSCoder *)aCoder;
56 - (id)initWithCoder:(NSCoder *)aDecoder;
58     // convenience
59 - (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame;
60 - (unsigned)indexOfCellAtPoint:(NSPoint)point;
61 - (unsigned)indexOfCellAtPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame;
62 - (PSMTabBarCell *)lastVisibleTab;
63 - (int)numberOfVisibleTabs;
65 @end
67 @implementation PSMTabBarControl
68 #pragma mark -
69 #pragma mark Characteristics
70 + (NSBundle *)bundle;
72     static NSBundle *bundle = nil;
73     if (!bundle) bundle = [NSBundle bundleForClass:[PSMTabBarControl class]];
74     return bundle;
77 - (float)availableCellWidth
79     float width = [self frame].size.width;
80     width = width - [style leftMarginForTabBarControl] - [style rightMarginForTabBarControl];
81     return width;
84 - (NSRect)genericCellRect
86     NSRect aRect=[self frame];
87     aRect.origin.x = [style leftMarginForTabBarControl];
88     aRect.origin.y = 0.0;
89     aRect.size.width = [self availableCellWidth];
90     aRect.size.height = kPSMTabBarControlHeight;
91     return aRect;
94 #pragma mark -
95 #pragma mark Constructor/destructor
97 - (void)initAddedProperties
99     _cells = [[NSMutableArray alloc] initWithCapacity:10];
100     
101     // default config
102     _allowsDragBetweenWindows = YES;
103     _delegateHandlingDrag = NO;
104     _canCloseOnlyTab = NO;
105     _showAddTabButton = NO;
106     _hideForSingleTab = NO;
107     _sizeCellsToFit = NO;
108     _isHidden = NO;
109     _hideIndicators = NO;
110     _awakenedFromNib = NO;
111     _cellMinWidth = 100;
112     _cellMaxWidth = 280;
113     _cellOptimumWidth = 130;
114     style = [[PSMMetalTabStyle alloc] init];
115     
116     // the overflow button/menu
117     NSRect overflowButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 0, [style rightMarginForTabBarControl] - 1, [self frame].size.height);
118     _overflowPopUpButton = [[PSMOverflowPopUpButton alloc] initWithFrame:overflowButtonRect pullsDown:YES];
119     if(_overflowPopUpButton){
120         // configure
121         [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable|NSViewMinXMargin];
122     }
123     
124     // new tab button
125     NSRect addTabButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 3.0, 16.0, 16.0);
126     _addTabButton = [[PSMRolloverButton alloc] initWithFrame:addTabButtonRect];
127     if(_addTabButton){
128         NSImage *newButtonImage = [style addTabButtonImage];
129         if(newButtonImage)
130             [_addTabButton setUsualImage:newButtonImage];
131         newButtonImage = [style addTabButtonPressedImage];
132         if(newButtonImage)
133             [_addTabButton setAlternateImage:newButtonImage];
134         newButtonImage = [style addTabButtonRolloverImage];
135         if(newButtonImage)
136             [_addTabButton setRolloverImage:newButtonImage];
137         [_addTabButton setTitle:@""];
138         [_addTabButton setImagePosition:NSImageOnly];
139         [_addTabButton setButtonType:NSMomentaryChangeButton];
140         [_addTabButton setBordered:NO];
141         [_addTabButton setBezelStyle:NSShadowlessSquareBezelStyle];
142         if(_showAddTabButton){
143             [_addTabButton setHidden:NO];
144         } else {
145             [_addTabButton setHidden:YES];
146         }
147         [_addTabButton setNeedsDisplay:YES];
148     }
151 - (id)initWithFrame:(NSRect)frame
153     self = [super initWithFrame:frame];
154     if (self) {
155         // Initialization
156         [self initAddedProperties];
157         [self registerForDraggedTypes:[NSArray arrayWithObjects: @"PSMTabBarControlItemPBType", nil]];
158     }
159     [self setTarget:self];
160     return self;
163 - (void)dealloc
165     [_overflowPopUpButton release];
166     [_cells release];
167     [tabView release];
168     [_addTabButton release];
169     [partnerView release];
170     [_lastMouseDownEvent release];
171     [style release];
172     [delegate release];
173     
174     [self unregisterDraggedTypes];
175     
176     [super dealloc];
179 - (void)awakeFromNib
181     // build cells from existing tab view items
182     NSArray *existingItems = [tabView tabViewItems];
183     NSEnumerator *e = [existingItems objectEnumerator];
184     NSTabViewItem *item;
185     while(item = [e nextObject]){
186         if(![[self representedTabViewItems] containsObject:item])
187             [self addTabViewItem:item];
188     }
189     
190     // resize
191     [self setPostsFrameChangedNotifications:YES];
192     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
193     
194     // window status
195     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidBecomeKeyNotification object:[self window]];
196     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidResignKeyNotification object:[self window]];
197     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:[self window]];
201 #pragma mark -
202 #pragma mark Accessors
204 - (NSMutableArray *)cells
206     return _cells;
209 - (NSEvent *)lastMouseDownEvent
211     return _lastMouseDownEvent;
214 - (void)setLastMouseDownEvent:(NSEvent *)event
216     [event retain];
217     [_lastMouseDownEvent release];
218     _lastMouseDownEvent = event;
221 - (id)delegate
223     return delegate;
226 - (void)setDelegate:(id)object
228     [object retain];
229     [delegate release];
230     delegate = object;
233 - (NSTabView *)tabView
235     return tabView;
238 - (void)setTabView:(NSTabView *)view
240     [view retain];
241     [tabView release];
242     tabView = view;
245 - (id<PSMTabStyle>)style
247     return style;
250 - (NSString *)styleName
252     return [style name];
255 - (void)setStyleNamed:(NSString *)name
257     [style release];
258     if([name isEqualToString:@"Aqua"]){
259         style = [[PSMAquaTabStyle alloc] init];
260     }
261         else if ([name isEqualToString:@"Unified"]){
262                 style = [[PSMUnifiedTabStyle alloc] init];
263         }
264         else {
265         style = [[PSMMetalTabStyle alloc] init];
266     }
267    
268     // restyle add tab button
269     if(_addTabButton){
270         NSImage *newButtonImage = [style addTabButtonImage];
271         if(newButtonImage)
272             [_addTabButton setUsualImage:newButtonImage];
273         newButtonImage = [style addTabButtonPressedImage];
274         if(newButtonImage)
275             [_addTabButton setAlternateImage:newButtonImage];
276         newButtonImage = [style addTabButtonRolloverImage];
277         if(newButtonImage)
278             [_addTabButton setRolloverImage:newButtonImage];
279     }
280     
281     [self update];
284 - (BOOL)canCloseOnlyTab
286     return _canCloseOnlyTab;
289 - (void)setCanCloseOnlyTab:(BOOL)value
291     _canCloseOnlyTab = value;
292     if ([_cells count] == 1) {
293         [self update];
294     }
297 - (BOOL)allowsDragBetweenWindows
299         return _allowsDragBetweenWindows;
302 - (void)setAllowsDragBetweenWindows:(BOOL)flag
304         _allowsDragBetweenWindows = flag;
307 - (BOOL)hideForSingleTab
309     return _hideForSingleTab;
312 - (void)setHideForSingleTab:(BOOL)value
314     _hideForSingleTab = value;
315     [self update];
318 - (BOOL)showAddTabButton
320     return _showAddTabButton;
323 - (void)setShowAddTabButton:(BOOL)value
325     _showAddTabButton = value;
326     [self update];
329 - (int)cellMinWidth
331     return _cellMinWidth;
334 - (void)setCellMinWidth:(int)value
336     _cellMinWidth = value;
337     [self update];
340 - (int)cellMaxWidth
342     return _cellMaxWidth;
345 - (void)setCellMaxWidth:(int)value
347     _cellMaxWidth = value;
348     [self update];
351 - (int)cellOptimumWidth
353     return _cellOptimumWidth;
356 - (void)setCellOptimumWidth:(int)value
358     _cellOptimumWidth = value;
359     [self update];
362 - (BOOL)sizeCellsToFit
364     return _sizeCellsToFit;
367 - (void)setSizeCellsToFit:(BOOL)value
369     _sizeCellsToFit = value;
370     [self update];
373 - (PSMRolloverButton *)addTabButton
375     return _addTabButton;
378 - (PSMOverflowPopUpButton *)overflowPopUpButton
380     return _overflowPopUpButton;
383 #pragma mark -
384 #pragma mark Tool tips
386 - (void)setToolTip:(NSString *)value forTabViewItem:(NSTabViewItem *)tvi
388     int i, cellCount = [_cells count];
389     for (i = 0; i < cellCount; i++) {
390         PSMTabBarCell *cell = [_cells objectAtIndex:i];
391         if ([cell representedObject] == tvi)
392             [cell setToolTip:value];
393     }
395     [self update];
399 #pragma mark -
400 #pragma mark Functionality
401 - (void)addTabViewItem:(NSTabViewItem *)item
403     // create cell
404     PSMTabBarCell *cell = [[PSMTabBarCell alloc] initWithControlView:self];
405     [cell setRepresentedObject:item];
406     // bind the indicator to the represented object's status (if it exists)
407     [[cell indicator] setHidden:YES];
408     if([item identifier] != nil){
409         if([[item identifier] respondsToSelector:@selector(content)]){
410             if([[[[cell representedObject] identifier] content] respondsToSelector:@selector(isProcessing)]){
411                 NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
412                 [bindingOptions setObject:NSNegateBooleanTransformerName forKey:@"NSValueTransformerName"];
413                 [[cell indicator] bind:@"animate" toObject:[item identifier] withKeyPath:@"selection.isProcessing" options:nil];
414                 [[cell indicator] bind:@"hidden" toObject:[item identifier] withKeyPath:@"selection.isProcessing" options:bindingOptions];
415                 [[item identifier] addObserver:self forKeyPath:@"selection.isProcessing" options:nil context:nil];
416             } 
417         } 
418     } 
419     
420     // bind for the existence of an icon
421     [cell setHasIcon:NO];
422     if([item identifier] != nil){
423         if([[item identifier] respondsToSelector:@selector(content)]){
424             if([[[[cell representedObject] identifier] content] respondsToSelector:@selector(icon)]){
425                 NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
426                 [bindingOptions setObject:NSIsNotNilTransformerName forKey:@"NSValueTransformerName"];
427                 [cell bind:@"hasIcon" toObject:[item identifier] withKeyPath:@"selection.icon" options:bindingOptions];
428                 [[item identifier] addObserver:self forKeyPath:@"selection.icon" options:nil context:nil];
429             } 
430         } 
431     }
432     
433     // bind for the existence of a counter
434     [cell setCount:0];
435     if([item identifier] != nil){
436         if([[item identifier] respondsToSelector:@selector(content)]){
437             if([[[[cell representedObject] identifier] content] respondsToSelector:@selector(objectCount)]){
438                 [cell bind:@"count" toObject:[item identifier] withKeyPath:@"selection.objectCount" options:nil];
439                 [[item identifier] addObserver:self forKeyPath:@"selection.objectCount" options:nil context:nil];
440             } 
441         } 
442     }
443     
444     // bind my string value to the label on the represented tab
445     [cell bind:@"title" toObject:item withKeyPath:@"label" options:nil];
446     
447     // add to collection
448     [_cells addObject:cell];
449     [cell release];
450     if([_cells count] == [tabView numberOfTabViewItems]){
451         [self update]; // don't update unless all are accounted for!
452     }
455 - (void)removeTabForCell:(PSMTabBarCell *)cell
457     // unbind
458     [[cell indicator] unbind:@"animate"];
459     [[cell indicator] unbind:@"hidden"];
460     [cell unbind:@"hasIcon"];
461     [cell unbind:@"title"];
462     [cell unbind:@"count"];
463     
464     // remove indicator
465     if([[self subviews] containsObject:[cell indicator]]){
466         [[cell indicator] removeFromSuperview];
467     }
468     // remove tracking
469     [[NSNotificationCenter defaultCenter] removeObserver:cell];
470     if([cell closeButtonTrackingTag] != 0){
471         [self removeTrackingRect:[cell closeButtonTrackingTag]];
472     }
473     if([cell cellTrackingTag] != 0){
474         [self removeTrackingRect:[cell cellTrackingTag]];
475     }
477     // pull from collection
478     [_cells removeObject:cell];
480     [self update];
483 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
485     // the progress indicator, label, icon, or count has changed - must redraw
486     [self update];
489 #pragma mark -
490 #pragma mark Hide/Show
492 - (void)hideTabBar:(BOOL)hide animate:(BOOL)animate
494     if(!_awakenedFromNib)
495         return;
496     if(_isHidden && hide)
497         return;
498     if(!_isHidden && !hide)
499         return;
500     
501     [[self subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
502     _hideIndicators = YES;
503     
504     NSTimer *animationTimer;
505     _isHidden = hide;
506     _currentStep = 0;
507     if(!animate)
508         _currentStep = (int)kPSMHideAnimationSteps;
509     
510     float partnerOriginalHeight, partnerOriginalY, myOriginalHeight, myOriginalY, partnerTargetHeight, partnerTargetY, myTargetHeight, myTargetY;
511     
512     // current (original) values
513     myOriginalHeight = [self frame].size.height;
514     myOriginalY = [self frame].origin.y;
515     if(partnerView){
516         partnerOriginalHeight = [partnerView frame].size.height;
517         partnerOriginalY = [partnerView frame].origin.y;
518     } else {
519         partnerOriginalHeight = [[self window] frame].size.height;
520         partnerOriginalY = [[self window] frame].origin.y;
521     }
522     
523     // target values for partner
524     if(partnerView){
525         // above or below me?
526         if((myOriginalY - 22) > partnerOriginalY){
527             // partner is below me
528             if(_isHidden){
529                 // I'm shrinking
530                 myTargetY = myOriginalY + 21;
531                 myTargetHeight = myOriginalHeight - 21;
532                 partnerTargetY = partnerOriginalY;
533                 partnerTargetHeight = partnerOriginalHeight + 21;
534             } else {
535                 // I'm growing
536                 myTargetY = myOriginalY - 21;
537                 myTargetHeight = myOriginalHeight + 21;
538                 partnerTargetY = partnerOriginalY;
539                 partnerTargetHeight = partnerOriginalHeight - 21;
540             }
541         } else {
542             // partner is above me
543             if(_isHidden){
544                 // I'm shrinking
545                 myTargetY = myOriginalY;
546                 myTargetHeight = myOriginalHeight - 21;
547                 partnerTargetY = partnerOriginalY - 21;
548                 partnerTargetHeight = partnerOriginalHeight + 21;
549             } else {
550                 // I'm growing
551                 myTargetY = myOriginalY;
552                 myTargetHeight = myOriginalHeight + 21;
553                 partnerTargetY = partnerOriginalY + 21;
554                 partnerTargetHeight = partnerOriginalHeight - 21;
555             }
556         }
557     } else {
558         // for window movement
559         if(_isHidden){
560             // I'm shrinking
561             myTargetY = myOriginalY;
562             myTargetHeight = myOriginalHeight - 21;
563             partnerTargetY = partnerOriginalY + 21;
564             partnerTargetHeight = partnerOriginalHeight - 21;
565         } else {
566             // I'm growing
567             myTargetY = myOriginalY;
568             myTargetHeight = myOriginalHeight + 21;
569             partnerTargetY = partnerOriginalY - 21;
570             partnerTargetHeight = partnerOriginalHeight + 21;
571         }
572     }
574     NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:myOriginalY], @"myOriginalY", [NSNumber numberWithFloat:partnerOriginalY], @"partnerOriginalY", [NSNumber numberWithFloat:myOriginalHeight], @"myOriginalHeight", [NSNumber numberWithFloat:partnerOriginalHeight], @"partnerOriginalHeight", [NSNumber numberWithFloat:myTargetY], @"myTargetY", [NSNumber numberWithFloat:partnerTargetY], @"partnerTargetY", [NSNumber numberWithFloat:myTargetHeight], @"myTargetHeight", [NSNumber numberWithFloat:partnerTargetHeight], @"partnerTargetHeight", nil];
575     animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0/20.0) target:self selector:@selector(animateShowHide:) userInfo:userInfo repeats:YES];
578 - (void)animateShowHide:(NSTimer *)timer
580     // moves the frame of the tab bar and window (or partner view) linearly to hide or show the tab bar
581     NSRect myFrame = [self frame];
582     float myCurrentY = ([[[timer userInfo] objectForKey:@"myOriginalY"] floatValue] + (([[[timer userInfo] objectForKey:@"myTargetY"] floatValue] - [[[timer userInfo] objectForKey:@"myOriginalY"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
583     float myCurrentHeight = ([[[timer userInfo] objectForKey:@"myOriginalHeight"] floatValue] + (([[[timer userInfo] objectForKey:@"myTargetHeight"] floatValue] - [[[timer userInfo] objectForKey:@"myOriginalHeight"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
584     float partnerCurrentY = ([[[timer userInfo] objectForKey:@"partnerOriginalY"] floatValue] + (([[[timer userInfo] objectForKey:@"partnerTargetY"] floatValue] - [[[timer userInfo] objectForKey:@"partnerOriginalY"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
585     float partnerCurrentHeight = ([[[timer userInfo] objectForKey:@"partnerOriginalHeight"] floatValue] + (([[[timer userInfo] objectForKey:@"partnerTargetHeight"] floatValue] - [[[timer userInfo] objectForKey:@"partnerOriginalHeight"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
586     
587     NSRect myNewFrame = NSMakeRect(myFrame.origin.x, myCurrentY, myFrame.size.width, myCurrentHeight);
588     
589     if(partnerView){
590         // resize self and view
591         [partnerView setFrame:NSMakeRect([partnerView frame].origin.x, partnerCurrentY, [partnerView frame].size.width, partnerCurrentHeight)];
592         [partnerView setNeedsDisplay:YES];
593         [self setFrame:myNewFrame];
594     } else {
595         // resize self and window
596         [[self window] setFrame:NSMakeRect([[self window] frame].origin.x, partnerCurrentY, [[self window] frame].size.width, partnerCurrentHeight) display:YES];
597         [self setFrame:myNewFrame];
598     }
599     
600     // next
601     _currentStep++;
602     if(_currentStep == kPSMHideAnimationSteps + 1){
603         [timer invalidate];
604         [self viewDidEndLiveResize];
605         _hideIndicators = NO;
606         [self update];
607     }
608     [[self window] display];
611 - (id)partnerView
613     return partnerView;
616 - (void)setPartnerView:(id)view
618     [partnerView release];
619     [view retain];
620     partnerView = view;
623 #pragma mark -
624 #pragma mark Drawing
626 - (BOOL)isFlipped
628     return YES;
631 - (void)drawRect:(NSRect)rect 
633     [style drawTabBar:self inRect:rect];
636 - (void)update
638     // abandon hope, all ye who enter here :-)
639     // this method handles all of the cell layout, and is called when something changes to require the refresh.  This method is not called during drag and drop; see the PSMTabDragAssistant's calculateDragAnimationForTabBar: method, which does layout in that case.
640    
641     // make sure all of our tabs are accounted for before updating
642     if ([tabView numberOfTabViewItems] != [_cells count]) {
643         return;
644     }
646     // hide/show? (these return if already in desired state)
647     if((_hideForSingleTab) && ([_cells count] <= 1)){
648         [self hideTabBar:YES animate:YES];
649     } else {
650         [self hideTabBar:NO animate:YES];
651     }
653     // size all cells appropriately and create tracking rects
654     // nuke old tracking rects
655     int i, cellCount = [_cells count];
656     for(i = 0; i < cellCount; i++){
657         id cell = [_cells objectAtIndex:i];
658         [[NSNotificationCenter defaultCenter] removeObserver:cell];
659         if([cell closeButtonTrackingTag] != 0){
660             [self removeTrackingRect:[cell closeButtonTrackingTag]];
661         }
662         if([cell cellTrackingTag] != 0){
663             [self removeTrackingRect:[cell cellTrackingTag]];
664         }
665     }
667     // nuke old tool tips
668     [self removeAllToolTips];
670     // calculate number of cells to fit in control and cell widths
671     float availableWidth = [self availableCellWidth];
672     NSMutableArray *newWidths = [NSMutableArray arrayWithCapacity:cellCount];
673     int numberOfVisibleCells = 1;
674     float totalOccupiedWidth = 0.0;
675     NSMenu *overflowMenu = nil;
676     for(i = 0; i < cellCount; i++){
677         PSMTabBarCell *cell = [_cells objectAtIndex:i];
678         float width;
679         
680         // supress close button? 
681         if (cellCount == 1 && [self canCloseOnlyTab] == NO) {
682             [cell setCloseButtonSuppressed:YES];
683         } else {
684             [cell setCloseButtonSuppressed:NO];
685         }
686         
687         // Determine cell width
688         if(_sizeCellsToFit){
689             width = [cell desiredWidthOfCell];
690             if (width > _cellMaxWidth) {
691                 width = _cellMaxWidth;
692             }
693         } else {
694             width = _cellOptimumWidth;
695         }
696         
697         // too much?
698         totalOccupiedWidth += width;
699         if (totalOccupiedWidth >= availableWidth) {
700             numberOfVisibleCells = i;
701             if(_sizeCellsToFit){
702                 int neededWidth = width - (totalOccupiedWidth - availableWidth);
703                 // can I squeeze it in without violating min cell width?
704                 int widthIfAllMin = (numberOfVisibleCells + 1) * _cellMinWidth;
705             
706                 if ((width + widthIfAllMin) <= availableWidth) {
707                     // squeeze - distribute needed sacrifice among all cells
708                     int q;
709                     for(q = (i - 1); q >= 0; q--){
710                         int desiredReduction = (int)neededWidth/(q+1);
711                         if(([[newWidths objectAtIndex:q] floatValue] - desiredReduction) < _cellMinWidth){
712                             int actualReduction = (int)[[newWidths objectAtIndex:q] floatValue] - _cellMinWidth;
713                             [newWidths replaceObjectAtIndex:q withObject:[NSNumber numberWithFloat:_cellMinWidth]];
714                             neededWidth -= actualReduction;
715                         } else {
716                             int newCellWidth = (int)[[newWidths objectAtIndex:q] floatValue] - desiredReduction;
717                             [newWidths replaceObjectAtIndex:q withObject:[NSNumber numberWithFloat:newCellWidth]];
718                             neededWidth -= desiredReduction;
719                         }
720                     }
721                     // one cell left!
722                     int thisWidth = width - neededWidth;
723                     [newWidths addObject:[NSNumber numberWithFloat:thisWidth]];
724                     numberOfVisibleCells++;
725                 } else {
726                     // stretch - distribute leftover room among cells
727                     int leftoverWidth = availableWidth - totalOccupiedWidth + width;
728                     int q;
729                     for(q = (i - 1); q >= 0; q--){
730                         int desiredAddition = (int)leftoverWidth/(q+1);
731                         int newCellWidth = (int)[[newWidths objectAtIndex:q] floatValue] + desiredAddition;
732                         [newWidths replaceObjectAtIndex:q withObject:[NSNumber numberWithFloat:newCellWidth]];
733                         leftoverWidth -= desiredAddition;
734                     }
735                 }
736                 break; // done assigning widths; remaining cells go in overflow menu
737             } else {
738                 int revisedWidth = availableWidth/(i + 1);
739                 if(revisedWidth >= _cellMinWidth){
740                     int q;
741                     totalOccupiedWidth = 0;
742                     for(q = 0; q < [newWidths count]; q++){
743                         [newWidths replaceObjectAtIndex:q withObject:[NSNumber numberWithFloat:revisedWidth]];
744                         totalOccupiedWidth += revisedWidth;
745                     }
746                     // just squeezed this one in...
747                     [newWidths addObject:[NSNumber numberWithFloat:revisedWidth]];
748                     totalOccupiedWidth += revisedWidth;
749                     numberOfVisibleCells++;
750                 } else {
751                     // couldn't fit that last one...
752                     break;
753                 }
754             }
755         } else {
756             numberOfVisibleCells = cellCount;
757             [newWidths addObject:[NSNumber numberWithFloat:width]];
758         }
759     }
761     // Set up cells with frames and rects
762     NSRect cellRect = [self genericCellRect];
763     for(i = 0; i < cellCount; i++){
764         PSMTabBarCell *cell = [_cells objectAtIndex:i];
765         NSTabViewItem *tvi = [cell representedObject];
766         int tabState = 0;
767         if (i < numberOfVisibleCells) {
768             // set cell frame
769             cellRect.size.width = [[newWidths objectAtIndex:i] floatValue];
770             [cell setFrame:cellRect];
771             NSTrackingRectTag tag;
772             
773             // close button tracking rect
774             if ([cell hasCloseButton]) {
775                 tag = [self addTrackingRect:[cell closeButtonRectForFrame:cellRect] owner:cell userData:nil assumeInside:NO];
776                 [cell setCloseButtonTrackingTag:tag];
777             }
778             
779             // entire tab tracking rect
780             tag = [self addTrackingRect:cellRect owner:cell userData:nil assumeInside:NO];
781             [cell setCellTrackingTag:tag];
782             [cell setEnabled:YES];
784             // add tool tip
785             NSString *tt = [cell toolTip];
786             if (tt && [tt length] > 0)
787                 [self addToolTipRect:cellRect owner:tt userData:NULL];
789             // selected? set tab states...
790             if([tvi isEqualTo:[tabView selectedTabViewItem]]){
791                 [cell setState:NSOnState];
792                 tabState |= PSMTab_SelectedMask;
793                 // previous cell
794                 if(i > 0){
795                     [[_cells objectAtIndex:i-1] setTabState:([(PSMTabBarCell *)[_cells objectAtIndex:i-1] tabState] | PSMTab_RightIsSelectedMask)];
796                 }
797                 // next cell - see below
798             } else {
799                 [cell setState:NSOffState];
800                 // see if prev cell was selected
801                 if(i > 0){
802                     if([[_cells objectAtIndex:i-1] state] == NSOnState){
803                         tabState |= PSMTab_LeftIsSelectedMask;
804                     }
805                 }
806             }
807             // more tab states
808             if(cellCount == 1){
809                 tabState |= PSMTab_PositionLeftMask | PSMTab_PositionRightMask | PSMTab_PositionSingleMask;
810             } else if(i == 0){
811                 tabState |= PSMTab_PositionLeftMask;
812             } else if(i-1 == cellCount){
813                 tabState |= PSMTab_PositionRightMask;
814             }
815             [cell setTabState:tabState];
816             [cell setIsInOverflowMenu:NO];
817             
818             // indicator
819             if(![[cell indicator] isHidden] && !_hideIndicators){
820                 [[cell indicator] setFrame:[cell indicatorRectForFrame:cellRect]];
821                 if(![[self subviews] containsObject:[cell indicator]]){
822                     [self addSubview:[cell indicator]];
823                     [[cell indicator] startAnimation:self];
824                 }
825             }
826             
827             // next...
828             cellRect.origin.x += [[newWidths objectAtIndex:i] floatValue];
829             
830         } else {
831             // set up menu items
832             NSMenuItem *menuItem;
833             if(overflowMenu == nil){
834                 overflowMenu = [[[NSMenu alloc] initWithTitle:@"TITLE"] autorelease];
835                 [overflowMenu insertItemWithTitle:@"FIRST" action:nil keyEquivalent:@"" atIndex:0]; // Because the overflowPupUpButton is a pull down menu
836             }
837             menuItem = [[[NSMenuItem alloc] initWithTitle:[[cell attributedStringValue] string] action:@selector(overflowMenuAction:) keyEquivalent:@""] autorelease];
838             [menuItem setTarget:self];
839             [menuItem setRepresentedObject:tvi];
840             [cell setIsInOverflowMenu:YES];
841             [[cell indicator] removeFromSuperview];
842             if ([tvi isEqualTo:[tabView selectedTabViewItem]])
843                 [menuItem setState:NSOnState];
844             if([cell hasIcon])
845                 [menuItem setImage:[[[tvi identifier] content] icon]];
846             if([cell count] > 0)
847                 [menuItem setTitle:[[menuItem title] stringByAppendingFormat:@" (%d)",[cell count]]];
848             [overflowMenu addItem:menuItem];
849         }
850     }
851     
853     // Overflow menu
854     cellRect.origin.y = 0;
855     cellRect.size.height = kPSMTabBarControlHeight;
856     cellRect.size.width = [style rightMarginForTabBarControl];
857     if (overflowMenu) {
858         cellRect.origin.x = [self frame].size.width - [style rightMarginForTabBarControl] + 1;
859         if(![[self subviews] containsObject:_overflowPopUpButton]){
860             [self addSubview:_overflowPopUpButton];
861         }
862         [_overflowPopUpButton setFrame:cellRect];
863         [_overflowPopUpButton setMenu:overflowMenu];
864         if ([_overflowPopUpButton isHidden]) [_overflowPopUpButton setHidden:NO];
865     } else {
866         if (![_overflowPopUpButton isHidden]) [_overflowPopUpButton setHidden:YES];
867     }
868     
869     // add tab button
870     if(!overflowMenu && _showAddTabButton){
871         if(![[self subviews] containsObject:_addTabButton])
872             [self addSubview:_addTabButton];
873         if([_addTabButton isHidden] && _showAddTabButton)
874             [_addTabButton setHidden:NO];
875         cellRect.size = [_addTabButton frame].size;
876         cellRect.origin.y = MARGIN_Y;
877         cellRect.origin.x += 2;
878         [_addTabButton setImage:[style addTabButtonImage]];
879         [_addTabButton setFrame:cellRect];
880         [_addTabButton setNeedsDisplay:YES];
881     } else {
882         [_addTabButton setHidden:YES];
883         [_addTabButton setNeedsDisplay:YES];
884     }
885     
886     [self setNeedsDisplay:YES];
889 #pragma mark -
890 #pragma mark Mouse Tracking
892 - (BOOL)mouseDownCanMoveWindow
894     return NO;
897 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
899     return YES;
902 - (void)mouseDown:(NSEvent *)theEvent
904     // keep for dragging
905     [self setLastMouseDownEvent:theEvent];
906     // what cell?
907     NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
908     NSRect cellFrame;
909     PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
910     if(cell){
911 #if 0
912         NSRect iconRect = [cell closeButtonRectForFrame:cellFrame];
913         if(NSMouseInRect(mousePt, iconRect,[self isFlipped])){
914             [cell setCloseButtonPressed:YES];
915         } else {
916             [cell setCloseButtonPressed:NO];
917         }
918         [self setNeedsDisplay:YES];
919 #else
920         // HACK!  Let the tabs react on the mouse down instead of mouse up
921         NSRect iconRect = [cell closeButtonRectForFrame:cellFrame];
922         if((NSMouseInRect(mousePt, iconRect,[self isFlipped]))){
923             //[self performSelector:@selector(closeTabClick:) withObject:cell];
924             [self closeTabClick:cell];
925         } else if(NSMouseInRect(mousePt, cellFrame,[self isFlipped])){
926             //[self performSelector:@selector(tabClick:) withObject:cell];
927             [self tabClick:cell];
928         } else {
929             //[self performSelector:@selector(tabNothing:) withObject:cell];
930             [self tabNothing:cell];
931         }
932 #endif
933     }
936 - (void)mouseDragged:(NSEvent *)theEvent
938     if([self lastMouseDownEvent] == nil){
939         return;
940     }
941     
942     if ([_cells count] < 2) {
943         return;
944     }
945     
946     NSRect cellFrame;
947     NSPoint trackingStartPoint = [self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil];
948     PSMTabBarCell *cell = [self cellForPoint:trackingStartPoint cellFrame:&cellFrame];
949     if (!cell) 
950         return;
951     
952     NSPoint currentPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
953     float dx = fabs(currentPoint.x - trackingStartPoint.x);
954     float dy = fabs(currentPoint.y - trackingStartPoint.y);
955     float distance = sqrt(dx * dx + dy * dy);
956     if (distance < 10)
957         return;
958     
959     if(![[PSMTabDragAssistant sharedDragAssistant] isDragging]) {
960         [[PSMTabDragAssistant sharedDragAssistant] startDraggingCell:cell fromTabBar:self withMouseDownEvent:[self lastMouseDownEvent]];
961     }
964 - (void)mouseUp:(NSEvent *)theEvent
966 #if 0  // HACK!  Tabs react on mouse down instead of mouse up
967     // what cell?
968     NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
969     NSRect cellFrame, mouseDownCellFrame;
970     PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
971     PSMTabBarCell *mouseDownCell = [self cellForPoint:[self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil] cellFrame:&mouseDownCellFrame];
972     if(cell){
973         NSRect iconRect = [mouseDownCell closeButtonRectForFrame:mouseDownCellFrame];
974         if((NSMouseInRect(mousePt, iconRect,[self isFlipped])) && [mouseDownCell closeButtonPressed]){
975             [self performSelector:@selector(closeTabClick:) withObject:cell];
976         } else if(NSMouseInRect(mousePt, mouseDownCellFrame,[self isFlipped])){
977             [mouseDownCell setCloseButtonPressed:NO];
978             [self performSelector:@selector(tabClick:) withObject:cell];
979         } else {
980             [mouseDownCell setCloseButtonPressed:NO];
981             [self performSelector:@selector(tabNothing:) withObject:cell];
982         }
983     }
984 #endif
987 #pragma mark -
988 #pragma mark Drag and Drop
990 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent
992     return YES;
995 // NSDraggingSource
996 - (unsigned int)draggingSourceOperationMaskForLocal:(BOOL)isLocal
998     return (isLocal ? NSDragOperationMove : NSDragOperationNone);
1001 - (BOOL)ignoreModifierKeysWhileDragging
1003     return YES;
1006 - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
1008     [[PSMTabDragAssistant sharedDragAssistant] draggedImageEndedAt:aPoint operation:operation];
1011 // NSDraggingDestination
1012 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
1014     NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
1015     _delegateHandlingDrag = NO;
1016     if([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
1017                 
1018                 if ([sender draggingSource] != self && ![self allowsDragBetweenWindows])
1019                         return NSDragOperationNone;
1020                 
1021         [[PSMTabDragAssistant sharedDragAssistant] draggingEnteredTabBar:self atPoint:point];
1022         return NSDragOperationMove;
1023     } else if (delegate && [delegate respondsToSelector:@selector(tabBarControl:draggingEntered:forTabAtIndex:)]) {
1024         NSDragOperation op = [delegate tabBarControl:self draggingEntered:sender forTabAtIndex:[self indexOfCellAtPoint:point]];
1025         _delegateHandlingDrag = (op != NSDragOperationNone);
1026         _delegateInitialDragOperation = op;
1027         return op;
1028     }
1029         
1030     return NSDragOperationNone;
1033 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
1035     NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
1036     if ([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
1037                 
1038                 if ([sender draggingSource] != self && ![self allowsDragBetweenWindows])
1039                         return NSDragOperationNone;
1040                 
1041         [[PSMTabDragAssistant sharedDragAssistant] draggingUpdatedInTabBar:self atPoint:point];
1042         return NSDragOperationMove;
1043     } else if (_delegateHandlingDrag) {
1044         if ([delegate respondsToSelector:@selector(tabBarControl:draggingUpdated:forTabAtIndex:)])
1045             return [delegate tabBarControl:self draggingUpdated:sender forTabAtIndex:[self indexOfCellAtPoint:point]];
1046         else
1047             return _delegateInitialDragOperation;
1048     }
1049         
1050     return NSDragOperationNone;
1053 - (void)draggingExited:(id <NSDraggingInfo>)sender
1055     if (!_delegateHandlingDrag) {
1056         [[PSMTabDragAssistant sharedDragAssistant] draggingExitedTabBar:self];
1057     } else if ([delegate respondsToSelector:@selector(tabBarControl:draggingExited:forTabAtIndex:)]) {
1058         NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
1059         [delegate tabBarControl:self draggingExited:sender forTabAtIndex:[self indexOfCellAtPoint:point]];
1060     }
1063 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
1065     if (_delegateHandlingDrag && [delegate respondsToSelector:@selector(tabBarControl:prepareForDragOperation:forTabAtIndex:)]) {
1066         NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
1067         return [delegate tabBarControl:self prepareForDragOperation:sender forTabAtIndex:[self indexOfCellAtPoint:point]];
1068     }
1069     
1070     return YES;
1073 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
1075     if (!_delegateHandlingDrag) {
1076 #if 1
1077         // HACK!  Used below.
1078         NSTabViewItem *tvi = [[[PSMTabDragAssistant sharedDragAssistant] draggedCell] representedObject];
1079 #endif
1081         [[PSMTabDragAssistant sharedDragAssistant] performDragOperation];
1083 #if 1
1084         // HACK!  Notify the delegate that a tab was dragged to a new position.
1085         if (delegate && [delegate respondsToSelector:@selector(tabView:didDragTabViewItem:toIndex:)]) {
1086             int idx = [[self representedTabViewItems] indexOfObject:tvi];
1087             if (NSNotFound != idx) {
1088                 [delegate tabView:[self tabView] didDragTabViewItem:tvi toIndex:idx];
1089             }
1090         }
1091 #endif
1092     } else {
1093         if ([delegate respondsToSelector:@selector(tabBarControl:performDragOperation:forTabAtIndex:)]) {
1094             NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
1095             return [delegate tabBarControl:self performDragOperation:sender forTabAtIndex:[self indexOfCellAtPoint:point]];
1096         } else {
1097             return NO;
1098         }
1099     }
1101     return YES;
1104 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
1106     if (_delegateHandlingDrag && [delegate respondsToSelector:@selector(tabBarControl:concludeDragOperation:forTabAtIndex:)]) {
1107         NSPoint point = [self convertPoint:[sender draggingLocation] fromView:nil];
1108         [delegate tabBarControl:self concludeDragOperation:sender forTabAtIndex:[self indexOfCellAtPoint:point]];
1109     }
1112 #pragma mark -
1113 #pragma mark Actions
1115 - (void)overflowMenuAction:(id)sender
1117     [tabView selectTabViewItem:[sender representedObject]];
1118     [self update];
1121 - (void)closeTabClick:(id)sender
1123     [sender retain];
1124     if(([_cells count] == 1) && (![self canCloseOnlyTab]))
1125         return;
1126     
1127     if(([self delegate]) && ([[self delegate] respondsToSelector:@selector(tabView:shouldCloseTabViewItem:)])){
1128         if(![[self delegate] tabView:tabView shouldCloseTabViewItem:[sender representedObject]]){
1129             // fix mouse downed close button
1130             [sender setCloseButtonPressed:NO];
1131             return;
1132         }
1133     }
1134     
1135     if(([self delegate]) && ([[self delegate] respondsToSelector:@selector(tabView:willCloseTabViewItem:)])){
1136         [[self delegate] tabView:tabView willCloseTabViewItem:[sender representedObject]];
1137     }
1138     
1139     [[sender representedObject] retain];
1140     [tabView removeTabViewItem:[sender representedObject]];
1141     
1142     if(([self delegate]) && ([[self delegate] respondsToSelector:@selector(tabView:didCloseTabViewItem:)])){
1143         [[self delegate] tabView:tabView didCloseTabViewItem:[sender representedObject]];
1144     }
1145     [[sender representedObject] release];
1146     [sender release];
1149 - (void)tabClick:(id)sender
1151     [tabView selectTabViewItem:[sender representedObject]];
1152     [self update];
1155 - (void)tabNothing:(id)sender
1157     [self update];  // takes care of highlighting based on state
1160 - (void)frameDidChange:(NSNotification *)notification
1162     [self update];
1163     // trying to address the drawing artifacts for the progress indicators - hackery follows
1164     // this one fixes the "blanking" effect when the control hides and shows itself
1165     NSEnumerator *e = [_cells objectEnumerator];
1166     PSMTabBarCell *cell;
1167     while(cell = [e nextObject]){
1168         [[cell indicator] stopAnimation:self];
1169         [[cell indicator] startAnimation:self];
1170     }
1171     [self setNeedsDisplay:YES];
1174 - (void)viewWillStartLiveResize
1176     NSEnumerator *e = [_cells objectEnumerator];
1177     PSMTabBarCell *cell;
1178     while(cell = [e nextObject]){
1179         [[cell indicator] stopAnimation:self];
1180     }
1181     [self setNeedsDisplay:YES];
1184 -(void)viewDidEndLiveResize
1186     NSEnumerator *e = [_cells objectEnumerator];
1187     PSMTabBarCell *cell;
1188     while(cell = [e nextObject]){
1189         [[cell indicator] startAnimation:self];
1190     }
1191     [self setNeedsDisplay:YES];
1194 - (void)windowDidMove:(NSNotification *)aNotification
1196     [self setNeedsDisplay:YES];
1199 - (void)windowStatusDidChange:(NSNotification *)notification
1201     // hide? must readjust things if I'm not supposed to be showing
1202     // this block of code only runs when the app launches
1203     if(_hideForSingleTab && ([_cells count] <= 1) && !_awakenedFromNib){
1204         // must adjust frames now before display
1205         NSRect myFrame = [self frame];
1206         if(partnerView){
1207             NSRect partnerFrame = [partnerView frame];
1208             // above or below me?
1209             if(([self frame].origin.y - 22) > [partnerView frame].origin.y){
1210                 // partner is below me
1211                 [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y + 21, myFrame.size.width, myFrame.size.height - 21)];
1212                 [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y, partnerFrame.size.width, partnerFrame.size.height + 21)];
1213             } else {
1214                 // partner is above me
1215                 [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
1216                 [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y - 21, partnerFrame.size.width, partnerFrame.size.height + 21)];
1217             }
1218             [partnerView setNeedsDisplay:YES];
1219             [self setNeedsDisplay:YES];
1220         } else {
1221             // for window movement
1222             NSRect windowFrame = [[self window] frame];
1223             [[self window] setFrame:NSMakeRect(windowFrame.origin.x, windowFrame.origin.y + 21, windowFrame.size.width, windowFrame.size.height - 21) display:YES];
1224             [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
1225         }
1226         _isHidden = YES;
1227         [self setNeedsDisplay:YES];
1228         //[[self window] display];
1229     }
1230      _awakenedFromNib = YES;
1231     [self update];
1234 #pragma mark -
1235 #pragma mark NSTabView Delegate
1237 - (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
1239     // here's a weird one - this message is sent before the "tabViewDidChangeNumberOfTabViewItems"
1240     // message, thus I can end up updating when there are no cells, if no tabs were (yet) present
1241     if([_cells count] > 0){
1242         [self update];
1243     }
1244     if([self delegate]){
1245         if([[self delegate] respondsToSelector:@selector(tabView:didSelectTabViewItem:)]){
1246             [[self delegate] performSelector:@selector(tabView:didSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
1247         }
1248     }
1250     
1251 - (BOOL)tabView:(NSTabView *)aTabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem
1253     if([self delegate]){
1254         if([[self delegate] respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)]){
1255             return (int)[[self delegate] performSelector:@selector(tabView:shouldSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
1256         } else {
1257             return YES;
1258         }
1259     } else {
1260         return YES;
1261     }
1263 - (void)tabView:(NSTabView *)aTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
1265     if([self delegate]){
1266         if([[self delegate] respondsToSelector:@selector(tabView:willSelectTabViewItem:)]){
1267             [[self delegate] performSelector:@selector(tabView:willSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
1268         }
1269     }
1272 - (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)aTabView
1274     NSArray *tabItems = [tabView tabViewItems];
1275     // go through cells, remove any whose representedObjects are not in [tabView tabViewItems]
1276     NSEnumerator *e = [_cells objectEnumerator];
1277     PSMTabBarCell *cell;
1278     while(cell = [e nextObject]){
1279         if(![tabItems containsObject:[cell representedObject]]){
1280             [self removeTabForCell:cell];
1281         }
1282     }
1283     
1284     // go through tab view items, add cell for any not present
1285     NSMutableArray *cellItems = [self representedTabViewItems];
1286     NSEnumerator *ex = [tabItems objectEnumerator];
1287     NSTabViewItem *item;
1288     while(item = [ex nextObject]){
1289         if(![cellItems containsObject:item]){
1290             [self addTabViewItem:item];
1291         }
1292     }
1294 #if 0
1295     // HACK!  Make sure '_cells' is ordered the same as 'tabItems'.
1296     NSMutableArray *temp = [[NSMutableArray alloc] initWithArray:_cells];
1297     e = [_cells objectEnumerator];
1298     int count = [temp count];
1299     while ((cell = [e nextObject])) {
1300         int idx = [tabItems indexOfObject:[cell representedObject]];
1301         if (NSNotFound != idx && idx < count) {
1302             [temp replaceObjectAtIndex:idx withObject:cell];
1303         }
1304     }
1306     [_cells release];
1307     _cells = temp;
1309     if ([_cells count] == [tabView numberOfTabViewItems]) {
1310         [self update]; // don't update unless all are accounted for!
1311     }
1312 #endif
1313   
1314     // pass along for other delegate responses
1315     if([self delegate]){
1316         if([[self delegate] respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)]){
1317             [[self delegate] performSelector:@selector(tabViewDidChangeNumberOfTabViewItems:) withObject:aTabView];
1318         }
1319     }
1322 #pragma mark -
1323 #pragma mark Archiving
1325 - (void)encodeWithCoder:(NSCoder *)aCoder 
1327     [super encodeWithCoder:aCoder];
1328     if ([aCoder allowsKeyedCoding]) {
1329         [aCoder encodeObject:_cells forKey:@"PSMcells"];
1330         [aCoder encodeObject:tabView forKey:@"PSMtabView"];
1331         [aCoder encodeObject:_overflowPopUpButton forKey:@"PSMoverflowPopUpButton"];
1332         [aCoder encodeObject:_addTabButton forKey:@"PSMaddTabButton"];
1333         [aCoder encodeObject:style forKey:@"PSMstyle"];
1334         [aCoder encodeBool:_canCloseOnlyTab forKey:@"PSMcanCloseOnlyTab"];
1335         [aCoder encodeBool:_hideForSingleTab forKey:@"PSMhideForSingleTab"];
1336         [aCoder encodeBool:_showAddTabButton forKey:@"PSMshowAddTabButton"];
1337         [aCoder encodeBool:_sizeCellsToFit forKey:@"PSMsizeCellsToFit"];
1338         [aCoder encodeInt:_cellMinWidth forKey:@"PSMcellMinWidth"];
1339         [aCoder encodeInt:_cellMaxWidth forKey:@"PSMcellMaxWidth"];
1340         [aCoder encodeInt:_cellOptimumWidth forKey:@"PSMcellOptimumWidth"];
1341         [aCoder encodeInt:_currentStep forKey:@"PSMcurrentStep"];
1342         [aCoder encodeBool:_isHidden forKey:@"PSMisHidden"];
1343         [aCoder encodeBool:_hideIndicators forKey:@"PSMhideIndicators"];
1344         [aCoder encodeObject:partnerView forKey:@"PSMpartnerView"];
1345         [aCoder encodeBool:_awakenedFromNib forKey:@"PSMawakenedFromNib"];
1346         [aCoder encodeObject:_lastMouseDownEvent forKey:@"PSMlastMouseDownEvent"];
1347         [aCoder encodeObject:delegate forKey:@"PSMdelegate"];
1348         
1349     }
1352 - (id)initWithCoder:(NSCoder *)aDecoder 
1354     self = [super initWithCoder:aDecoder];
1355     if (self) {
1356         if ([aDecoder allowsKeyedCoding]) {
1357             _cells = [[aDecoder decodeObjectForKey:@"PSMcells"] retain];
1358             tabView = [[aDecoder decodeObjectForKey:@"PSMtabView"] retain];
1359             _overflowPopUpButton = [[aDecoder decodeObjectForKey:@"PSMoverflowPopUpButton"] retain];
1360             _addTabButton = [[aDecoder decodeObjectForKey:@"PSMaddTabButton"] retain];
1361             style = [[aDecoder decodeObjectForKey:@"PSMstyle"] retain];
1362             _canCloseOnlyTab = [aDecoder decodeBoolForKey:@"PSMcanCloseOnlyTab"];
1363             _hideForSingleTab = [aDecoder decodeBoolForKey:@"PSMhideForSingleTab"];
1364             _showAddTabButton = [aDecoder decodeBoolForKey:@"PSMshowAddTabButton"];
1365             _sizeCellsToFit = [aDecoder decodeBoolForKey:@"PSMsizeCellsToFit"];
1366             _cellMinWidth = [aDecoder decodeIntForKey:@"PSMcellMinWidth"];
1367             _cellMaxWidth = [aDecoder decodeIntForKey:@"PSMcellMaxWidth"];
1368             _cellOptimumWidth = [aDecoder decodeIntForKey:@"PSMcellOptimumWidth"];
1369             _currentStep = [aDecoder decodeIntForKey:@"PSMcurrentStep"];
1370             _isHidden = [aDecoder decodeBoolForKey:@"PSMisHidden"];
1371             _hideIndicators = [aDecoder decodeBoolForKey:@"PSMhideIndicators"];
1372             partnerView = [[aDecoder decodeObjectForKey:@"PSMpartnerView"] retain];
1373             _awakenedFromNib = [aDecoder decodeBoolForKey:@"PSMawakenedFromNib"];
1374             _lastMouseDownEvent = [[aDecoder decodeObjectForKey:@"PSMlastMouseDownEvent"] retain];
1375             delegate = [[aDecoder decodeObjectForKey:@"PSMdelegate"] retain];
1376         }
1377     }
1378     return self;
1381 #pragma mark -
1382 #pragma mark IB Palette
1384 - (NSSize)minimumFrameSizeFromKnobPosition:(int)position
1386     return NSMakeSize(100.0, 22.0);
1389 - (NSSize)maximumFrameSizeFromKnobPosition:(int)knobPosition
1391     return NSMakeSize(10000.0, 22.0);
1394 - (void)placeView:(NSRect)newFrame
1396     // this is called any time the view is resized in IB
1397     [self setFrame:newFrame];
1398     [self update];
1401 #pragma mark -
1402 #pragma mark Convenience
1404 - (NSMutableArray *)representedTabViewItems
1406     NSMutableArray *temp = [NSMutableArray arrayWithCapacity:[_cells count]];
1407     NSEnumerator *e = [_cells objectEnumerator];
1408     PSMTabBarCell *cell;
1409     while(cell = [e nextObject]){
1410         [temp addObject:[cell representedObject]];
1411     }
1412     return temp;
1415 - (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame
1417     unsigned i = [self indexOfCellAtPoint:point cellFrame:outFrame];
1418     if (i == NSNotFound)
1419         return nil;
1420     PSMTabBarCell *cell = [_cells objectAtIndex:i];
1421     return cell;
1424 - (unsigned)indexOfCellAtPoint:(NSPoint)point
1426     return [self indexOfCellAtPoint:point cellFrame:NULL];
1429 - (unsigned)indexOfCellAtPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame
1431     NSRect aRect = [self genericCellRect];
1432     
1433     if(!NSPointInRect(point,aRect)){
1434         return NSNotFound;
1435     }
1436     
1437     int i, cnt = [_cells count];
1438     for(i = 0; i < cnt; i++){
1439         PSMTabBarCell *cell = [_cells objectAtIndex:i];
1440         float width = [cell width];
1441         aRect.size.width = width;
1442         
1443         if(NSPointInRect(point, aRect)){
1444             if(outFrame){
1445                 *outFrame = aRect;
1446             }
1447             return i;
1448         }
1449         aRect.origin.x += width;
1450     }
1451     return NSNotFound;
1454 - (PSMTabBarCell *)lastVisibleTab
1456     int i, cellCount = [_cells count];
1457     for(i = 0; i < cellCount; i++){
1458         if([[_cells objectAtIndex:i] isInOverflowMenu])
1459             return [_cells objectAtIndex:(i-1)];
1460     }
1461     return [_cells objectAtIndex:(cellCount - 1)];
1464 - (int)numberOfVisibleTabs
1466     int i, cellCount = [_cells count];
1467     for(i = 0; i < cellCount; i++){
1468         if([[_cells objectAtIndex:i] isInOverflowMenu])
1469             return i+1;
1470     }
1471     return cellCount;
1473     
1475 @end