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

#import <objc/runtime.h>

#import "PSMTabBarControl.h"
#import "PSMTabBarCell.h"
#import "PSMOverflowPopUpButton.h"
#import "PSMRolloverButton.h"
#import "PSMTabStyle.h"
#import "PSMUnifiedTabStyle.h"
#import "PSMTabDragAssistant.h"
#import "PSMTabBarController.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)update;
- (void)update:(BOOL)animate;
- (void)_setupTrackingRectsForCell:(PSMTabBarCell *)cell;
- (void)_positionOverflowMenu;
- (void)_checkWindowFrame;

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

// notification handlers
- (void)frameDidChange:(NSNotification *)notification;
- (void)windowDidMove:(NSNotification *)aNotification;
- (void)windowDidUpdate:(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)_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.0;
	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;
	_cellMinWidth = 100;
	_cellMaxWidth = 280;
	_cellOptimumWidth = 130;
	_tearOffStyle = PSMTabBarTearOffAlphaWindow;
	style = [[[[self class] defaultStyleClass] 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.0, 16.0, 16.0);
	_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];
	}
}

+ (Class) defaultStyleClass
{
	return [PSMUnifiedTabStyle class];
}

- (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
	// http://code.google.com/p/maccode/issues/detail?id=35
	NSEnumerator *enumerator = [[[_cells copy] autorelease] 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:NSWindowDidBecomeKeyNotification object:aWindow];
		[center addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidResignKeyNotification 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 arrayWithObject:@"PSMTabBarControlItemPBType"];

	//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 {
	
	Class styleClass = NSClassFromString( [NSString stringWithFormat: @"PSM%@TabStyle", [name capitalizedString]] );
	if (styleClass == Nil) {
		styleClass = object_getClass([PSMTabBarControl defaultStyleClass]);
	}

	id <PSMTabStyle> newStyle = [[styleClass 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;
	[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];
}

- (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)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;
}

- (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;
	if([_cells lastObject] != nil) {
		cellRect = lastCellFrame = [[_cells lastObject] frame];
	} else {
		cellRect = lastCellFrame = NSZeroRect;
	}

	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([_cells count] == (NSUInteger)[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(objectCount)]) {
			[[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"] doubleValue] + (([[userInfo objectForKey:@"myTargetOrigin"] doubleValue] - [[userInfo objectForKey:@"myOriginalOrigin"] doubleValue]) * (_currentStep / kPSMHideAnimationSteps)));
	CGFloat myCurrentSize = ([[userInfo objectForKey:@"myOriginalSize"] doubleValue] + (([[userInfo objectForKey:@"myTargetSize"] doubleValue] - [[userInfo objectForKey:@"myOriginalSize"] doubleValue]) * (_currentStep / kPSMHideAnimationSteps)));
	CGFloat partnerCurrentOrigin = ([[userInfo objectForKey:@"partnerOriginalOrigin"] doubleValue] + (([[userInfo objectForKey:@"partnerTargetOrigin"] doubleValue] - [[userInfo objectForKey:@"partnerOriginalOrigin"] doubleValue]) * (_currentStep / kPSMHideAnimationSteps)));
	CGFloat partnerCurrentSize = ([[userInfo objectForKey:@"partnerOriginalSize"] doubleValue] + (([[userInfo objectForKey:@"partnerTargetSize"] doubleValue] - [[userInfo objectForKey:@"partnerOriginalSize"] doubleValue]) * (_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] respondsToSelector:@selector(tabView:tabBarDidHide:)]) {
				[[self delegate] tabView:[self tabView] tabBarDidHide:self];
			}
		} else {
			[self addSubview:_overflowPopUpButton];
			[self addSubview:_addTabButton];

			if([[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
	if((NSUInteger)[[self tabView] numberOfTabViewItems] != [_cells count]) {
		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 = [_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++) {
				PSMTabBarCell *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 {
	NSInteger tag, index = [_cells indexOfObject:cell];
	NSRect cellTrackingRect = [_controller cellTrackingRectAtIndex:index];
	NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
	BOOL mouseInCell = NSMouseInRect(mousePoint, cellTrackingRect, [self isFlipped]);

	//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:index];
		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 {
			[cell setCloseButtonPressed:NO];
			if(_selectsTabsOnMouseDown) {
				[self performSelector:@selector(tabClick:) withObject:cell];
			}
		}
		[self setNeedsDisplay:YES];
	}
}

- (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 = fabs(currentPoint.x - trackingStartPoint.x);
		CGFloat dy = fabs(currentPoint.y - trackingStartPoint.y);
		CGFloat distance = sqrt(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
- (NSDragOperation)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];
		}
		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];
			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
	NSUInteger tabIndex = [aTabView indexOfTabViewItem:tabViewItem];

	if([_cells count] > 0 && tabIndex < [_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]];
		
		// resize
		[self setPostsFrameChangedNotifications:YES];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
		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];
		}
	}
	[self setTarget:self];
	return self;
}

#pragma mark -
#pragma mark IB Palette

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

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

- (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(objectCount)]) {
			[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)];
}

- (NSInteger)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
