//
//  PSMTabBarControl.m
//  PSMTabBarControl
//
//  Created by John Pannell on 10/13/05.
//  Copyright 2005 Positive Spin Media. All rights reserved.
//

#import "PSMTabBarControl.h"
#import "PSMTabBarCell.h"
#import "PSMOverflowPopUpButton.h"
#import "PSMRolloverButton.h"
#import "PSMTabStyle.h"
#import "PSMMetalTabStyle.h"
#import "PSMAquaTabStyle.h"
#import "PSMUnifiedTabStyle.h"
#import "PSMCardTabStyle.h"
#import "PSMAdiumTabStyle.h"
#import "PSMSequelProTabStyle.h"
#import "PSMTabDragAssistant.h"
#import "PSMTabBarController.h"

#include <Carbon/Carbon.h> /* for GetKeys() and KeyMap */
#include <bitstring.h>

@interface PSMTabBarControl (Private)

    // constructor/destructor
- (void)initAddedProperties;

    // accessors
- (NSEvent *)lastMouseDownEvent;
- (void)setLastMouseDownEvent:(NSEvent *)event;

    // contents
- (void)addTabViewItem:(NSTabViewItem *)item;
- (void)removeTabForCell:(PSMTabBarCell *)cell;

    // draw
- (void)_setupTrackingRectsForCell:(PSMTabBarCell *)cell;
- (void)_positionOverflowMenu;
- (void)_checkWindowFrame;

    // actions
- (void)closeTabClick:(id)sender;
- (void)tabNothing:(id)sender;

	// notification handlers
- (void)frameDidChange:(NSNotification *)notification;
- (void)windowDidMove:(NSNotification *)aNotification;
- (void)windowDidUpdate:(NSNotification *)notification;
- (void)windowStatusDidChange:(NSNotification *)notification;

    // NSTabView delegate
- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem;
- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem;
- (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem;
- (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView;

    // archiving
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

    // convenience
- (void)_bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item;
- (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame;

- (void)fireSpring:(NSTimer *)timer;
- (void)animateShowHide:(NSTimer *)timer;
- (void)_animateCells:(NSTimer *)timer;
@end

@implementation PSMTabBarControl

#pragma mark -
#pragma mark Characteristics
+ (NSBundle *)bundle;
{
    static NSBundle *bundle = nil;
    if (!bundle) bundle = [NSBundle bundleForClass:[PSMTabBarControl class]];
    return bundle;
}

/*!
    @method     availableCellWidth
    @abstract   The number of pixels available for cells
    @discussion Calculates the number of pixels available for cells based on margins and the window resize badge.
    @returns    Returns the amount of space for cells.
*/

- (CGFloat)availableCellWidth
{
    return [self frame].size.width - [style leftMarginForTabBarControl] - [style rightMarginForTabBarControl] - _resizeAreaCompensation;
}

/*!
    @method     genericCellRect
    @abstract   The basic rect for a tab cell.
    @discussion Creates a generic frame for a tab cell based on the current control state.
    @returns    Returns a basic rect for a tab cell.
*/

- (NSRect)genericCellRect
{
    NSRect aRect=[self frame];
    aRect.origin.x = [style leftMarginForTabBarControl];
    aRect.origin.y = 0.0f;
    aRect.size.width = [self availableCellWidth];
    aRect.size.height = [style tabCellHeight];
    return aRect;
}

#pragma mark -
#pragma mark Constructor/destructor

- (void)initAddedProperties
{
    _cells = [[NSMutableArray alloc] initWithCapacity:10];
	_controller = [[PSMTabBarController alloc] initWithTabBarControl:self];
    _animationTimer = nil;
	
    // default config
	_currentStep = kPSMIsNotBeingResized;
	_orientation = PSMTabBarHorizontalOrientation;
    _canCloseOnlyTab = NO;
	_disableTabClose = NO;
    _showAddTabButton = NO;
    _hideForSingleTab = NO;
    _sizeCellsToFit = NO;
    _isHidden = NO;
    _awakenedFromNib = NO;
	_automaticallyAnimates = NO;
    _useOverflowMenu = YES;
	_allowsBackgroundTabClosing = YES;
	_allowsResizing = NO;
	_selectsTabsOnMouseDown = NO;
    _alwaysShowActiveTab = NO;
	_allowsScrubbing = NO;
	_useSafariStyleDragging = NO;
    _cellMinWidth = 100;
    _cellMaxWidth = 280;
    _cellOptimumWidth = 130;
	_tearOffStyle = PSMTabBarTearOffAlphaWindow;
	style = [[PSMSequelProTabStyle alloc] init];
    
    // the overflow button/menu
    NSRect overflowButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 0, [style rightMarginForTabBarControl] - 1, [self frame].size.height);
    _overflowPopUpButton = [[PSMOverflowPopUpButton alloc] initWithFrame:overflowButtonRect pullsDown:YES];
    [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin];
    [_overflowPopUpButton setHidden:YES];
    [self addSubview:_overflowPopUpButton];
    [self _positionOverflowMenu];
    
    // new tab button
    NSRect addTabButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 3.0f, 16.0f, 16.0f);
    _addTabButton = [[PSMRolloverButton alloc] initWithFrame:addTabButtonRect];
    if (_addTabButton) {
        NSImage *newButtonImage = [style addTabButtonImage];
        if (newButtonImage) {
            [_addTabButton setUsualImage:newButtonImage];
        }
        newButtonImage = [style addTabButtonPressedImage];
        if (newButtonImage) {
            [_addTabButton setAlternateImage:newButtonImage];
        }
        newButtonImage = [style addTabButtonRolloverImage];
        if (newButtonImage) {
            [_addTabButton setRolloverImage:newButtonImage];
        }
        [_addTabButton setTitle:@""];
        [_addTabButton setImagePosition:NSImageOnly];
        [_addTabButton setButtonType:NSMomentaryChangeButton];
        [_addTabButton setBordered:NO];
        [_addTabButton setBezelStyle:NSShadowlessSquareBezelStyle];
        [self addSubview:_addTabButton];
        
        if (_showAddTabButton) {
            [_addTabButton setHidden:NO];
        } else {
            [_addTabButton setHidden:YES];
        }
        [_addTabButton setNeedsDisplay:YES];
    }
}
    
- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization
        [self initAddedProperties];
        [self registerForDraggedTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil]];
		
		// resize
		[self setPostsFrameChangedNotifications:YES];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
    }
    [self setTarget:self];
    return self;
}

- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	//stop any animations that may be running
	[_animationTimer invalidate];
	[_animationTimer release]; _animationTimer = nil;
	
	[_showHideAnimationTimer invalidate];
	[_showHideAnimationTimer release]; _showHideAnimationTimer = nil;

	//Also unwind the spring, if it's wound.
	[_springTimer invalidate];
	[_springTimer release]; _springTimer = nil;

	//unbind all the items to prevent crashing
	//not sure if this is necessary or not
    NSArray *cells = [NSArray arrayWithArray:_cells];  // create a copy as we will change the original array while being enumerated
	NSEnumerator *enumerator = [cells objectEnumerator];
	PSMTabBarCell *nextCell;
	while ( (nextCell = [enumerator nextObject]) ) {
		[self removeTabForCell:nextCell];
	}
	
    [_overflowPopUpButton release];
    [_cells release];
	[_controller release];
    [tabView release];
    [_addTabButton release];
    [partnerView release];
    [_lastMouseDownEvent release];
    [style release];
    
    [self unregisterDraggedTypes];
	
    [super dealloc];
}

- (void)awakeFromNib
{
    // build cells from existing tab view items
    NSArray *existingItems = [tabView tabViewItems];
    NSEnumerator *e = [existingItems objectEnumerator];
    NSTabViewItem *item;
    while ( (item = [e nextObject]) ) {
        if (![[self representedTabViewItems] containsObject:item]) {
            [self addTabViewItem:item];
		}
    }
}

- (void)viewWillMoveToWindow:(NSWindow *)aWindow {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
	
	[center removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil];
	[center removeObserver:self name:NSWindowDidResignKeyNotification object:nil];
	[center removeObserver:self name:NSWindowDidUpdateNotification object:nil];
	[center removeObserver:self name:NSWindowDidMoveNotification object:nil];
	
	if (_showHideAnimationTimer) {
		[_showHideAnimationTimer invalidate];
		[_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
	}
	
    if (aWindow) {
		[center addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidBecomeMainNotification object:aWindow];
		[center addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidResignMainNotification object:aWindow];
		[center addObserver:self selector:@selector(windowDidUpdate:) name:NSWindowDidUpdateNotification object:aWindow];
		[center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:aWindow];
    }
}

- (void)windowStatusDidChange:(NSNotification *)notification
{
	[self setNeedsDisplay:YES];
}

#pragma mark -
#pragma mark Accessors

- (NSMutableArray *)cells
{
    return _cells;
}

- (NSEvent *)lastMouseDownEvent
{
    return _lastMouseDownEvent;
}

- (void)setLastMouseDownEvent:(NSEvent *)event
{
    [event retain];
    [_lastMouseDownEvent release];
    _lastMouseDownEvent = event;
}

- (id)delegate
{
    return delegate;
}

- (void)setDelegate:(id)object
{
    delegate = object;
	
	NSMutableArray *types = [NSMutableArray arrayWithObjects:@"PSMTabBarControlItemPBType", NSStringPboardType,nil];
	
	//Update the allowed drag types
	if ([self delegate] && [[self delegate] respondsToSelector:@selector(allowedDraggedTypesForTabView:)]) {
		[types addObjectsFromArray:[[self delegate] allowedDraggedTypesForTabView:tabView]];
	}
	[self unregisterDraggedTypes];
	[self registerForDraggedTypes:types];
}

- (NSTabView *)tabView
{
    return tabView;
}

- (void)setTabView:(NSTabView *)view
{
    [view retain];
    [tabView release];
    tabView = view;
}

- (id<PSMTabStyle>)style
{
    return style;
}

- (NSString *)styleName
{
    return [style name];
}

- (void)setStyle:(id <PSMTabStyle>)newStyle
{
    if (style != newStyle) {
        [style autorelease];
        style = [newStyle retain];
        
        // restyle add tab button
        if (_addTabButton) {
            NSImage *newButtonImage = [style addTabButtonImage];
            if (newButtonImage) {
                [_addTabButton setUsualImage:newButtonImage];
            }
            
            newButtonImage = [style addTabButtonPressedImage];
            if (newButtonImage) {
                [_addTabButton setAlternateImage:newButtonImage];
            }
            
            newButtonImage = [style addTabButtonRolloverImage];
            if (newButtonImage) {
                [_addTabButton setRolloverImage:newButtonImage];
            }
        }
        
        [self update];
    }
}

- (void)setStyleNamed:(NSString *)name
{
    id <PSMTabStyle> newStyle;
	
	if ([name isEqualToString:@"Aqua"]) {
		//newStyle = [[PSMAquaTabStyle alloc] init];
		
	} else if ([name isEqualToString:@"Unified"]) {
		//newStyle = [[PSMUnifiedTabStyle alloc] init];
		
	} else if ([name isEqualToString:@"Adium"]) {
		//newStyle = [[PSMAdiumTabStyle alloc] init];
	
	} else if ([name isEqualToString:@"Card"]) {
		//newStyle = [[PSMCardTabStyle alloc] init]; 
	
	} else if ([name isEqualToString:@"Metal"]) {
		//newStyle = [[PSMMetalTabStyle alloc] init];

	} else if ([name isEqualToString:@"SequelPro"]) {
		newStyle = [[PSMSequelProTabStyle alloc] init];

	} else {
		newStyle = [[PSMSequelProTabStyle alloc] init];
	}

    [self setStyle:newStyle];
    [newStyle release];
}

- (PSMTabBarOrientation)orientation
{
	return _orientation;
}

- (void)setOrientation:(PSMTabBarOrientation)value
{
	PSMTabBarOrientation lastOrientation = _orientation;
	_orientation = value;

	if (_tabBarWidth < 10) {
		_tabBarWidth = 120;
	}
	
	if (lastOrientation != _orientation) {
		[[self style] setOrientation:_orientation];

        [self _positionOverflowMenu]; //move the overflow popup button to the right place
		[self update:NO];
	}
}

- (BOOL)canCloseOnlyTab
{
    return _canCloseOnlyTab;
}

- (void)setCanCloseOnlyTab:(BOOL)value
{
    _canCloseOnlyTab = value;
    if ([_cells count] == 1) {
        [self update];
    }
}

- (BOOL)disableTabClose
{
	return _disableTabClose;
}

- (void)setDisableTabClose:(BOOL)value
{
	_disableTabClose = value;
	[self update];
}

- (BOOL)hideForSingleTab
{
    return _hideForSingleTab;
}

- (void)setHideForSingleTab:(BOOL)value
{
    _hideForSingleTab = value;
    if ([_cells count] == 1) {
        [self update];
    }
}

- (BOOL)showAddTabButton
{
    return _showAddTabButton;
}

- (void)setShowAddTabButton:(BOOL)value
{
    _showAddTabButton = value;
	if (!NSIsEmptyRect([_controller addButtonRect]))
		[_addTabButton setFrame:[_controller addButtonRect]];

    [_addTabButton setHidden:!_showAddTabButton];
	[_addTabButton setNeedsDisplay:YES];

	[self update];
}

- (id)createNewTabTarget
{	
	return _createNewTabTarget;
}

- (void)setCreateNewTabTarget:(id)object
{
	_createNewTabTarget = object;
	[[self addTabButton] setTarget:object];
}

- (SEL)createNewTabAction
{
	return _createNewTabAction;	
}

- (void)setCreateNewTabAction:(SEL)selector
{
	_createNewTabAction = selector;
	[[self addTabButton] setAction:selector];
}

- (id)doubleClickTarget
{	
	return _doubleClickTarget;
}

- (void)setDoubleClickTarget:(id)object
{
	_doubleClickTarget = object;
}

- (SEL)doubleClickAction
{
	return _doubleClickAction;	
}

- (void)setDoubleClickAction:(SEL)selector
{
	_doubleClickAction = selector;
}

- (NSInteger)cellMinWidth
{
    return _cellMinWidth;
}

- (void)setCellMinWidth:(NSInteger)value
{
    _cellMinWidth = value;
    [self update];
}

- (NSInteger)cellMaxWidth
{
    return _cellMaxWidth;
}

- (void)setCellMaxWidth:(NSInteger)value
{
    _cellMaxWidth = value;
    [self update];
}

- (NSInteger)cellOptimumWidth
{
    return _cellOptimumWidth;
}

- (void)setCellOptimumWidth:(NSInteger)value
{
    _cellOptimumWidth = value;
    [self update];
}

- (BOOL)sizeCellsToFit
{
    return _sizeCellsToFit;
}

- (void)setSizeCellsToFit:(BOOL)value
{
    _sizeCellsToFit = value;
    [self update];
}

- (BOOL)useOverflowMenu
{
    return _useOverflowMenu;
}

- (void)setUseOverflowMenu:(BOOL)value
{
    _useOverflowMenu = value;
    [self update];
}

- (PSMRolloverButton *)addTabButton
{
    return _addTabButton;
}

- (PSMOverflowPopUpButton *)overflowPopUpButton
{
    return _overflowPopUpButton;
}

- (BOOL)allowsBackgroundTabClosing
{
	return _allowsBackgroundTabClosing;
}

- (void)setAllowsBackgroundTabClosing:(BOOL)value
{
	_allowsBackgroundTabClosing = value;
}

- (BOOL)allowsResizing
{
	return _allowsResizing;
}

- (void)setAllowsResizing:(BOOL)value
{
	_allowsResizing = value;
}

- (BOOL)selectsTabsOnMouseDown
{
	return _selectsTabsOnMouseDown;
}

- (void)setSelectsTabsOnMouseDown:(BOOL)value
{
	_selectsTabsOnMouseDown = value;
}

- (BOOL)createsTabOnDoubleClick;
{
	return _createsTabOnDoubleClick;
}

- (void)setCreatesTabOnDoubleClick:(BOOL)value
{
	_createsTabOnDoubleClick = value;
}

- (BOOL)automaticallyAnimates
{
	return _automaticallyAnimates;
}

- (void)setAutomaticallyAnimates:(BOOL)value
{
	_automaticallyAnimates = value;
}

- (BOOL)alwaysShowActiveTab
{
	return _alwaysShowActiveTab;
}

- (void)setAlwaysShowActiveTab:(BOOL)value
{
	_alwaysShowActiveTab = value;
}

- (BOOL)allowsScrubbing
{
	return _allowsScrubbing;
}

- (void)setAllowsScrubbing:(BOOL)value
{
	_allowsScrubbing = value;
}

- (BOOL)usesSafariStyleDragging
{
	return _useSafariStyleDragging;
}

- (void)setUsesSafariStyleDragging:(BOOL)value
{
	_useSafariStyleDragging = value;
}

- (PSMTabBarTearOffStyle)tearOffStyle
{
	return _tearOffStyle;
}

- (void)setTearOffStyle:(PSMTabBarTearOffStyle)tearOffStyle
{
	_tearOffStyle = tearOffStyle;
}

#pragma mark -
#pragma mark Functionality

- (void)addTabViewItem:(NSTabViewItem *)item
{
    // create cell
    PSMTabBarCell *cell = [[PSMTabBarCell alloc] initWithControlView:self];
	NSRect cellRect, lastCellFrame = [[_cells lastObject] frame];
	
	if ([self orientation] == PSMTabBarHorizontalOrientation) {
		cellRect = [self genericCellRect];
		cellRect.size.width = 30;
		cellRect.origin.x = lastCellFrame.origin.x + lastCellFrame.size.width;
	} else {
		cellRect = /*lastCellFrame*/[self genericCellRect];
		cellRect.size.width = lastCellFrame.size.width;
		cellRect.size.height = 0;
		cellRect.origin.y = lastCellFrame.origin.y + lastCellFrame.size.height;
	}
	
    [cell setRepresentedObject:item];
	[cell setFrame:cellRect];
    
    // bind it up
    [self bindPropertiesForCell:cell andTabViewItem:item];
	
    // add to collection
    [_cells addObject:cell];
    [cell release];
    if ((NSInteger)[_cells count] == [tabView numberOfTabViewItems]) {
        [self update]; // don't update unless all are accounted for!
	}
}

- (void)removeTabForCell:(PSMTabBarCell *)cell
{
	NSTabViewItem *item = [cell representedObject];
	
    // unbind
    [[cell indicator] unbind:@"animate"];
    [[cell indicator] unbind:@"hidden"];
    [cell unbind:@"hasIcon"];
    [cell unbind:@"hasLargeImage"];
    [cell unbind:@"title"];
    [cell unbind:@"count"];
	[cell unbind:@"countColor"];
    [cell unbind:@"isEdited"];

	if ([item identifier] != nil) {
		if ([[item identifier] respondsToSelector:@selector(isProcessing)]) {
			[[item identifier] removeObserver:cell forKeyPath:@"isProcessing"];
		}
	}
	
	if ([item identifier] != nil) {
		if ([[item identifier] respondsToSelector:@selector(icon)]) {
			[[item identifier] removeObserver:cell forKeyPath:@"icon"];
		}
	}
	
	if ([item identifier] != nil) {
		if ([[item identifier] respondsToSelector:@selector(count)]) {
			[[item identifier] removeObserver:cell forKeyPath:@"objectCount"];
		}
	}
	
	if ([item identifier] != nil) {
		if ([[item identifier] respondsToSelector:@selector(countColor)]) {
			[[item identifier] removeObserver:cell forKeyPath:@"countColor"];
		}
	}

	if ([item identifier] != nil) {
		if ([[item identifier] respondsToSelector:@selector(largeImage)]) {
			[[item identifier] removeObserver:cell forKeyPath:@"largeImage"];
		}
	}
	
	if ([item identifier] != nil) {
		if ([[item identifier] respondsToSelector:@selector(isEdited)]) {
			[[item identifier] removeObserver:cell forKeyPath:@"isEdited"];
		}
	}
	
    // stop watching identifier
    [item removeObserver:self forKeyPath:@"identifier"];
    
    // remove indicator
    if ([[self subviews] containsObject:[cell indicator]]) {
        [[cell indicator] removeFromSuperview];
    }
    // remove tracking
    [[NSNotificationCenter defaultCenter] removeObserver:cell];
	
    if ([cell closeButtonTrackingTag] != 0) {
        [self removeTrackingRect:[cell closeButtonTrackingTag]];
		[cell setCloseButtonTrackingTag:0];
    }
    if ([cell cellTrackingTag] != 0) {
        [self removeTrackingRect:[cell cellTrackingTag]];
		[cell setCellTrackingTag:0];
    }

    // pull from collection
    [_cells removeObject:cell];

    [self update];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // did the tab's identifier change?
    if ([keyPath isEqualToString:@"identifier"]) {
        NSEnumerator *e = [_cells objectEnumerator];
        PSMTabBarCell *cell;
        while ( (cell = [e nextObject]) ) {
            if ([cell representedObject] == object) {
                [self _bindPropertiesForCell:cell andTabViewItem:object];
			}
        }
    }
}

#pragma mark -
#pragma mark Hide/Show

- (void)hideTabBar:(BOOL)hide animate:(BOOL)animate
{
    if (!_awakenedFromNib || (_isHidden && hide) || (!_isHidden && !hide) || (_currentStep != kPSMIsNotBeingResized)) {
        return;
	}
	
    [[self subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];

    _isHidden = hide;
    _currentStep = 0;
    if (!animate) {
        _currentStep = (NSInteger)kPSMHideAnimationSteps;
	}

	if (hide) {
		[_overflowPopUpButton removeFromSuperview];
		[_addTabButton removeFromSuperview];
	} else if (!animate) {
		[self addSubview:_overflowPopUpButton];
		[self addSubview:_addTabButton];
	}

    CGFloat partnerOriginalSize, partnerOriginalOrigin, myOriginalSize, myOriginalOrigin, partnerTargetSize, partnerTargetOrigin, myTargetSize, myTargetOrigin;
    
    // target values for partner
    if ([self orientation] == PSMTabBarHorizontalOrientation) {
		// current (original) values
		myOriginalSize = [self frame].size.height;
		myOriginalOrigin = [self frame].origin.y;
		if (partnerView) {
			partnerOriginalSize = [partnerView frame].size.height;
			partnerOriginalOrigin = [partnerView frame].origin.y;
		} else {
			partnerOriginalSize = [[self window] frame].size.height;
			partnerOriginalOrigin = [[self window] frame].origin.y;
		}
		
		if (partnerView) {
			// above or below me?
			if ((myOriginalOrigin - 22) > partnerOriginalOrigin) {
				// partner is below me
				if (_isHidden) {
					// I'm shrinking
					myTargetOrigin = myOriginalOrigin + 21;
					myTargetSize = myOriginalSize - 21;
					partnerTargetOrigin = partnerOriginalOrigin;
					partnerTargetSize = partnerOriginalSize + 21;
				} else {
					// I'm growing
					myTargetOrigin = myOriginalOrigin - 21;
					myTargetSize = myOriginalSize + 21;
					partnerTargetOrigin = partnerOriginalOrigin;
					partnerTargetSize = partnerOriginalSize - 21;
				}
			} else {
				// partner is above me
				if (_isHidden) {
					// I'm shrinking
					myTargetOrigin = myOriginalOrigin;
					myTargetSize = myOriginalSize - 21;
					partnerTargetOrigin = partnerOriginalOrigin - 21;
					partnerTargetSize = partnerOriginalSize + 21;
				} else {
					// I'm growing
					myTargetOrigin = myOriginalOrigin;
					myTargetSize = myOriginalSize + 21;
					partnerTargetOrigin = partnerOriginalOrigin + 21;
					partnerTargetSize = partnerOriginalSize - 21;
				}
			}
		} else {
			// for window movement
			if (_isHidden) {
				// I'm shrinking
				myTargetOrigin = myOriginalOrigin;
				myTargetSize = myOriginalSize - 21;
				partnerTargetOrigin = partnerOriginalOrigin + 21;
				partnerTargetSize = partnerOriginalSize - 21;
			} else {
				// I'm growing
				myTargetOrigin = myOriginalOrigin;
				myTargetSize = myOriginalSize + 21;
				partnerTargetOrigin = partnerOriginalOrigin - 21;
				partnerTargetSize = partnerOriginalSize + 21;
			}
		}
	} else /* vertical */ {
		// current (original) values
		myOriginalSize = [self frame].size.width;
		myOriginalOrigin = [self frame].origin.x;
		if (partnerView) {
			partnerOriginalSize = [partnerView frame].size.width;
			partnerOriginalOrigin = [partnerView frame].origin.x;
		} else {
			partnerOriginalSize = [[self window] frame].size.width;
			partnerOriginalOrigin = [[self window] frame].origin.x;
		}
		
		if (partnerView) {
			//to the left or right?
			if (myOriginalOrigin < partnerOriginalOrigin + partnerOriginalSize) {
				// partner is to the left
				if (_isHidden) {
					// I'm shrinking
					myTargetOrigin = myOriginalOrigin;
					myTargetSize = 1;
					partnerTargetOrigin = partnerOriginalOrigin - myOriginalSize + 1;
					partnerTargetSize = partnerOriginalSize + myOriginalSize - 1;
					_tabBarWidth = myOriginalSize;
				} else {
					// I'm growing
					myTargetOrigin = myOriginalOrigin;
					myTargetSize = myOriginalSize + _tabBarWidth;
					partnerTargetOrigin = partnerOriginalOrigin + _tabBarWidth;
					partnerTargetSize = partnerOriginalSize - _tabBarWidth;
				}
			} else {
				// partner is to the right
				if (_isHidden) {
					// I'm shrinking
					myTargetOrigin = myOriginalOrigin + myOriginalSize;
					myTargetSize = 1;
					partnerTargetOrigin = partnerOriginalOrigin;
					partnerTargetSize = partnerOriginalSize + myOriginalSize;
					_tabBarWidth = myOriginalSize;
				} else {
					// I'm growing
					myTargetOrigin = myOriginalOrigin - _tabBarWidth;
					myTargetSize = myOriginalSize + _tabBarWidth;
					partnerTargetOrigin = partnerOriginalOrigin;
					partnerTargetSize = partnerOriginalSize - _tabBarWidth;
				}
			}
		} else {
			// for window movement
			if (_isHidden) {
				// I'm shrinking
				myTargetOrigin = myOriginalOrigin;
				myTargetSize = 1;
				partnerTargetOrigin = partnerOriginalOrigin + myOriginalSize - 1;
				partnerTargetSize = partnerOriginalSize - myOriginalSize + 1;
				_tabBarWidth = myOriginalSize;
			} else {
				// I'm growing
				myTargetOrigin = myOriginalOrigin;
				myTargetSize = _tabBarWidth;
				partnerTargetOrigin = partnerOriginalOrigin - _tabBarWidth + 1;
				partnerTargetSize = partnerOriginalSize + _tabBarWidth - 1;
			}
		}
		
		if (!_isHidden && [[self delegate] respondsToSelector:@selector(desiredWidthForVerticalTabBar:)])
			myTargetSize = [[self delegate] desiredWidthForVerticalTabBar:self];
	}

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:myOriginalOrigin], @"myOriginalOrigin", [NSNumber numberWithDouble:partnerOriginalOrigin], @"partnerOriginalOrigin", [NSNumber numberWithDouble:myOriginalSize], @"myOriginalSize", [NSNumber numberWithDouble:partnerOriginalSize], @"partnerOriginalSize", [NSNumber numberWithDouble:myTargetOrigin], @"myTargetOrigin", [NSNumber numberWithDouble:partnerTargetOrigin], @"partnerTargetOrigin", [NSNumber numberWithDouble:myTargetSize], @"myTargetSize", [NSNumber numberWithDouble:partnerTargetSize], @"partnerTargetSize", nil];
	if (_showHideAnimationTimer) {
		[_showHideAnimationTimer invalidate];
		[_showHideAnimationTimer release];
	}
    _showHideAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0 / 30.0) target:self selector:@selector(animateShowHide:) userInfo:userInfo repeats:YES] retain];
}

- (void)animateShowHide:(NSTimer *)timer
{
    // moves the frame of the tab bar and window (or partner view) linearly to hide or show the tab bar
    NSRect myFrame = [self frame];
	NSDictionary *userInfo = [timer userInfo];
    CGFloat myCurrentOrigin = ([[userInfo objectForKey:@"myOriginalOrigin"] floatValue] + (([[userInfo objectForKey:@"myTargetOrigin"] floatValue] - [[userInfo objectForKey:@"myOriginalOrigin"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
    CGFloat myCurrentSize = ([[userInfo objectForKey:@"myOriginalSize"] floatValue] + (([[userInfo objectForKey:@"myTargetSize"] floatValue] - [[userInfo objectForKey:@"myOriginalSize"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
    CGFloat partnerCurrentOrigin = ([[userInfo objectForKey:@"partnerOriginalOrigin"] floatValue] + (([[userInfo objectForKey:@"partnerTargetOrigin"] floatValue] - [[userInfo objectForKey:@"partnerOriginalOrigin"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
    CGFloat partnerCurrentSize = ([[userInfo objectForKey:@"partnerOriginalSize"] floatValue] + (([[userInfo objectForKey:@"partnerTargetSize"] floatValue] - [[userInfo objectForKey:@"partnerOriginalSize"] floatValue]) * (_currentStep/kPSMHideAnimationSteps)));
    
	NSRect myNewFrame;
	if ([self orientation] == PSMTabBarHorizontalOrientation) {
		myNewFrame = NSMakeRect(myFrame.origin.x, myCurrentOrigin, myFrame.size.width, myCurrentSize);
	} else {
		myNewFrame = NSMakeRect(myCurrentOrigin, myFrame.origin.y, myCurrentSize, myFrame.size.height);
	}
    
    if (partnerView) {
        // resize self and view
		NSRect resizeRect;
        if ([self orientation] == PSMTabBarHorizontalOrientation) {
			resizeRect = NSMakeRect([partnerView frame].origin.x, partnerCurrentOrigin, [partnerView frame].size.width, partnerCurrentSize);
		} else {
			resizeRect = NSMakeRect(partnerCurrentOrigin, [partnerView frame].origin.y, partnerCurrentSize, [partnerView frame].size.height);
		}
		[partnerView setFrame:resizeRect];
        [partnerView setNeedsDisplay:YES];
        [self setFrame:myNewFrame];
    } else {
        // resize self and window
		NSRect resizeRect;
        if ([self orientation] == PSMTabBarHorizontalOrientation) {
			resizeRect = NSMakeRect([[self window] frame].origin.x, partnerCurrentOrigin, [[self window] frame].size.width, partnerCurrentSize);
		} else {
			resizeRect = NSMakeRect(partnerCurrentOrigin, [[self window] frame].origin.y, partnerCurrentSize, [[self window] frame].size.height);
		}
        [[self window] setFrame:resizeRect display:YES];
        [self setFrame:myNewFrame];
    }
    
    // next
    _currentStep++;
    if (_currentStep == kPSMHideAnimationSteps + 1) {
		_currentStep = kPSMIsNotBeingResized;
        [self viewDidEndLiveResize];
        [self update:NO];
		
		//send the delegate messages
		if (_isHidden) {
			if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:tabBarDidHide:)]) {
				[[self delegate] tabView:[self tabView] tabBarDidHide:self];
			}
		} else {
			[self addSubview:_overflowPopUpButton];
			[self addSubview:_addTabButton];

			if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:tabBarDidUnhide:)]) {
				[[self delegate] tabView:[self tabView] tabBarDidUnhide:self];
			}
		}
		
		[_showHideAnimationTimer invalidate];
		[_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
    }
    [[self window] display];
}

- (BOOL)isTabBarHidden
{
	return _isHidden;
}

- (BOOL)isAnimating
{
    return _animationTimer != nil;
}

- (id)partnerView
{
    return partnerView;
}

- (void)setPartnerView:(id)view
{
    [partnerView release];
    [view retain];
    partnerView = view;
}

#pragma mark -
#pragma mark Drawing

- (BOOL)isFlipped
{
    return YES;
}

- (void)drawRect:(NSRect)rect 
{
    [style drawTabBar:self inRect:rect];
}

- (void)update
{
	[self update:_automaticallyAnimates];
}

- (void)update:(BOOL)animate
{
    // make sure all of our tabs are accounted for before updating,
	// or only proceed if a drag is in progress (where counts may mismatch)
    if ([[self tabView] numberOfTabViewItems] != (NSInteger)[_cells count] && ![[PSMTabDragAssistant sharedDragAssistant] isDragging]) {
        return;
    }

    // hide/show? (these return if already in desired state)
    if ( (_hideForSingleTab) && ([_cells count] <= 1) ) {
        [self hideTabBar:YES animate:YES];
        return;
    } else {
        [self hideTabBar:NO animate:YES];
    }
	
    [self removeAllToolTips];
    [_controller layoutCells]; //eventually we should only have to call this when we know something has changed
    
    PSMTabBarCell *currentCell;
    
    NSMenu *overflowMenu = [_controller overflowMenu];
    [_overflowPopUpButton setHidden:(overflowMenu == nil)];
    [_overflowPopUpButton setMenu:overflowMenu];

	if (_animationTimer) {
		[_animationTimer invalidate];
		[_animationTimer release]; _animationTimer = nil;
	}	

    if (animate) {
        NSMutableArray *targetFrames = [NSMutableArray arrayWithCapacity:[_cells count]];
        
        for (NSUInteger i = 0; i < [_cells count]; i++) {
            currentCell = [_cells objectAtIndex:i];
            
            //we're going from NSRect -> NSValue -> NSRect -> NSValue here - oh well
            [targetFrames addObject:[NSValue valueWithRect:[_controller cellFrameAtIndex:i]]];
        }
        
        [_addTabButton setHidden:!_showAddTabButton];
        
        NSAnimation *animation = [[NSAnimation alloc] initWithDuration:0.50 animationCurve:NSAnimationEaseInOut];
        [animation setAnimationBlockingMode:NSAnimationNonblocking];
        [animation startAnimation];
        _animationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0
															target:self
														  selector:@selector(_animateCells:)
														  userInfo:[NSArray arrayWithObjects:targetFrames, animation, nil]
														   repeats:YES] retain];
		[animation release];
		[[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSEventTrackingRunLoopMode];
		[self _animateCells:_animationTimer];

    } else {
        for (NSUInteger i = 0; i < [_cells count]; i++) {
            currentCell = [_cells objectAtIndex:i];
            [currentCell setFrame:[_controller cellFrameAtIndex:i]];
            
            if (![currentCell isInOverflowMenu]) {
                [self _setupTrackingRectsForCell:currentCell];
            }
        }
        
        [_addTabButton setFrame:[_controller addButtonRect]];
        [_addTabButton setHidden:!_showAddTabButton];
        [self setNeedsDisplay:YES];
    }
}

- (void)_animateCells:(NSTimer *)timer
{
    NSAnimation *animation = [[timer userInfo] objectAtIndex:1];
	NSArray *targetFrames = [[timer userInfo] objectAtIndex:0];
    PSMTabBarCell *currentCell;
	NSUInteger cellCount = (NSUInteger)[_cells count];
	
    if ((cellCount > 0) && [animation isAnimating]) {
		//compare our target position with the current position and move towards the target
		for (NSUInteger i = 0; i < [targetFrames count] && i < cellCount; i++) {
			currentCell = [_cells objectAtIndex:i];
			NSRect cellFrame = [currentCell frame], targetFrame = [[targetFrames objectAtIndex:i] rectValue];
			CGFloat sizeChange;
			CGFloat originChange;
			
			if ([self orientation] == PSMTabBarHorizontalOrientation) {
				sizeChange = (targetFrame.size.width - cellFrame.size.width) * [animation currentProgress];
				originChange = (targetFrame.origin.x - cellFrame.origin.x) * [animation currentProgress];
				cellFrame.size.width += sizeChange;
				cellFrame.origin.x += originChange;
			} else {
				sizeChange = (targetFrame.size.height - cellFrame.size.height) * [animation currentProgress];
				originChange = (targetFrame.origin.y - cellFrame.origin.y) * [animation currentProgress];
				cellFrame.size.height += sizeChange;
				cellFrame.origin.y += originChange;
			}
			
			[currentCell setFrame:cellFrame];
			
			//highlight the cell if the mouse is over it
			NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
			NSRect closeRect = [currentCell closeButtonRectForFrame:cellFrame];
			[currentCell setHighlighted:NSMouseInRect(mousePoint, cellFrame, [self isFlipped])];
			[currentCell setCloseButtonOver:NSMouseInRect(mousePoint, closeRect, [self isFlipped])];
		}
        
        if (_showAddTabButton) {
            //animate the add tab button
            NSRect target = [_controller addButtonRect], frame = [_addTabButton frame];
            frame.origin.x += (target.origin.x - frame.origin.x) * [animation currentProgress];
            [_addTabButton setFrame:frame];
        }
    } else { 
		//put all the cells where they should be in their final position
		if (cellCount > 0) {
			for (NSUInteger i = 0; i < [targetFrames count] && i < cellCount; i++) {
				currentCell = [_cells objectAtIndex:i];
				NSRect cellFrame = [currentCell frame], targetFrame = [[targetFrames objectAtIndex:i] rectValue];
				
                if ([self orientation] == PSMTabBarHorizontalOrientation) {
                    cellFrame.size.width = targetFrame.size.width;
                    cellFrame.origin.x = targetFrame.origin.x;
                } else {
                    cellFrame.size.height = targetFrame.size.height;
                    cellFrame.origin.y = targetFrame.origin.y;
                }
				
				[currentCell setFrame:cellFrame];
                
                //highlight the cell if the mouse is over it
                NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
                NSRect closeRect = [currentCell closeButtonRectForFrame:cellFrame];
                [currentCell setHighlighted:NSMouseInRect(mousePoint, cellFrame, [self isFlipped])];
                [currentCell setCloseButtonOver:NSMouseInRect(mousePoint, closeRect, [self isFlipped])];
			}
		}
        
		//set the frame for the add tab button
        if (_showAddTabButton) {
            NSRect frame = [_addTabButton frame];
            frame.origin.x = [_controller addButtonRect].origin.x;
            [_addTabButton setFrame:frame];
        }

		[_animationTimer invalidate];
		[_animationTimer release]; _animationTimer = nil;
		
        for (NSUInteger i = 0; i < cellCount; i++) {
            currentCell = [_cells objectAtIndex:i];
            
            //we've hit the cells that are in overflow, stop setting up tracking rects
            if ([currentCell isInOverflowMenu]) {
                break;
            }
            
            [self _setupTrackingRectsForCell:currentCell];
        }
    }
    
    [self setNeedsDisplay:YES];
}

- (void)_setupTrackingRectsForCell:(PSMTabBarCell *)cell
{

	// Skip tracking rects for placeholders - not required.
	if ([cell isPlaceholder]) return;

    NSInteger tag;
	NSUInteger anIndex = [_cells indexOfObject:cell];
    NSRect cellTrackingRect = [_controller cellTrackingRectAtIndex:anIndex];
    NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
    BOOL mouseInCell = NSMouseInRect(mousePoint, cellTrackingRect, [self isFlipped]);

	// If dragging, suppress mouse interaction
	if ([[PSMTabDragAssistant sharedDragAssistant] isDragging]) mouseInCell = NO;

    //set the cell tracking rect
    [self removeTrackingRect:[cell cellTrackingTag]];
    tag = [self addTrackingRect:cellTrackingRect owner:cell userData:nil assumeInside:mouseInCell];
    [cell setCellTrackingTag:tag];
    [cell setHighlighted:mouseInCell];
    
    if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
        NSRect closeRect = [_controller closeButtonTrackingRectAtIndex:anIndex];
        BOOL mouseInCloseRect = NSMouseInRect(mousePoint, closeRect, [self isFlipped]);
        
        //set the close button tracking rect
        [self removeTrackingRect:[cell closeButtonTrackingTag]];
        tag = [self addTrackingRect:closeRect owner:cell userData:nil assumeInside:mouseInCloseRect];
        [cell setCloseButtonTrackingTag:tag];
        
        [cell setCloseButtonOver:mouseInCloseRect];
    }
    
    //set the tooltip tracking rect
    [self addToolTipRect:[cell frame] owner:self userData:nil];
}

- (void)_positionOverflowMenu
{
    NSRect cellRect, frame = [self frame];
    cellRect.size.height = [style tabCellHeight];
    cellRect.size.width = [style rightMarginForTabBarControl];
    
	if ([self orientation] == PSMTabBarHorizontalOrientation) {
		cellRect.origin.y = 0;
		cellRect.origin.x = frame.size.width - [style rightMarginForTabBarControl] + (_resizeAreaCompensation ? -(_resizeAreaCompensation - 1) : 1);
		[_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin];
	} else {
		cellRect.origin.x = 0;
		cellRect.origin.y = frame.size.height - [style tabCellHeight];
		cellRect.size.width = frame.size.width;
		[_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin | NSViewMinYMargin];
	}
	
    [_overflowPopUpButton setFrame:cellRect];
}

- (void)_checkWindowFrame
{
	//figure out if the new frame puts the control in the way of the resize widget
	NSWindow *window = [self window];
	
	if (window) {
		NSRect resizeWidgetFrame = [[window contentView] frame];
		resizeWidgetFrame.origin.x += resizeWidgetFrame.size.width - 22;
		resizeWidgetFrame.size.width = 22;
		resizeWidgetFrame.size.height = 22;
		
		if ([window showsResizeIndicator] && NSIntersectsRect([self frame], resizeWidgetFrame)) {
			//the resize widgets are larger on metal windows
			_resizeAreaCompensation = [window styleMask] & NSTexturedBackgroundWindowMask ? 20 : 8;
		} else {
			_resizeAreaCompensation = 0;
		}
		
		[self _positionOverflowMenu];
	}
}

#pragma mark -
#pragma mark Mouse Tracking

- (BOOL)mouseDownCanMoveWindow
{
    return NO;
}

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
    return YES;
}

- (void)mouseDown:(NSEvent *)theEvent
{
	_didDrag = NO;
	
    // keep for dragging
    [self setLastMouseDownEvent:theEvent];
    // what cell?
    NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
	NSRect frame = [self frame];
	
	if ([self orientation] == PSMTabBarVerticalOrientation && [self allowsResizing] && partnerView && (mousePt.x > frame.size.width - 3)) {
		_resizing = YES;
	}
	
    NSRect cellFrame;
    PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
    if (cell) {
		BOOL overClose = NSMouseInRect(mousePt, [cell closeButtonRectForFrame:cellFrame], [self isFlipped]);
        if (overClose && 
			![self disableTabClose] && 
			![cell isCloseButtonSuppressed] &&
			([self allowsBackgroundTabClosing] || [[cell representedObject] isEqualTo:[tabView selectedTabViewItem]] || [theEvent modifierFlags] & NSCommandKeyMask)) {
            [cell setCloseButtonOver:NO];
            [cell setCloseButtonPressed:YES];
			_closeClicked = YES;
		}
		else if ([theEvent clickCount] == 2) {
			[cell setCloseButtonOver:NO];
			
			[_doubleClickTarget performSelector:_doubleClickAction withObject:cell];
        } else {
            [cell setCloseButtonPressed:NO];
			if (_selectsTabsOnMouseDown) {
				[self performSelector:@selector(tabClick:) withObject:cell];
			}
        }
        [self setNeedsDisplay:YES];
    } else {
		if ([theEvent clickCount] == 2) {
			// fire create new tab
			if ([self createsTabOnDoubleClick] && [self createNewTabTarget] != nil && [self createNewTabAction] != nil) {
				[[self createNewTabTarget] performSelector:[self createNewTabAction]];
			}
			return;
		}
	}
}

- (void)mouseDragged:(NSEvent *)theEvent
{
    if ([self lastMouseDownEvent] == nil) {
        return;
    }
    
	NSPoint currentPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
	
	if (_resizing) { 
		NSRect frame = [self frame];
		CGFloat resizeAmount = [theEvent deltaX];
		if ((currentPoint.x > frame.size.width && resizeAmount > 0) || (currentPoint.x < frame.size.width && resizeAmount < 0)) {
			[[NSCursor resizeLeftRightCursor] push];
			
			NSRect partnerFrame = [partnerView frame];
			
			//do some bounds checking
			if ((frame.size.width + resizeAmount > [self cellMinWidth]) && (frame.size.width + resizeAmount < [self cellMaxWidth])) {
				frame.size.width += resizeAmount;
				partnerFrame.size.width -= resizeAmount;
				partnerFrame.origin.x += resizeAmount;
				
				[self setFrame:frame];
				[partnerView setFrame:partnerFrame];
				[[self superview] setNeedsDisplay:YES];
			}	
		}
		return;
	}
	
    NSRect cellFrame;
    NSPoint trackingStartPoint = [self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil];
    PSMTabBarCell *cell = [self cellForPoint:trackingStartPoint cellFrame:&cellFrame];
    if (cell) {
		//check to see if the close button was the target in the clicked cell
		//highlight/unhighlight the close button as necessary
		NSRect iconRect = [cell closeButtonRectForFrame:cellFrame];
		
		if (_closeClicked && NSMouseInRect(trackingStartPoint, iconRect, [self isFlipped]) &&
				([self allowsBackgroundTabClosing] || [[cell representedObject] isEqualTo:[tabView selectedTabViewItem]])) {
			[cell setCloseButtonPressed:NSMouseInRect(currentPoint, iconRect, [self isFlipped])];
			[self setNeedsDisplay:YES];
			return;
		}
		
		CGFloat dx = fabsf(currentPoint.x - trackingStartPoint.x);
		CGFloat dy = fabsf(currentPoint.y - trackingStartPoint.y);
		CGFloat distance = sqrtf(dx * dx + dy * dy);
		
		if (distance >= 10 && !_didDrag && ![[PSMTabDragAssistant sharedDragAssistant] isDragging] &&
				[self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDragTabViewItem:fromTabBar:)] &&
				[[self delegate] tabView:tabView shouldDragTabViewItem:[cell representedObject] fromTabBar:self]) {
			_didDrag = YES;
			[[PSMTabDragAssistant sharedDragAssistant] startDraggingCell:cell fromTabBar:self withMouseDownEvent:[self lastMouseDownEvent]];
		}
	}
}

- (void)mouseUp:(NSEvent *)theEvent
{
	if (_resizing) {
		_resizing = NO;
		[[NSCursor arrowCursor] set];
	} else {
		// what cell?
		NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
		NSRect cellFrame, mouseDownCellFrame;
		PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
		PSMTabBarCell *mouseDownCell = [self cellForPoint:[self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil] cellFrame:&mouseDownCellFrame];
		if (cell) {
			NSPoint trackingStartPoint = [self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil];
			NSRect iconRect = [mouseDownCell closeButtonRectForFrame:mouseDownCellFrame];
			
			if ((NSMouseInRect(mousePt, iconRect,[self isFlipped])) && ![self disableTabClose] && ![cell isCloseButtonSuppressed] && [mouseDownCell closeButtonPressed]) {
				if (([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0) {
					//If the user is holding Option, close all other tabs
					NSEnumerator	*enumerator = [[[[self cells] copy] autorelease] objectEnumerator];
					PSMTabBarCell	*otherCell;
					
					while ((otherCell = [enumerator nextObject])) {
						if (otherCell != cell)
							[self performSelector:@selector(closeTabClick:) withObject:otherCell];
					}
					
					//Fix the close button for the clicked tab not to be pressed
					[cell setCloseButtonPressed:NO];
					
				} else {
					//Otherwise, close this tab
					[self performSelector:@selector(closeTabClick:) withObject:cell];
				}

			} else if (NSMouseInRect(mousePt, mouseDownCellFrame, [self isFlipped]) &&
					   (!NSMouseInRect(trackingStartPoint, [cell closeButtonRectForFrame:cellFrame], [self isFlipped]) || ![self allowsBackgroundTabClosing] || [self disableTabClose])) {
				[mouseDownCell setCloseButtonPressed:NO];
				// If -[self selectsTabsOnMouseDown] is TRUE, we already performed tabClick: on mouseDown.
				if (![self selectsTabsOnMouseDown]) {
					[self performSelector:@selector(tabClick:) withObject:cell];
				}

			} else {
				[mouseDownCell setCloseButtonPressed:NO];
				[self performSelector:@selector(tabNothing:) withObject:cell];
			}
		}
		
		_closeClicked = NO;
	}
}

- (NSMenu *)menuForEvent:(NSEvent *)event
{
	NSMenu *menu = nil;
	NSTabViewItem *item = [[self cellForPoint:[self convertPoint:[event locationInWindow] fromView:nil] cellFrame:nil] representedObject];
	
	if (item && [[self delegate] respondsToSelector:@selector(tabView:menuForTabViewItem:)]) {
		menu = [[self delegate] tabView:tabView menuForTabViewItem:item];
	}
	return menu;
}

#pragma mark -
#pragma mark Drag and Drop

- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent
{
    return YES;
}

// NSDraggingSource
- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
    return (isLocal ? NSDragOperationMove : NSDragOperationNone);
}

- (BOOL)ignoreModifierKeysWhileDragging
{
    return YES;
}

- (void)draggedImage:(NSImage *)anImage beganAt:(NSPoint)screenPoint
{
	[[PSMTabDragAssistant sharedDragAssistant] draggingBeganAt:screenPoint];
}

- (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenPoint
{
	[[PSMTabDragAssistant sharedDragAssistant] draggingMovedTo:screenPoint];
}

// NSDraggingDestination
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
    if([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
        
        if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
				![[self delegate] tabView:[[sender draggingSource] tabView] shouldDropTabViewItem:[[[PSMTabDragAssistant sharedDragAssistant] draggedCell] representedObject] inTabBar:self]) {
			return NSDragOperationNone;
		}
        
        [[PSMTabDragAssistant sharedDragAssistant] draggingEnteredTabBar:self atPoint:[self convertPoint:[sender draggingLocation] fromView:nil]];
        return NSDragOperationMove;
    }
        
    return NSDragOperationNone;
}

- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
	PSMTabBarCell *cell = [self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil];
	
    if ([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
        
		if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
				![[self delegate] tabView:[[sender draggingSource] tabView] shouldDropTabViewItem:[[[PSMTabDragAssistant sharedDragAssistant] draggedCell] representedObject] inTabBar:self]) {
			return NSDragOperationNone;
		}
		
        [[PSMTabDragAssistant sharedDragAssistant] draggingUpdatedInTabBar:self atPoint:[self convertPoint:[sender draggingLocation] fromView:nil]];
        return NSDragOperationMove;
    } else if (cell) {
		//something that was accepted by the delegate was dragged on

		//Test for the space bar (the skip-the-delay key).
		enum { virtualKeycodeForSpace = 49 }; //Source: IM:Tx (Fig. C-2)
		union {
			KeyMap keymap;
			char bits[16];
		} keymap;
		GetKeys(keymap.keymap);
		if ((GetCurrentEventKeyModifiers() == 0) && bit_test(keymap.bits, virtualKeycodeForSpace)) {
			//The user pressed the space bar. This skips the delay; the user wants to pop the spring on this tab *now*.

			//For some reason, it crashes if I call -fire here. I don't know why. It doesn't crash if I simply set the fire date to now.
			[_springTimer setFireDate:[NSDate date]];
		} else {
			//Wind the spring for a spring-loaded drop.
			//The delay time comes from Finder's defaults, which specifies it in milliseconds.
			//If the delegate can't handle our spring-loaded drop, we'll abort it when the timer fires. See fireSpring:. This is simpler than constantly (checking for spring-loaded awareness and tearing down/rebuilding the timer) at every delegate change.

			//If the user has dragged to a different tab, reset the timer.
			if (_tabViewItemWithSpring != [cell representedObject]) {
				[_springTimer invalidate];
				[_springTimer release]; _springTimer = nil;
				_tabViewItemWithSpring = [cell representedObject];
			}
			if (!_springTimer) {
				//Finder's default delay time, as of Tiger, is 668 ms. If the user has never changed it, there's no setting in its defaults, so we default to that amount.
				NSNumber *delayNumber = [(NSNumber *)CFPreferencesCopyAppValue((CFStringRef)@"SpringingDelayMilliseconds", (CFStringRef)@"com.apple.finder") autorelease];
				NSTimeInterval delaySeconds = delayNumber ? [delayNumber doubleValue] / 1000.0 : 0.668;
				_springTimer = [[NSTimer scheduledTimerWithTimeInterval:delaySeconds
																 target:self
															   selector:@selector(fireSpring:)
															   userInfo:sender
																repeats:NO] retain];
			}

			// Notify the delegate to respond to drag events if supported.  This allows custom
			// behaviour when dragging certain drag types onto the tab - for example changing the
			// view appropriately.
			if ([self delegate] && [[self delegate] respondsToSelector:@selector(draggingEvent:enteredTabBar:tabView:)]) {
				[[self delegate] draggingEvent:sender enteredTabBar:self tabView:[cell representedObject]];
			}
		}
		return NSDragOperationCopy;
	}
        
    return NSDragOperationNone;
}

- (void)draggingExited:(id <NSDraggingInfo>)sender
{
	[_springTimer invalidate];
	[_springTimer release]; _springTimer = nil;

    [[PSMTabDragAssistant sharedDragAssistant] draggingExitedTabBar:self];
}

- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
	//validate the drag operation only if there's a valid tab bar to drop into
	return [[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] == NSNotFound ||
				[[PSMTabDragAssistant sharedDragAssistant] destinationTabBar] != nil;
}

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
	if ([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
		[[PSMTabDragAssistant sharedDragAssistant] performDragOperation];
	} else if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:acceptedDraggingInfo:onTabViewItem:)]) {
		//forward the drop to the delegate
		[[self delegate] tabView:tabView acceptedDraggingInfo:sender onTabViewItem:[[self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil] representedObject]];
	}
    return YES;
}

- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{
	[[PSMTabDragAssistant sharedDragAssistant] draggedImageEndedAt:aPoint operation:operation];
}

- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
{

}

#pragma mark -
#pragma mark Spring-loading

- (void)fireSpring:(NSTimer *)timer
{
	NSAssert1(timer == _springTimer, @"Spring fired by unrecognized timer %@", timer);

	id <NSDraggingInfo> sender = [timer userInfo];
	PSMTabBarCell *cell = [self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil];
	[tabView selectTabViewItem:[cell representedObject]];

	_tabViewItemWithSpring = nil;
	[_springTimer invalidate];
	[_springTimer release]; _springTimer = nil;
}

#pragma mark -
#pragma mark Actions

- (void)overflowMenuAction:(id)sender
{
	NSTabViewItem *tabViewItem = (NSTabViewItem *)[sender representedObject];
	[tabView selectTabViewItem:tabViewItem];
}

- (void)closeTabClick:(id)sender
{
	NSTabViewItem *item = [sender representedObject];
    [sender retain];
    if(([_cells count] == 1) && (![self canCloseOnlyTab]))
        return;
    
    if ([[self delegate] respondsToSelector:@selector(tabView:shouldCloseTabViewItem:)]) {
        if (![[self delegate] tabView:tabView shouldCloseTabViewItem:item]) {
            // fix mouse downed close button
            [sender setCloseButtonPressed:NO];
            [sender release];
            return;
        }
    }
	
    [item retain];
    
	[tabView removeTabViewItem:item];
    [item release];
    [sender release];
}

- (void)tabClick:(id)sender
{
    [tabView selectTabViewItem:[sender representedObject]];
}

- (void)tabNothing:(id)sender
{
    //[self update];  // takes care of highlighting based on state
}

- (void)frameDidChange:(NSNotification *)notification
{
	[self _checkWindowFrame];

	// trying to address the drawing artifacts for the progress indicators - hackery follows
	// this one fixes the "blanking" effect when the control hides and shows itself
	NSEnumerator *e = [_cells objectEnumerator];
	PSMTabBarCell *cell;
	while ( (cell = [e nextObject]) ) {
		[[cell indicator] stopAnimation:self];

		[[cell indicator] performSelector:@selector(startAnimation:)
							   withObject:nil
							   afterDelay:0];
	}

	[self update:NO];
}

- (void)viewDidMoveToWindow
{
	[self _checkWindowFrame];
}

- (void)viewWillStartLiveResize
{
    NSEnumerator *e = [_cells objectEnumerator];
    PSMTabBarCell *cell;
    while ( (cell = [e nextObject]) ) {
        [[cell indicator] stopAnimation:self];
    }
    [self setNeedsDisplay:YES];
}

-(void)viewDidEndLiveResize
{
    NSEnumerator *e = [_cells objectEnumerator];
    PSMTabBarCell *cell;
    while ( (cell = [e nextObject]) ) {
        [[cell indicator] startAnimation:self];
    }
	
	[self _checkWindowFrame];
    [self update:NO];
}

- (void)resetCursorRects
{
	[super resetCursorRects];
	if ([self orientation] == PSMTabBarVerticalOrientation) {
		NSRect frame = [self frame];
		[self addCursorRect:NSMakeRect(frame.size.width - 2, 0, 2, frame.size.height) cursor:[NSCursor resizeLeftRightCursor]];
	}
}

- (void)windowDidMove:(NSNotification *)aNotification
{
    [self setNeedsDisplay:YES];
}

- (void)windowDidUpdate:(NSNotification *)notification
{
    // hide? must readjust things if I'm not supposed to be showing
    // this block of code only runs when the app launches
    if ([self hideForSingleTab] && ([_cells count] <= 1) && !_awakenedFromNib) {
        // must adjust frames now before display
        NSRect myFrame = [self frame];
		if ([self orientation] == PSMTabBarHorizontalOrientation) {
			if (partnerView) {
				NSRect partnerFrame = [partnerView frame];
				// above or below me?
				if (myFrame.origin.y - 22 > [partnerView frame].origin.y) {
					// partner is below me
					[self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y + 21, myFrame.size.width, myFrame.size.height - 21)];
					[partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y, partnerFrame.size.width, partnerFrame.size.height + 21)];
				} else {
					// partner is above me
					[self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
					[partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y - 21, partnerFrame.size.width, partnerFrame.size.height + 21)];
				}
				[partnerView setNeedsDisplay:YES];
				[self setNeedsDisplay:YES];
			} else {
				// for window movement
				NSRect windowFrame = [[self window] frame];
				[[self window] setFrame:NSMakeRect(windowFrame.origin.x, windowFrame.origin.y + 21, windowFrame.size.width, windowFrame.size.height - 21) display:YES];
				[self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
			}
		} else {
			if (partnerView) {
				NSRect partnerFrame = [partnerView frame];
				//to the left or right?
				if (myFrame.origin.x < [partnerView frame].origin.x) {
					// partner is to the left
					[self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, 1, myFrame.size.height)];
					[partnerView setFrame:NSMakeRect(partnerFrame.origin.x - myFrame.size.width + 1, partnerFrame.origin.y, partnerFrame.size.width + myFrame.size.width - 1, partnerFrame.size.height)];
				} else {
					// partner to the right
					[self setFrame:NSMakeRect(myFrame.origin.x + myFrame.size.width, myFrame.origin.y, 1, myFrame.size.height)];
					[partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y, partnerFrame.size.width + myFrame.size.width, partnerFrame.size.height)];
				}
				_tabBarWidth = myFrame.size.width;
				[partnerView setNeedsDisplay:YES];
				[self setNeedsDisplay:YES];
			} else {
				// for window movement
				NSRect windowFrame = [[self window] frame];
				[[self window] setFrame:NSMakeRect(windowFrame.origin.x + myFrame.size.width - 1, windowFrame.origin.y, windowFrame.size.width - myFrame.size.width + 1, windowFrame.size.height) display:YES];
				[self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, 1, myFrame.size.height)];
			}
		}
		
        _isHidden = YES;
        
		if ([[self delegate] respondsToSelector:@selector(tabView:tabBarDidHide:)]) {
			[[self delegate] tabView:[self tabView] tabBarDidHide:self];
		}
    }
	
	_awakenedFromNib = YES;
	[self setNeedsDisplay:YES];
	
	//we only need to do this once
	[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidUpdateNotification object:nil];
}

#pragma mark -
#pragma mark Menu Validation

- (BOOL)validateMenuItem:(NSMenuItem *)sender
{
	[sender setState:([[sender representedObject] isEqualTo:[tabView selectedTabViewItem]]) ? NSOnState : NSOffState];
	
	return [[self delegate] respondsToSelector:@selector(tabView:validateOverflowMenuItem:forTabViewItem:)] ?
		[[self delegate] tabView:[self tabView] validateOverflowMenuItem:sender forTabViewItem:[sender representedObject]] : YES;
}

#pragma mark -
#pragma mark NSTabView Delegate

- (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
    // here's a weird one - this message is sent before the "tabViewDidChangeNumberOfTabViewItems"
    // message, thus I can end up updating when there are no cells, if no tabs were (yet) present
	NSInteger tabIndex = [aTabView indexOfTabViewItem:tabViewItem];
	
    if ([_cells count] > 0 && tabIndex < (NSInteger)[_cells count]) {
		PSMTabBarCell *thisCell = [_cells objectAtIndex:tabIndex];
		if (_alwaysShowActiveTab && [thisCell isInOverflowMenu]) {
			
			//temporarily disable the delegate in order to move the tab to a different index
			id tempDelegate = [aTabView delegate];
			[aTabView setDelegate:nil];
			
			// move it all around first
			[tabViewItem retain];
			[thisCell retain];
			[aTabView removeTabViewItem:tabViewItem];
			[aTabView insertTabViewItem:tabViewItem atIndex:0];
			[_cells removeObjectAtIndex:tabIndex];
			[_cells insertObject:thisCell atIndex:0];
			[thisCell setIsInOverflowMenu:NO];	//very important else we get a fun recursive loop going
			[[_cells objectAtIndex:[_cells count] - 1] setIsInOverflowMenu:YES]; //these 2 lines are pretty uncool and this logic needs to be updated
			[thisCell release];
			[tabViewItem release];
			
			[aTabView setDelegate:tempDelegate];
			
            //reset the selection since removing it changed the selection
			[aTabView selectTabViewItem:tabViewItem];
            
			[self update];
		} else {
            [_controller setSelectedCell:thisCell];
            [self setNeedsDisplay:YES];
		}
    }
	
	if ([[self delegate] respondsToSelector:@selector(tabView:didSelectTabViewItem:)]) {
		[[self delegate] performSelector:@selector(tabView:didSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
	}
}

- (BOOL)tabView:(NSTabView *)aTabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
	if ([[self delegate] respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)]) {
		return [[self delegate] tabView:aTabView shouldSelectTabViewItem:tabViewItem];
	} else {
		return YES;
	}
}
- (void)tabView:(NSTabView *)aTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
	if ([[self delegate] respondsToSelector:@selector(tabView:willSelectTabViewItem:)]) {
		[[self delegate] performSelector:@selector(tabView:willSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
	}
}

- (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)aTabView
{
    NSArray *tabItems = [tabView tabViewItems];
    // go through cells, remove any whose representedObjects are not in [tabView tabViewItems]
    NSEnumerator *e = [[[_cells copy] autorelease] objectEnumerator];
    PSMTabBarCell *cell;
    while ( (cell = [e nextObject]) ) {
		//remove the observer binding
        if ([cell representedObject] && ![tabItems containsObject:[cell representedObject]]) {
			if ([[self delegate] respondsToSelector:@selector(tabView:didCloseTabViewItem:)]) {
				[[self delegate] tabView:aTabView didCloseTabViewItem:[cell representedObject]];
			}
			
            [self removeTabForCell:cell];
        }
    }
    
    // go through tab view items, add cell for any not present
    NSMutableArray *cellItems = [self representedTabViewItems];
    NSEnumerator *ex = [tabItems objectEnumerator];
    NSTabViewItem *item;
    while ( (item = [ex nextObject]) ) {
        if (![cellItems containsObject:item]) {
            [self addTabViewItem:item];
        }
    }

    // pass along for other delegate responses
    if ([[self delegate] respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)]) {
        [[self delegate] performSelector:@selector(tabViewDidChangeNumberOfTabViewItems:) withObject:aTabView];
    }
	
	// reset cursor tracking for the add tab button if one exists
	if ([self addTabButton]) [[self addTabButton] resetCursorRects];
}

#pragma mark -
#pragma mark Tooltips

- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)userData
{
	if ([[self delegate] respondsToSelector:@selector(tabView:toolTipForTabViewItem:)]) {
		return [[self delegate] tabView:[self tabView] toolTipForTabViewItem:[[self cellForPoint:point cellFrame:nil] representedObject]];
	}
	return nil;
}

#pragma mark -
#pragma mark Archiving

- (void)encodeWithCoder:(NSCoder *)aCoder 
{
    [super encodeWithCoder:aCoder];
    if ([aCoder allowsKeyedCoding]) {
        [aCoder encodeObject:_cells forKey:@"PSMcells"];
        [aCoder encodeObject:tabView forKey:@"PSMtabView"];
        [aCoder encodeObject:_overflowPopUpButton forKey:@"PSMoverflowPopUpButton"];
        [aCoder encodeObject:_addTabButton forKey:@"PSMaddTabButton"];
        [aCoder encodeObject:style forKey:@"PSMstyle"];
		[aCoder encodeInteger:_orientation forKey:@"PSMorientation"];
        [aCoder encodeBool:_canCloseOnlyTab forKey:@"PSMcanCloseOnlyTab"];
		[aCoder encodeBool:_disableTabClose forKey:@"PSMdisableTabClose"];
        [aCoder encodeBool:_hideForSingleTab forKey:@"PSMhideForSingleTab"];
		[aCoder encodeBool:_allowsBackgroundTabClosing forKey:@"PSMallowsBackgroundTabClosing"];
		[aCoder encodeBool:_allowsResizing forKey:@"PSMallowsResizing"];
		[aCoder encodeBool:_selectsTabsOnMouseDown forKey:@"PSMselectsTabsOnMouseDown"];
        [aCoder encodeBool:_showAddTabButton forKey:@"PSMshowAddTabButton"];
        [aCoder encodeBool:_sizeCellsToFit forKey:@"PSMsizeCellsToFit"];
        [aCoder encodeInteger:_cellMinWidth forKey:@"PSMcellMinWidth"];
        [aCoder encodeInteger:_cellMaxWidth forKey:@"PSMcellMaxWidth"];
        [aCoder encodeInteger:_cellOptimumWidth forKey:@"PSMcellOptimumWidth"];
        [aCoder encodeInteger:_currentStep forKey:@"PSMcurrentStep"];
        [aCoder encodeBool:_isHidden forKey:@"PSMisHidden"];
        [aCoder encodeObject:partnerView forKey:@"PSMpartnerView"];
        [aCoder encodeBool:_awakenedFromNib forKey:@"PSMawakenedFromNib"];
        [aCoder encodeObject:_lastMouseDownEvent forKey:@"PSMlastMouseDownEvent"];
        [aCoder encodeObject:delegate forKey:@"PSMdelegate"];
		[aCoder encodeBool:_useOverflowMenu forKey:@"PSMuseOverflowMenu"];
		[aCoder encodeBool:_automaticallyAnimates forKey:@"PSMautomaticallyAnimates"];
		[aCoder encodeBool:_alwaysShowActiveTab forKey:@"PSMalwaysShowActiveTab"];
    }
}

- (id)initWithCoder:(NSCoder *)aDecoder 
{
    self = [super initWithCoder:aDecoder];
    if (self) {

            // Initialization
        [self initAddedProperties];
        [self registerForDraggedTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil]];
    
        if ([aDecoder allowsKeyedCoding]) {
            _cells = [[aDecoder decodeObjectForKey:@"PSMcells"] retain];
            tabView = [[aDecoder decodeObjectForKey:@"PSMtabView"] retain];
            _overflowPopUpButton = [[aDecoder decodeObjectForKey:@"PSMoverflowPopUpButton"] retain];
            _addTabButton = [[aDecoder decodeObjectForKey:@"PSMaddTabButton"] retain];
            style = [[aDecoder decodeObjectForKey:@"PSMstyle"] retain];
			_orientation = (PSMTabBarOrientation)[aDecoder decodeIntegerForKey:@"PSMorientation"];
            _canCloseOnlyTab = [aDecoder decodeBoolForKey:@"PSMcanCloseOnlyTab"];
			_disableTabClose = [aDecoder decodeBoolForKey:@"PSMdisableTabClose"];
            _hideForSingleTab = [aDecoder decodeBoolForKey:@"PSMhideForSingleTab"];
			_allowsBackgroundTabClosing = [aDecoder decodeBoolForKey:@"PSMallowsBackgroundTabClosing"];
			_allowsResizing = [aDecoder decodeBoolForKey:@"PSMallowsResizing"];
			_selectsTabsOnMouseDown = [aDecoder decodeBoolForKey:@"PSMselectsTabsOnMouseDown"];
            _showAddTabButton = [aDecoder decodeBoolForKey:@"PSMshowAddTabButton"];
            _sizeCellsToFit = [aDecoder decodeBoolForKey:@"PSMsizeCellsToFit"];
            _cellMinWidth = [aDecoder decodeIntegerForKey:@"PSMcellMinWidth"];
            _cellMaxWidth = [aDecoder decodeIntegerForKey:@"PSMcellMaxWidth"];
            _cellOptimumWidth = [aDecoder decodeIntegerForKey:@"PSMcellOptimumWidth"];
            _currentStep = [aDecoder decodeIntegerForKey:@"PSMcurrentStep"];
            _isHidden = [aDecoder decodeBoolForKey:@"PSMisHidden"];
            partnerView = [[aDecoder decodeObjectForKey:@"PSMpartnerView"] retain];
            _awakenedFromNib = [aDecoder decodeBoolForKey:@"PSMawakenedFromNib"];
            _lastMouseDownEvent = [[aDecoder decodeObjectForKey:@"PSMlastMouseDownEvent"] retain];
			_useOverflowMenu = [aDecoder decodeBoolForKey:@"PSMuseOverflowMenu"];
			_automaticallyAnimates = [aDecoder decodeBoolForKey:@"PSMautomaticallyAnimates"];
			_alwaysShowActiveTab = [aDecoder decodeBoolForKey:@"PSMalwaysShowActiveTab"];
            delegate = [[aDecoder decodeObjectForKey:@"PSMdelegate"] retain];
        }
        
            // resize
        [self setPostsFrameChangedNotifications:YES];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
    }
    
    [self setTarget:self];    
    return self;
}

#pragma mark -
#pragma mark IB Palette

- (NSSize)minimumFrameSizeFromKnobPosition:(NSInteger)position
{
    return NSMakeSize(100.0f, 22.0f);
}

- (NSSize)maximumFrameSizeFromKnobPosition:(NSInteger)knobPosition
{
    return NSMakeSize(10000.0f, 22.0f);
}

- (void)placeView:(NSRect)newFrame
{
    // this is called any time the view is resized in IB
    [self setFrame:newFrame];
    [self update:NO];
}

#pragma mark -
#pragma mark Convenience

- (void)bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item
{
    [self _bindPropertiesForCell:cell andTabViewItem:item];
    
    // watch for changes in the identifier
    [item addObserver:self forKeyPath:@"identifier" options:0 context:nil];
}

- (void)_bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item
{
    // bind the indicator to the represented object's status (if it exists)
    [[cell indicator] setHidden:YES];
    if ([item identifier] != nil) {
		if ([[[cell representedObject] identifier] respondsToSelector:@selector(isProcessing)]) {
			NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
			[bindingOptions setObject:NSNegateBooleanTransformerName forKey:@"NSValueTransformerName"];
			[[cell indicator] bind:@"animate" toObject:[item identifier] withKeyPath:@"isProcessing" options:nil];
			[[cell indicator] bind:@"hidden" toObject:[item identifier] withKeyPath:@"isProcessing" options:bindingOptions];
            [[item identifier] addObserver:cell forKeyPath:@"isProcessing" options:0 context:nil];
        }
    }
    
    // bind for the existence of an icon
    [cell setHasIcon:NO];
    if ([item identifier] != nil) {
		if ([[[cell representedObject] identifier] respondsToSelector:@selector(icon)]) {
			NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
			[bindingOptions setObject:NSIsNotNilTransformerName forKey:@"NSValueTransformerName"];
			[cell bind:@"hasIcon" toObject:[item identifier] withKeyPath:@"icon" options:bindingOptions];
			[[item identifier] addObserver:cell forKeyPath:@"icon" options:0 context:nil];
        }
    }
    
    // bind for the existence of a counter
    [cell setCount:0];
    if ([item identifier] != nil) {
		if ([[[cell representedObject] identifier] respondsToSelector:@selector(count)]) {
			[cell bind:@"count" toObject:[item identifier] withKeyPath:@"objectCount" options:nil];
			[[item identifier] addObserver:cell forKeyPath:@"objectCount" options:0 context:nil];
		}
    }
	
    // bind for the color of a counter
    [cell setCountColor:nil];
    if ([item identifier] != nil) {
		if ([[[cell representedObject] identifier] respondsToSelector:@selector(countColor)]) {
			[cell bind:@"countColor" toObject:[item identifier] withKeyPath:@"countColor" options:nil];
			[[item identifier] addObserver:cell forKeyPath:@"countColor" options:0 context:nil];
		}
    }

	// bind for a large image
	[cell setHasLargeImage:NO];
    if ([item identifier] != nil) {
		if ([[[cell representedObject] identifier] respondsToSelector:@selector(largeImage)]) {
			NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
			[bindingOptions setObject:NSIsNotNilTransformerName forKey:@"NSValueTransformerName"];
			[cell bind:@"hasLargeImage" toObject:[item identifier] withKeyPath:@"largeImage" options:bindingOptions];
			[[item identifier] addObserver:cell forKeyPath:@"largeImage" options:0 context:nil];
		}
    }
	
    [cell setIsEdited:NO];
    if ([item identifier] != nil) {
		if ([[[cell representedObject] identifier] respondsToSelector:@selector(isEdited)]) {
			[cell bind:@"isEdited" toObject:[item identifier] withKeyPath:@"isEdited" options:nil];
			[[item identifier] addObserver:cell forKeyPath:@"isEdited" options:0 context:nil];
		}
    }
    
    // bind my string value to the label on the represented tab
    [cell bind:@"title" toObject:item withKeyPath:@"label" options:nil];
}

- (NSMutableArray *)representedTabViewItems
{
    NSMutableArray *temp = [NSMutableArray arrayWithCapacity:[_cells count]];
    NSEnumerator *e = [_cells objectEnumerator];
    PSMTabBarCell *cell;
    while ( (cell = [e nextObject])) {
        if ([cell representedObject]) {
			[temp addObject:[cell representedObject]];
		}
    }
    return temp;
}

- (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame
{
    if ([self orientation] == PSMTabBarHorizontalOrientation && !NSPointInRect(point, [self genericCellRect])) {
        return nil;
    }
    
    NSInteger i, cnt = [_cells count];
    for (i = 0; i < cnt; i++) {
        PSMTabBarCell *cell = [_cells objectAtIndex:i];
        
		if (NSPointInRect(point, [cell frame])) {
            if (outFrame) {
                *outFrame = [cell frame];
            }
            return cell;
        }
    }
    return nil;
}

- (PSMTabBarCell *)lastVisibleTab
{
    NSInteger i, cellCount = [_cells count];
    for (i = 0; i < cellCount; i++) {
        if ([[_cells objectAtIndex:i] isInOverflowMenu]) {
            return [_cells objectAtIndex:(i - 1)];
        }
    }
    return [_cells objectAtIndex:(cellCount - 1)];
}

- (NSUInteger)numberOfVisibleTabs
{
    NSUInteger i, cellCount = 0;
	PSMTabBarCell *nextCell;
	
    for (i = 0; i < [_cells count]; i++) {
		nextCell = [_cells objectAtIndex:i];
		
		if ([nextCell isInOverflowMenu]) {
            break;
        }
		
		if (![nextCell isPlaceholder]) {
			cellCount++;
		}
    }
	
    return cellCount;
}

#pragma mark -
#pragma mark Accessibility

-(BOOL)accessibilityIsIgnored {
	return NO;
}

- (id)accessibilityAttributeValue:(NSString *)attribute {
	id attributeValue = nil;
	if ([attribute isEqualToString: NSAccessibilityRoleAttribute]) {
		attributeValue = NSAccessibilityGroupRole;
	} else if ([attribute isEqualToString: NSAccessibilityChildrenAttribute]) {
		attributeValue = NSAccessibilityUnignoredChildren(_cells);
	} else {
		attributeValue = [super accessibilityAttributeValue:attribute];
	}
	return attributeValue;
}

- (id)accessibilityHitTest:(NSPoint)point {
	id hitTestResult = self;
	
	NSEnumerator *enumerator = [_cells objectEnumerator];
	PSMTabBarCell *cell = nil;
	PSMTabBarCell *highlightedCell = nil;
	
	while (!highlightedCell && (cell = [enumerator nextObject])) {
		if ([cell isHighlighted]) {
			highlightedCell = cell;
		}
	}
	
	if (highlightedCell) {
		hitTestResult = [highlightedCell accessibilityHitTest:point];
	}
	
	return hitTestResult;
}

@end