root/tags/2C11/AICustomTabsView.m

Revision 2039, 30.7 kB (checked in by timothy, 4 years ago)

Latest Adium tab code. Merged our tooltip code in.

Line 
1 /*-------------------------------------------------------------------------------------------------------*\
2 | Adium, Copyright (C) 2001-2004, Adam Iser  (adamiser@mac.com | http://www.adiumx.com)                   |
3 \---------------------------------------------------------------------------------------------------------/
4 | This program is free software; you can redistribute it and/or modify it under the terms of the GNU
5 | General Public License as published by the Free Software Foundation; either version 2 of the License,
6 | or (at your option) any later version.
7 |
8 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
9 | the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
10 | Public License for more details.
11 |
12 | You should have received a copy of the GNU General Public License along with this program; if not,
13 | write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
14 \------------------------------------------------------------------------------------------------------ */
15
16 #import "AICustomTabsView.h"
17 #import "AICustomTabCell.h"
18 #import "AICustomTabDragging.h"
19 #import "NSImageAdditions.h"
20
21 #define TAB_DRAG_DISTANCE               3                                       //Distance required before a drag kicks in
22 #define CUSTOM_TABS_FPS                 30.0                            //Animation speed
23 #define CUSTOM_TABS_STEP        0.5                                     //Step size per frame
24 #define CUSTOM_TABS_SLOW_STEP   0.1                                     //Step size per frame (When shift is held)
25 #define CUSTOM_TABS_GAP                 1                                       //Gap between tabs
26 #define CUSTOM_TABS_INDENT              3                                       //Indent on left and right of tabbar
27
28 //Images shared by all instances of AICustomTabsView
29 static  NSImage                 *tabBackground = nil;
30 static  NSImage                 *tabDivider = nil;
31
32 @interface AICustomTabsView (PRIVATE)
33 - (id)initWithFrame:(NSRect)frameRect;
34
35 //Positioning
36 - (void)arrangeTabs;
37 - (void)smoothlyArrangeTabs;
38 - (void)smoothlyArrangeTabsWithGapOfWidth:(int)width atIndex:(int)index;
39 - (void)_arrangeCellTimer:(NSTimer *)inTimer;
40 - (BOOL)_arrangeCellsAbsolute:(BOOL)absolute;
41
42 //Dragging
43 - (NSArray *)acceptableDragTypes;
44 - (NSPoint)_dropPointForTabOfWidth:(int)dragTabWidth hoveredAtScreenPoint:(NSPoint)inPoint dropIndex:(int *)outIndex;
45 - (BOOL)allowsTabRearranging;
46
47 //Tab Data Access (Guarded)
48 - (void)removeTabCell:(AICustomTabCell *)inCell;
49 - (NSArray *)tabCellArray;
50
51 //Cursor tracking
52 - (void)startCursorTracking;
53 - (void)stopCursorTracking;
54 @end
55
56 @implementation AICustomTabsView
57
58 //Create a new custom tab view
59 + (id)customTabViewWithFrame:(NSRect)frameRect
60 {
61     return([[[self alloc] initWithFrame:frameRect] autorelease]);
62 }
63
64 //init
65 - (id)initWithFrame:(NSRect)frameRect
66 {
67     //Init
68     [super initWithFrame:frameRect];
69     arrangeCellTimer = nil;
70     removingLastTabHidesWindow = YES;
71         allowsTabRearranging = YES;
72     tabCellArray = nil;
73     selectedCustomTabCell = nil;
74         ignoreTabNumberChange = NO;
75
76     //register as a drag observer
77     [self registerForDraggedTypes:[self acceptableDragTypes]];
78     [self rebuildTabCells];
79
80     return(self);
81 }
82
83 //Dealloc
84 - (void)dealloc
85 {
86         [dragCell release]; dragCell = nil;
87     [arrangeCellTimer invalidate]; [arrangeCellTimer release]; arrangeCellTimer = nil;
88     [tabCellArray release]; tabCellArray = nil;
89     [super dealloc];
90 }
91
92 //Allow tab switching from the background
93 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
94 {
95     return(YES);
96 }
97
98 //Prevent dragging on metal windows
99 - (BOOL)mouseDownCanMoveWindow
100 {
101     return(NO);
102 }
103
104
105 //Configure ------------------------------------------------------------------------------------------------------------
106 #pragma mark Configure
107 //Set our delegate
108 - (void)setDelegate:(id)inDelegate
109 {
110     delegate = inDelegate;
111    
112     //Update our accepted drag types
113     [self unregisterDraggedTypes];
114     [self registerForDraggedTypes:[self acceptableDragTypes]];
115 }
116 - (id)delegate
117 {
118     return(delegate);
119 }
120
121 //Does removing the last tab of a window cause that window to hide?
122 - (void)setRemovingLastTabHidesWindow:(BOOL)inValue
123 {
124     removingLastTabHidesWindow = inValue;
125 }
126 - (BOOL)removingLastTabHidesWindow
127 {
128     return(removingLastTabHidesWindow);
129 }
130
131 //Can the user close inactive tabs?
132 - (void)setAllowsInactiveTabClosing:(BOOL)inValue
133 {
134     NSEnumerator                *enumerator;
135     AICustomTabCell             *tabCell;
136    
137     //Save the value
138     allowsInactiveTabClosing = inValue;
139    
140     //Pass it onto our tabs
141     enumerator = [tabCellArray objectEnumerator];
142     while((tabCell = [enumerator nextObject])){           
143                 [tabCell setAllowsInactiveTabClosing:allowsInactiveTabClosing];
144     }
145 }
146 - (BOOL)allowsInactiveTabClosing
147 {
148     return(allowsInactiveTabClosing);
149 }
150
151 //Is the user allowed to rearrange tabs within the window?
152 - (void)setAllowsTabRearranging:(BOOL)inValue
153 {
154         allowsTabRearranging = inValue;
155 }
156 - (BOOL)allowsTabRearranging
157 {
158         return(allowsTabRearranging);
159 }
160
161
162 //Additional Public Methods --------------------------------------------------------------------------------------------
163 #pragma mark Additional Public Methods
164 //Redisplay a tab
165 - (void)redisplayTabForTabViewItem:(NSTabViewItem *)inTabViewItem
166 {
167         [self setNeedsDisplayInRect:[[self tabCellForTabViewItem:inTabViewItem] frame]];
168 }
169
170 //Resize a tab
171 - (void)resizeTabForTabViewItem:(NSTabViewItem *)inTabViewItem
172 {
173         [self smoothlyArrangeTabs];
174 }
175
176 //Move a tab
177 - (void)moveTab:(NSTabViewItem *)tabViewItem toIndex:(int)index
178 {
179         [self moveTab:tabViewItem toIndex:index selectTab:NO animate:YES];
180 }
181
182 //Returns number of tab view items
183 - (int)numberOfTabViewItems
184 {
185         return([tabView numberOfTabViewItems]);
186 }
187
188
189 //Tabs -----------------------------------------------------------------------------------------------------------------
190 #pragma mark Tabs
191 //Tell our delegate to close a tab
192 - (void)closeTab:(AICustomTabCell *)tabCell
193 {
194     if([delegate respondsToSelector:@selector(customTabView:closeTabViewItem:)]){
195         [delegate customTabView:self closeTabViewItem:[tabCell tabViewItem]];
196     }
197 }
198
199 //Tell our delegate to close all tabs except for the one passed
200 - (void)closeAllTabsExceptFor:(AICustomTabCell *)targetCell
201 {
202     if([delegate respondsToSelector:@selector(customTabView:closeTabViewItem:)]){
203                 NSEnumerator    *enumerator = [tabCellArray objectEnumerator];
204                 AICustomTabCell *tabCell;
205                
206                 while(tabCell = [enumerator nextObject]){
207                         if(tabCell != targetCell){
208                                 [delegate customTabView:self closeTabViewItem:[tabCell tabViewItem]];
209                         }
210                 }
211     }
212 }
213
214 //Reposition a tab
215 - (void)moveTab:(NSTabViewItem *)tabViewItem toIndex:(int)index selectTab:(BOOL)shouldSelect animate:(BOOL)animate
216 {
217         //Ignore the move request if the tab is already at the proper index
218         if([tabView indexOfTabViewItem:tabViewItem] != index){
219                 AICustomTabCell         *tabCell = [self tabCellForTabViewItem:tabViewItem];
220
221                 //Ignore the 'shouldSelect' choice if this cell is already selected
222                 if(tabViewItem == [tabView selectedTabViewItem]) shouldSelect = YES;
223                
224                 //Move the tab cell
225                 int     currentIndex = [tabCellArray indexOfObject:tabCell];
226                 int     newIndex = index;
227                
228                 //Account for shifting
229                 if(currentIndex < newIndex) newIndex--;
230                
231                 //Move via a remove and add :(
232                 [tabCell retain];
233                 [tabCellArray removeObject:tabCell];
234                 [tabCellArray insertObject:tabCell atIndex:newIndex];
235                 [tabCell release];
236                
237                 //Move the tab
238                 ignoreTabNumberChange = YES;
239                 [tabViewItem retain];
240                 if([tabView indexOfTabViewItem:tabViewItem] < index) index--;
241                 [tabView removeTabViewItem:tabViewItem];
242                 [tabView insertTabViewItem:tabViewItem atIndex:index];
243                 [tabViewItem release];
244                 ignoreTabNumberChange = NO;
245                
246                 //Inform our delegate of the re-order
247                 if([delegate respondsToSelector:@selector(customTabViewDidChangeOrderOfTabViewItems:)]){
248                         [delegate customTabViewDidChangeOrderOfTabViewItems:self];
249                 }
250                
251                 //Smoothly animate into place
252                 if(animate){
253                         [self smoothlyArrangeTabs];
254                 }else{
255                         [self rebuildTabCells];
256                 }
257         }
258        
259         if(shouldSelect) [tabView selectTabViewItem:tabViewItem];
260 }
261
262 //Returns tab cell at the specified point
263 - (AICustomTabCell *)tabAtPoint:(NSPoint)clickLocation
264 {
265     NSEnumerator        *enumerator;
266     AICustomTabCell     *tabCell;
267        
268     enumerator = [tabCellArray objectEnumerator];
269     while((tabCell = [enumerator nextObject])){
270                 if(tabCell != dragCell && NSPointInRect(clickLocation, [tabCell frame])) break;
271     }
272        
273     return(tabCell);
274 }
275
276 //Returns the total width of our tabs
277 - (int)totalWidthOfTabs
278 {
279     int                         totalWidth = (CUSTOM_TABS_INDENT * 2);
280     NSEnumerator        *enumerator = [tabCellArray objectEnumerator];
281     AICustomTabCell     *tabCell;
282    
283     while((tabCell = [enumerator nextObject])){
284                 if(tabCell != dragCell) totalWidth += [tabCell size].width + CUSTOM_TABS_GAP;
285     }
286    
287     return(totalWidth);
288 }
289
290 //Change our selection to match the current selected tabViewItem
291 - (void)tabView:(NSTabView *)inTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
292 {
293     NSEnumerator        *enumerator;
294     AICustomTabCell     *tabCell;
295     NSTabViewItem       *selectedTab = [inTabView selectedTabViewItem];
296        
297     //Set old cell for a redisplay
298     [self setNeedsDisplayInRect:NSInsetRect([selectedCustomTabCell frame], -(CUSTOM_TABS_GAP * 2), 0)];
299        
300     //Record the new selected tab cell, and correctly set it as selected
301     enumerator = [tabCellArray objectEnumerator];
302     while((tabCell = [enumerator nextObject])){
303         if([tabCell tabViewItem] == selectedTab){
304             [tabCell setSelected:YES];
305                         selectedCustomTabCell = tabCell;
306         }else{
307             [tabCell setSelected:NO];
308         }
309     }
310        
311     //Redisplay new cell
312     [self setNeedsDisplayInRect:NSInsetRect([selectedCustomTabCell frame], -(CUSTOM_TABS_GAP * 2), 0)];
313        
314     //Inform our delegate of the selection change
315     if([delegate respondsToSelector:@selector(customTabView:didSelectTabViewItem:)]){
316         [delegate customTabView:self didSelectTabViewItem:tabViewItem];
317     }
318 }
319
320 //Rebuild our tab list to match the tabView
321 - (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)inTabView
322 {
323         if(!ignoreTabNumberChange){
324                 //Reset our tab list
325                 [self rebuildTabCells];       
326                
327                 //Inform our delegate of the tab count change
328                 if([delegate respondsToSelector:@selector(customTabViewDidChangeNumberOfTabViewItems:)]){
329                         [delegate customTabViewDidChangeNumberOfTabViewItems:self];
330                 }
331         }
332 }
333
334 //Intercept frame changes and correctly resize our tabs
335 - (void)setFrame:(NSRect)frameRect
336 {
337     [super setFrame:frameRect];
338     [self arrangeTabs];
339         [self resetCursorTracking];
340 }
341
342 //Rebuild the tab cells for this view
343 - (void)rebuildTabCells
344 {
345         //Clean up existing cells
346         [self stopCursorTracking];
347     [tabCellArray release]; tabCellArray = [[NSMutableArray alloc] init];
348         selectedCustomTabCell = nil;
349        
350         //Create a tab cell for each tabViewItem
351         int     loop;
352         for(loop = 0;loop < [tabView numberOfTabViewItems];loop++){
353                 NSTabViewItem           *tabViewItem = [tabView tabViewItemAtIndex:loop];
354                 AICustomTabCell         *tabCell;
355                
356                 //Create a new tab cell
357                 tabCell = [AICustomTabCell customTabForTabViewItem:tabViewItem customTabsView:self];
358                 [tabCell setSelected:(tabViewItem == [tabView selectedTabViewItem])];
359                 [tabCell setAllowsInactiveTabClosing:allowsInactiveTabClosing];
360                
361                 //Update our direct reference to the selected cell
362                 if(tabViewItem == [tabView selectedTabViewItem]){
363                         selectedCustomTabCell = tabCell;
364                 }
365                
366                 //Add the tab cell to our array
367                 [tabCellArray addObject:tabCell];
368         }
369        
370         [self arrangeTabs];
371         [self startCursorTracking];
372 }
373
374 - (AICustomTabCell *)tabCellForTabViewItem:(NSTabViewItem *)tabViewItem
375 {
376         NSEnumerator    *enumerator = [tabCellArray objectEnumerator];
377         AICustomTabCell *tabCell;
378        
379         while((tabCell = [enumerator nextObject]) && [tabCell tabViewItem] != tabViewItem);
380        
381         return(tabCell);
382 }
383
384
385 //Positioning ----------------------------------------------------------------------------------------------------------
386 #pragma mark Positioning
387 //More our tabs instantly into the correct positions
388 - (void)arrangeTabs
389 {
390         tabGapWidth = 0;
391         tabGapIndex = 0;
392         [self _arrangeCellsAbsolute:YES];
393 }
394
395 //Slowly move our tabs into the correct positions
396 - (void)smoothlyArrangeTabs
397 {
398         [self smoothlyArrangeTabsWithGapOfWidth:0 atIndex:0];
399 }
400
401 //Slowly move our tabs to make a gap
402 - (void)smoothlyArrangeTabsWithGapOfWidth:(int)width atIndex:(int)index
403 {
404         tabGapWidth = width;
405         tabGapIndex = index;
406        
407         if(!arrangeCellTimer){ //Ignore the request if animation is already occuring       
408                 arrangeCellTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0/CUSTOM_TABS_FPS)
409                                                                                                                          target:self
410                                                                                                                    selector:@selector(_arrangeCellTimer:)
411                                                                                                                    userInfo:nil
412                                                                                                                         repeats:YES] retain];
413                 [self _arrangeCellsAbsolute:NO];
414         }
415 }
416
417 //Animation timer.  Continue arranging cells until they are in the correct position
418 - (void)_arrangeCellTimer:(NSTimer *)inTimer
419 {   
420     if([self _arrangeCellsAbsolute:NO]){
421         [arrangeCellTimer invalidate]; [arrangeCellTimer release]; arrangeCellTimer = nil;
422     }
423 }
424
425 //Re-arrange our cells to their correct positions.  Returns YES is finished.  Pass NO for a partial movement
426 - (BOOL)_arrangeCellsAbsolute:(BOOL)absolute
427 {
428     NSEnumerator        *enumerator;
429     AICustomTabCell     *tabCell;
430     int                         xLocation;
431     BOOL                        finished = YES;
432     int                         tabExtraWidth;
433     int                         totalTabWidth;
434     int                         reducedWidth = 0;
435     int                         reduceThreshold = 1000000;
436
437     //Get the total tab width
438     totalTabWidth = [self totalWidthOfTabs] + tabGapWidth;
439
440     //If the tabs are too wide, we need to shrink the bigger ones down
441     tabExtraWidth = totalTabWidth - [self frame].size.width;
442     if(tabExtraWidth > 0){
443         NSArray                 *sortedTabArray;
444         int                             tabCount = 0;
445         int                             totalTabWidthForShrinking = 0;
446
447         //Make a copy of the tabArray sorted by width
448         sortedTabArray = [tabCellArray sortedArrayUsingSelector:@selector(compareWidth:)];
449
450         //Process each tab to determine how many should be squished, and the size they should squish to
451         enumerator = [sortedTabArray reverseObjectEnumerator];
452         tabCell = [enumerator nextObject];
453         do{
454                         if(tabCell != dragCell){
455                                 tabCount++;
456                                 totalTabWidthForShrinking += [tabCell size].width;
457                                 reducedWidth = (totalTabWidthForShrinking - tabExtraWidth) / tabCount;
458                         }
459
460         }while((tabCell = [enumerator nextObject]) && (reducedWidth <= [tabCell size].width));
461
462         //Remember the treshold at which tabs are squished
463         reduceThreshold = (tabCell ? [tabCell size].width : 0);
464     }
465
466     //Position the tabs
467     xLocation = CUSTOM_TABS_INDENT;
468     enumerator = [tabCellArray objectEnumerator];
469     int index = 0;
470
471     while((tabCell = [enumerator nextObject])){
472                 if(tabCell != dragCell){
473                         NSSize  size;
474                         NSPoint origin;
475                        
476                         //Make a gap to signify that the dragged cell can be dropped here
477                         if(index == tabGapIndex) xLocation += tabGapWidth;
478                        
479                         //Get the object's size
480                         size = [tabCell size];
481                        
482                         //If this tab is > next biggest, use the 'reduced' width calculated above
483                         if(size.width > reduceThreshold){
484                                 size.width = reducedWidth;
485                         }
486                        
487                         //Move the tab closer to its desired location
488                         origin = NSMakePoint(xLocation, 0 );
489                         if(!absolute){
490                                 if(origin.x > [tabCell frame].origin.x){
491                                         int distance = (origin.x - [tabCell frame].origin.x) * (( [[[NSApplication sharedApplication] currentEvent] modifierFlags] & NSShiftKeyMask ) ? CUSTOM_TABS_SLOW_STEP : CUSTOM_TABS_STEP);
492                                         if(distance < 1) distance = 1;
493                                        
494                                         origin.x = [tabCell frame].origin.x + distance;
495                                        
496                                         if(finished) finished = NO;
497                                 }else if(origin.x < [tabCell frame].origin.x){
498                                         int distance = ([tabCell frame].origin.x - origin.x) * (( [[[NSApplication sharedApplication] currentEvent] modifierFlags] & NSShiftKeyMask ) ? CUSTOM_TABS_SLOW_STEP : CUSTOM_TABS_STEP);
499                                         if(distance < 1) distance = 1;
500                                        
501                                         origin.x = [tabCell frame].origin.x - distance;
502                                         if(finished) finished = NO;
503                                 }
504                         }
505                         [tabCell setFrame:NSMakeRect((int)origin.x, (int)origin.y, (int)size.width, (int)size.height)];
506                        
507                         //Move to the next tab
508                         xLocation += size.width + CUSTOM_TABS_GAP; //overlap the tabs a bit
509                 }
510                 index++;
511         }
512    
513         //When we finish, update the cursor tracking
514         if(finished) [self resetCursorTracking];
515        
516     [self setNeedsDisplay:YES];
517     return(finished);
518 }
519
520
521 //Drawing --------------------------------------------------------------------------------------------------------------
522 //Draw
523 - (void)drawRect:(NSRect)rect
524 {
525     static  BOOL        haveLoadedImages = NO;
526     NSEnumerator        *enumerator;
527     AICustomTabCell     *tabCell, *nextTabCell;
528
529         [[NSColor windowBackgroundColor] set];
530         [NSBezierPath fillRect:rect];
531        
532     //Load our images (Images are shared between all AICustomTabsView instances)
533     if(!haveLoadedImages){
534                 tabDivider = [[NSImage imageNamed:@"aquaTabDivider"] retain];
535                 tabBackground = [[NSImage imageNamed:@"aquaTabBackground"] retain];
536         haveLoadedImages = YES;
537     }
538    
539     //Draw our background
540     [self drawBackgroundInRect:rect withFrame:[self frame] selectedTabRect:[selectedCustomTabCell frame]];
541        
542     //Draw our tabs
543     enumerator = [tabCellArray objectEnumerator];
544     tabCell = [enumerator nextObject];
545     while((nextTabCell = [enumerator nextObject]) || tabCell){
546         NSRect  cellFrame = [tabCell frame];
547                
548         if(NSIntersectsRect(cellFrame, rect)){
549                         if(tabCell != dragCell){
550                                 BOOL    ignoreSelection = ([[AICustomTabDragging sharedInstance] destinationTabView] == self ||
551                                                                                    [[AICustomTabDragging sharedInstance] sourceTabView] == self);
552                                
553                                 //Draw the tab cell
554                                 [tabCell drawWithFrame:cellFrame inView:self ignoreSelection:ignoreSelection];
555                                
556                                 //Draw the divider
557                                 //We don't draw the divider for the selected tab, or the tab to the right of the selected tab
558                                 //We also don't draw it for the index behind hovered
559                                 if((ignoreSelection ||
560                                         (tabCell != selectedCustomTabCell && (!nextTabCell || nextTabCell != selectedCustomTabCell)))
561                                    && [tabCellArray indexOfObject:tabCell] != tabGapIndex - 1){
562                                         [tabDivider compositeToPoint:NSMakePoint(cellFrame.origin.x + cellFrame.size.width, cellFrame.origin.y)
563                                                                            operation:NSCompositeSourceOver];
564                                 }
565                         }
566                 }
567                
568         tabCell = nextTabCell;
569         }
570 }
571
572 //Constrain a rect horizontally
573 NSRect AIConstrainRectWidth(NSRect rect, float left, float right)
574 {
575         if(rect.origin.x < left){
576                 rect.size.width -= left - rect.origin.x;
577                 rect.origin.x = left;
578         }
579         if(NSMaxX(rect) > right){
580                 rect.size.width -= NSMaxX(rect) - right;
581         }
582        
583         return(rect);
584 }
585
586 //Draw our background strip
587 - (void)drawBackgroundInRect:(NSRect)rect withFrame:(NSRect)viewFrame selectedTabRect:(NSRect)tabFrame
588 {
589         NSRect          drawRect;
590
591         if([[AICustomTabDragging sharedInstance] destinationTabView] == self ||
592            [[AICustomTabDragging sharedInstance] sourceTabView] == self){ //Draw dark gradient across entire view
593                 if(NSIntersectsRect(viewFrame, rect)){
594                         [tabBackground tileInRect:AIConstrainRectWidth(viewFrame, NSMinX(rect), NSMaxX(rect))];
595                 }
596                
597         }else{ //Draw dark gradient left and right of active tab
598                 drawRect = NSMakeRect(viewFrame.origin.x,
599                                                           viewFrame.origin.y,
600                                                           tabFrame.origin.x - viewFrame.origin.x,
601                                                           viewFrame.size.height);
602                 if(NSIntersectsRect(drawRect, rect)){
603                         [tabBackground tileInRect:AIConstrainRectWidth(drawRect, NSMinX(rect), NSMaxX(rect))];
604                 }
605                
606                 drawRect = NSMakeRect(tabFrame.origin.x + tabFrame.size.width,
607                                                           viewFrame.origin.y,
608                                                           (viewFrame.origin.x + viewFrame.size.width) - (tabFrame.origin.x + tabFrame.size.width),
609                                                           viewFrame.size.height);
610                 if(NSIntersectsRect(drawRect, rect)){
611                         [tabBackground tileInRect:AIConstrainRectWidth(drawRect, NSMinX(rect), NSMaxX(rect))];
612                 }
613         }
614 }
615
616
617 //Contextual menu ------------------------------------------------------------------------------------------------------
618 #pragma mark Contextual menu
619 //Return a contextual menu
620 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
621 {
622     NSPoint             clickLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
623     AICustomTabCell     *tabCell = [self tabAtPoint:clickLocation];
624        
625     //Pass this on to our delegate
626     if(tabCell && [delegate respondsToSelector:@selector(customTabView:menuForTabViewItem:)]){
627         return([delegate customTabView:self menuForTabViewItem:[tabCell tabViewItem]]);
628     }
629     return(nil);
630 }
631
632
633 //Tooltip ------------------------------------------------------------------------------------------------------
634 #pragma mark Tooltip
635 //Return a tooltip
636 - (NSString *) view:(NSView *) view stringForToolTip:(NSToolTipTag) tag point:(NSPoint) point userData:(void *) userData {
637         NSPoint         location = [self convertPoint:point fromView:nil];
638     AICustomTabCell     *tabCell = [self tabAtPoint:location];
639        
640         //Pass this on to our delegate
641     if(tabCell && [delegate respondsToSelector:@selector(customTabView:toolTipForTabViewItem:)]){
642         return([delegate customTabView:self toolTipForTabViewItem:[tabCell tabViewItem]]);
643     }
644     return(nil);
645 }
646
647 //Clicking & Dragging --------------------------------------------------------------------------------------------------
648 #pragma mark Clicking & Dragging
649 //Mouse Down
650 - (void)mouseDown:(NSEvent *)theEvent
651 {
652     AICustomTabCell     *tabCell;
653
654     //Remember the clicked location so we can track any dragging
655     lastClickLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
656        
657         //Give the tab cell a chance to handle tracking
658     if(tabCell = [self tabAtPoint:lastClickLocation]){
659         if(![tabCell willTrackMouse:theEvent inRect:[tabCell frame] ofView:self]){
660 //                      if(!( [[[NSApplication sharedApplication] currentEvent] modifierFlags] & NSCommandKeyMask )){ //Allow background dragging
661                 [tabView selectTabViewItem:[tabCell tabViewItem]];
662 //            }
663         }
664     }
665 }
666
667 //Mouse Dragged
668 - (void)mouseDragged:(NSEvent *)theEvent
669 {
670     NSPoint             clickLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
671
672         //Once we've dragged beyond a certain threshold, initiate a tab drag
673         if( (lastClickLocation.x - clickLocation.x) > TAB_DRAG_DISTANCE || (lastClickLocation.x - clickLocation.x) < -TAB_DRAG_DISTANCE ||
674                 (lastClickLocation.y - clickLocation.y) > TAB_DRAG_DISTANCE || (lastClickLocation.y - clickLocation.y) < -TAB_DRAG_DISTANCE ){
675                
676                 //Perform a tab drag
677                 if(lastClickLocation.x != -1 && lastClickLocation.y != -1){ //See note below about lastClickLocation
678                         
679                         dragCell = [self tabAtPoint:lastClickLocation];
680                         if(dragCell){
681                                 [self retain];
682                                 [dragCell retain];
683                                        
684                                 [self stopCursorTracking];
685                                 [[AICustomTabDragging sharedInstance] dragTabCell:dragCell
686                                                                                            fromCustomTabsView:self
687                                                                                                                 withEvent:theEvent
688                                                                                                                 selectTab:(!( [[[NSApplication sharedApplication] currentEvent] modifierFlags] & NSCommandKeyMask ))];
689                                
690                                 [dragCell release]; dragCell = nil;
691                                 [self autorelease];
692                         }
693                 }
694                
695                 //Sneaky Bug Fix --
696                 //When dragging quickly, mouseDragged may be called multiple times.  We only want to drag once, no matter what.
697                 //This is achieved by only allowing a drag if lastClickLocation is valid.  lastClickLocation will only be valid
698                 //for the first mouseDragged event, allowing others to be easily ignored.
699                 lastClickLocation = NSMakePoint(-1,-1);
700         }
701 }
702
703 //Return the drag types we accept
704 - (NSArray *)acceptableDragTypes
705 {
706     NSArray *types = nil;
707    
708         //We always accept tab drags, but ask our delegate if it accepts any additional types
709     if([delegate respondsToSelector:@selector(customTabViewAcceptableDragTypes:)]){
710         types = [delegate customTabViewAcceptableDragTypes:self];
711     }
712    
713     return(types ? [types arrayByAddingObject:TAB_CELL_IDENTIFIER] : [NSArray arrayWithObject:TAB_CELL_IDENTIFIER]);
714 }
715
716 //Return YES to accept drags
717 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
718 {
719     return(YES);
720 }
721
722 //Perform a drag operation (switching around the tabs)
723 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
724 {
725     NSPoint                     location = [self convertPoint:[sender draggingLocation] fromView:nil];
726         NSPasteboard    *pboard = [sender draggingPasteboard];
727     NSString        *type = [pboard availableTypeFromArray:[NSArray arrayWithObject:TAB_CELL_IDENTIFIER]];
728     BOOL            success = NO;
729         int                             dropIndex;
730         AICustomTabCell *tabCell;
731        
732         //Perform the drag
733     if(type && [type isEqualToString:TAB_CELL_IDENTIFIER]){
734                 [self _dropPointForTabOfWidth:[[AICustomTabDragging sharedInstance] sizeOfDraggedCell].width
735                                  hoveredAtScreenPoint:location
736                                                         dropIndex:&dropIndex];
737                 [[AICustomTabDragging sharedInstance] acceptDragIntoTabView:self atIndex:dropIndex];
738                 [self setNeedsDisplay:YES];
739                 [self displayIfNeeded];
740                
741                 success = YES;
742                
743     }else{
744         if(tabCell = [self tabAtPoint:[sender draggingLocation]]){           
745             if([delegate respondsToSelector:@selector(customTabView:didAcceptDragPasteboard:onTabViewItem:)]){
746                 success = [delegate customTabView: