//
//  PSMTabDragAssistant.m
//  PSMTabBarControl
//
//  Created by John Pannell on 4/10/06.
//  Copyright 2006 Positive Spin Media. All rights reserved.
//

#import "PSMTabDragAssistant.h"
#import "PSMTabBarCell.h"
#import "PSMTabStyle.h"
#import "PSMTabDragWindowController.h"

#define PI 3.1417

@interface PSMTabBarControl (Private)
- (void)update:(BOOL)animate;
@end

@interface PSMTabDragAssistant (Private)
- (NSImage *)_imageForViewOfCell:(PSMTabBarCell *)cell styleMask:(NSUInteger *)outMask;
- (NSImage *)_miniwindowImageOfWindow:(NSWindow *)window;
- (void)_expandWindow:(NSWindow *)window atPoint:(NSPoint)point;
@end

@implementation PSMTabDragAssistant

static PSMTabDragAssistant *sharedDragAssistant = nil;

#pragma mark -
#pragma mark Creation/Destruction

+ (PSMTabDragAssistant *)sharedDragAssistant {
	if(!sharedDragAssistant) {
		sharedDragAssistant = [[PSMTabDragAssistant alloc] init];
	}

	return sharedDragAssistant;
}

- (id)init {
	if((self = [super init])) {
		_sourceTabBar = nil;
		_destinationTabBar = nil;
		_participatingTabBars = [[NSMutableSet alloc] init];
		_draggedCell = nil;
		_animationTimer = nil;
		_sineCurveWidths = [[NSMutableArray alloc] initWithCapacity:kPSMTabDragAnimationSteps];
		_targetCell = nil;
		_isDragging = NO;
	}

	return self;
}

- (void)dealloc {
	[_sourceTabBar release];
	[_destinationTabBar release];
	[_participatingTabBars release];
	[_draggedCell release];
	[_animationTimer release];
	[_sineCurveWidths release];
	[_targetCell release];
	[super dealloc];
}

#pragma mark -
#pragma mark Accessors

- (PSMTabBarControl *)sourceTabBar {
	return _sourceTabBar;
}

- (void)setSourceTabBar:(PSMTabBarControl *)tabBar {
	[tabBar retain];
	[_sourceTabBar release];
	_sourceTabBar = tabBar;
}

- (PSMTabBarControl *)destinationTabBar {
	return _destinationTabBar;
}

- (void)setDestinationTabBar:(PSMTabBarControl *)tabBar {
	[tabBar retain];
	[_destinationTabBar release];
	_destinationTabBar = tabBar;
}

- (PSMTabBarCell *)draggedCell {
	return _draggedCell;
}

- (void)setDraggedCell:(PSMTabBarCell *)cell {
	[cell retain];
	[_draggedCell release];
	_draggedCell = cell;
}

- (NSInteger)draggedCellIndex {
	return _draggedCellIndex;
}

- (void)setDraggedCellIndex:(NSInteger)value {
	_draggedCellIndex = value;
}

- (BOOL)isDragging {
	return _isDragging;
}

- (void)setIsDragging:(BOOL)value {
	_isDragging = value;
}

- (NSPoint)currentMouseLoc {
	return _currentMouseLoc;
}

- (void)setCurrentMouseLoc:(NSPoint)point {
	_currentMouseLoc = point;
}

- (PSMTabBarCell *)targetCell {
	return _targetCell;
}

- (void)setTargetCell:(PSMTabBarCell *)cell {
	[cell retain];
	[_targetCell release];
	_targetCell = cell;
}

#pragma mark -
#pragma mark Functionality

- (void)startDraggingCell:(PSMTabBarCell *)cell fromTabBar:(PSMTabBarControl *)control withMouseDownEvent:(NSEvent *)event {
	[self setIsDragging:YES];
	[self setSourceTabBar:control];
	[self setDestinationTabBar:control];
	[_participatingTabBars addObject:control];
	[self setDraggedCell:cell];
	[self setDraggedCellIndex:[[control cells] indexOfObject:cell]];

	NSRect cellFrame = [cell frame];
	// list of widths for animation
	NSInteger i;
	CGFloat cellStepSize = ([control orientation] == PSMTabBarHorizontalOrientation) ? (cellFrame.size.width + 6) : (cellFrame.size.height + 1);
	for(i = 0; i < kPSMTabDragAnimationSteps - 1; i++) {
		NSInteger thisWidth = (NSInteger)(cellStepSize - ((cellStepSize / 2.0) + ((sin((PI / 2.0) + ((CGFloat)i / (CGFloat)kPSMTabDragAnimationSteps) * PI) * cellStepSize) / 2.0)));
		[_sineCurveWidths addObject:[NSNumber numberWithInteger:thisWidth]];
	}
	[_sineCurveWidths addObject:[NSNumber numberWithInteger:([control orientation] == PSMTabBarHorizontalOrientation) ? cellFrame.size.width : cellFrame.size.height]];

	// hide UI buttons
	[[control overflowPopUpButton] setHidden:YES];
	[[control addTabButton] setHidden:YES];

	[[NSCursor closedHandCursor] set];

	NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
	NSImage *dragImage = [cell dragImage];
	[[cell indicator] removeFromSuperview];
	[self distributePlaceholdersInTabBar:control withDraggedCell:cell];

	if([control isFlipped]) {
		cellFrame.origin.y += cellFrame.size.height;
	}
	[cell setHighlighted:NO];
	NSSize offset = NSZeroSize;
	[pboard declareTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil] owner: nil];
	[pboard setString:[[NSNumber numberWithInteger:[[control cells] indexOfObject:cell]] stringValue] forType:@"PSMTabBarControlItemPBType"];
	_animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 30.0) target:self selector:@selector(animateDrag:) userInfo:nil repeats:YES];

	[[NSNotificationCenter defaultCenter] postNotificationName:PSMTabDragDidBeginNotification object:nil];

	//retain the control in case the drag operation causes the control to be released
	[control retain];

	if([control delegate] && [[control delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
	   [[control delegate] tabView:[control tabView] shouldDropTabViewItem:[[self draggedCell] representedObject] inTabBar:nil]) {
		_currentTearOffStyle = [control tearOffStyle];
		_draggedTab = [[PSMTabDragWindowController alloc] initWithImage:dragImage styleMask:NSBorderlessWindowMask tearOffStyle:_currentTearOffStyle];

		cellFrame.origin.y -= cellFrame.size.height;
		[control dragImage:[[[NSImage alloc] initWithSize:NSMakeSize(1, 1)] autorelease] at:cellFrame.origin offset:offset event:event pasteboard:pboard source:control slideBack:NO];
	} else {
		[control dragImage:dragImage at:cellFrame.origin offset:offset event:event pasteboard:pboard source:control slideBack:YES];
	}

	[control release];
}

- (void)draggingEnteredTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc {
	if(_currentTearOffStyle == PSMTabBarTearOffMiniwindow && ![self destinationTabBar]) {
		[_draggedTab switchImages];
	}

	[self setDestinationTabBar:control];
	[self setCurrentMouseLoc:mouseLoc];
	// hide UI buttons
	[[control overflowPopUpButton] setHidden:YES];
	[[control addTabButton] setHidden:YES];
	if([[control cells] count] == 0 || ![[[control cells] objectAtIndex:0] isPlaceholder]) {
		[self distributePlaceholdersInTabBar:control];
	}
	[_participatingTabBars addObject:control];

	//tell the drag window to display only the header if there is one
	if(_currentTearOffStyle == PSMTabBarTearOffAlphaWindow && _draggedView) {
		if(_fadeTimer) {
			[_fadeTimer invalidate];
		}

		[[_draggedTab window] orderFront:nil];
		_fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(fadeOutDragWindow:) userInfo:nil repeats:YES];
	}
}

- (void)draggingUpdatedInTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc {
	if([self destinationTabBar] != control) {
		[self setDestinationTabBar:control];
	}
	[self setCurrentMouseLoc:mouseLoc];
}

- (void)draggingExitedTabBar:(PSMTabBarControl *)control {
	if([[control delegate] respondsToSelector:@selector(tabView:shouldAllowTabViewItem:toLeaveTabBar:)] &&
	   ![[control delegate] tabView:[control tabView] shouldAllowTabViewItem:[[self draggedCell] representedObject] toLeaveTabBar:control]) {
		return;
	}

	[self setDestinationTabBar:nil];
	[self setCurrentMouseLoc:NSMakePoint(-1.0, -1.0)];

	if(_fadeTimer) {
		[_fadeTimer invalidate];
		_fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(fadeInDragWindow:) userInfo:nil repeats:YES];
	} else if(_draggedTab) {
		if(_currentTearOffStyle == PSMTabBarTearOffAlphaWindow) {
			//create a new floating drag window
			if(!_draggedView) {
				NSUInteger styleMask;
				NSImage *viewImage = [self _imageForViewOfCell:[self draggedCell] styleMask:&styleMask];

				_draggedView = [[PSMTabDragWindowController alloc] initWithImage:viewImage styleMask:styleMask tearOffStyle:PSMTabBarTearOffAlphaWindow];
				[[_draggedView window] setAlphaValue:0.0];
			}

			NSPoint windowOrigin = [[control window] frame].origin;

			windowOrigin.x -= _dragWindowOffset.width;
			windowOrigin.y += _dragWindowOffset.height;
			[[_draggedView window] setFrameOrigin:windowOrigin];
			[[_draggedView window] orderWindow:NSWindowBelow relativeTo:[[_draggedTab window] windowNumber]];
		} else if(_currentTearOffStyle == PSMTabBarTearOffMiniwindow && ![_draggedTab alternateImage]) {
			NSImage *image;
			NSSize imageSize;
			NSUInteger mask;             //we don't need this but we can't pass nil in for the style mask, as some delegate implementations will crash

			if(!(image = [self _miniwindowImageOfWindow:[control window]])) {
				image = [[self _imageForViewOfCell:[self draggedCell] styleMask:&mask] copy];
			}

			imageSize = [image size];
			[image setScalesWhenResized:YES];

			if(imageSize.width > imageSize.height) {
				[image setSize:NSMakeSize(125, 125 * (imageSize.height / imageSize.width))];
			} else {
				[image setSize:NSMakeSize(125 * (imageSize.width / imageSize.height), 125)];
			}

			[_draggedTab setAlternateImage:image];
		}

		//set the window's alpha mask to zero if the last tab is being dragged
		//don't fade out the old window if the delegate doesn't respond to the new tab bar method, just to be safe
		if([[[self sourceTabBar] tabView] numberOfTabViewItems] == 1 && [self sourceTabBar] == control &&
		   [[[self sourceTabBar] delegate] respondsToSelector:@selector(tabView:newTabBarForDraggedTabViewItem:atPoint:)]) {
			[[[self sourceTabBar] window] setAlphaValue:0.0];

			if([_sourceTabBar tearOffStyle] == PSMTabBarTearOffAlphaWindow) {
				[[_draggedView window] setAlphaValue:kPSMTabDragWindowAlpha];
			} else {
				//#warning fix me - what should we do when the last tab is dragged as a miniwindow?
			}
		} else {
			if([_sourceTabBar tearOffStyle] == PSMTabBarTearOffAlphaWindow) {
				_fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(fadeInDragWindow:) userInfo:nil repeats:YES];
			} else {
				[_draggedTab switchImages];
				_centersDragWindows = YES;
			}
		}
	}
}

- (void)performDragOperation {
	// move cell
	NSInteger destinationIndex = [[[self destinationTabBar] cells] indexOfObject:[self targetCell]];

	//there is the slight possibility of the targetCell now being set properly, so avoid errors
	if(destinationIndex >= [[[self destinationTabBar] cells] count]) {
		destinationIndex = [[[self destinationTabBar] cells] count] - 1;
	}

	[[[self destinationTabBar] cells] replaceObjectAtIndex:destinationIndex withObject:[self draggedCell]];
	[[self draggedCell] setControlView:[self destinationTabBar]];

	// move actual NSTabViewItem
	if([self sourceTabBar] != [self destinationTabBar]) {
		//remove the tracking rects and bindings registered on the old tab
		[[self sourceTabBar] removeTrackingRect:[[self draggedCell] closeButtonTrackingTag]];
		[[self sourceTabBar] removeTrackingRect:[[self draggedCell] cellTrackingTag]];
		[[self sourceTabBar] removeTabForCell:[self draggedCell]];

		NSInteger i, insertIndex;
		NSArray *cells = [[self destinationTabBar] cells];

		//find the index of where the dragged cell was just dropped
		for(i = 0, insertIndex = 0; (i < [cells count]) && ([cells objectAtIndex:i] != [self draggedCell]); i++, insertIndex++) {
			if([[cells objectAtIndex:i] isPlaceholder]) {
				insertIndex--;
			}
		}

		[[[self sourceTabBar] tabView] removeTabViewItem:[[self draggedCell] representedObject]];
		[[[self destinationTabBar] tabView] insertTabViewItem:[[self draggedCell] representedObject] atIndex:insertIndex];

		//calculate the position for the dragged cell
		if([[self destinationTabBar] automaticallyAnimates]) {
			if(insertIndex > 0) {
				NSRect cellRect = [[cells objectAtIndex:insertIndex - 1] frame];
				cellRect.origin.x += cellRect.size.width;
				[[self draggedCell] setFrame:cellRect];
			}
		}

		//rebind the cell to the new control
		[[self destinationTabBar] bindPropertiesForCell:[self draggedCell] andTabViewItem:[[self draggedCell] representedObject]];

		//select the newly moved item in the destination tab view
		[[[self destinationTabBar] tabView] selectTabViewItem:[[self draggedCell] representedObject]];
	} else {
		//have to do this before checking the index of a cell otherwise placeholders will be counted
		[self removeAllPlaceholdersFromTabBar:[self sourceTabBar]];

		//rearrange the tab view items
		NSTabView *tabView = [[self sourceTabBar] tabView];
		NSTabViewItem *item = [[self draggedCell] representedObject];
		BOOL reselect = ([tabView selectedTabViewItem] == item);
		NSInteger index;
		NSArray *cells = [[self sourceTabBar] cells];

		//find the index of where the dragged cell was just dropped
		for(index = 0; index < [cells count] && [cells objectAtIndex:index] != [self draggedCell]; index++) {
			;
		}

		//temporarily disable the delegate in order to move the tab to a different index
		id tempDelegate = [tabView delegate];
		[tabView setDelegate:nil];
		[item retain];
		[tabView removeTabViewItem:item];
		[tabView insertTabViewItem:item atIndex:index];
		if(reselect) {
			[tabView selectTabViewItem:item];
		}
		[tabView setDelegate:tempDelegate];
	}

	if(([self sourceTabBar] != [self destinationTabBar] || [[[self sourceTabBar] cells] indexOfObject:[self draggedCell]] != _draggedCellIndex) && [[[self sourceTabBar] delegate] respondsToSelector:@selector(tabView:didDropTabViewItem:inTabBar:)]) {
		[[[self sourceTabBar] delegate] tabView:[[self sourceTabBar] tabView] didDropTabViewItem:[[self draggedCell] representedObject] inTabBar:[self destinationTabBar]];
	}

	[[NSNotificationCenter defaultCenter] postNotificationName:PSMTabDragDidEndNotification object:nil];

	[self finishDrag];
}

- (void)draggedImageEndedAt:(NSPoint)aPoint operation:(NSDragOperation)operation {
	if([self isDragging]) {   // means there was not a successful drop (performDragOperation)
		id sourceDelegate = [[self sourceTabBar] delegate];

		//split off the dragged tab into a new window
		if([self destinationTabBar] == nil &&
		   sourceDelegate && [sourceDelegate respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
		   [sourceDelegate tabView:[[self sourceTabBar] tabView] shouldDropTabViewItem:[[self draggedCell] representedObject] inTabBar:nil] &&
		   [sourceDelegate respondsToSelector:@selector(tabView:newTabBarForDraggedTabViewItem:atPoint:)]) {
			PSMTabBarControl *control = [sourceDelegate tabView:[[self sourceTabBar] tabView] newTabBarForDraggedTabViewItem:[[self draggedCell] representedObject] atPoint:aPoint];

			if(control) {
				//add the dragged tab to the new window
				[[control cells] insertObject:[self draggedCell] atIndex:0];

				//remove the tracking rects and bindings registered on the old tab
				[[self sourceTabBar] removeTrackingRect:[[self draggedCell] closeButtonTrackingTag]];
				[[self sourceTabBar] removeTrackingRect:[[self draggedCell] cellTrackingTag]];
				[[self sourceTabBar] removeTabForCell:[self draggedCell]];

				//rebind the cell to the new control
				[control bindPropertiesForCell:[self draggedCell] andTabViewItem:[[self draggedCell] representedObject]];

				[[self draggedCell] setControlView:control];

				[[[self sourceTabBar] tabView] removeTabViewItem:[[self draggedCell] representedObject]];

				[[control tabView] addTabViewItem:[[self draggedCell] representedObject]];
				[control update:NO];                 //make sure the new tab is set in the correct position

				if(_currentTearOffStyle == PSMTabBarTearOffAlphaWindow) {
					[[control window] makeKeyAndOrderFront:nil];
				} else {
					//center the window over where we ended dragging
					[self _expandWindow:[control window] atPoint:[NSEvent mouseLocation]];
				}

				if([sourceDelegate respondsToSelector:@selector(tabView:didDropTabViewItem:inTabBar:)]) {
					[sourceDelegate tabView:[[self sourceTabBar] tabView] didDropTabViewItem:[[self draggedCell] representedObject] inTabBar:control];
				}
			} else {
				NSLog(@"Delegate returned no control to add to.");
				[[[self sourceTabBar] cells] insertObject:[self draggedCell] atIndex:[self draggedCellIndex]];
			}
		} else {
			// put cell back
			[[[self sourceTabBar] cells] insertObject:[self draggedCell] atIndex:[self draggedCellIndex]];
		}

		[[NSNotificationCenter defaultCenter] postNotificationName:PSMTabDragDidEndNotification object:nil];

		[self finishDrag];
	}
}

- (void)finishDrag {
	if([[[self sourceTabBar] tabView] numberOfTabViewItems] == 0 && [[[self sourceTabBar] delegate] respondsToSelector:@selector(tabView:closeWindowForLastTabViewItem:)]) {
		[[[self sourceTabBar] delegate] tabView:[[self sourceTabBar] tabView] closeWindowForLastTabViewItem:[[self draggedCell] representedObject]];
	}

	if(_draggedTab) {
		[[_draggedTab window] orderOut:nil];
		[_draggedTab release];
		_draggedTab = nil;
	}

	if(_draggedView) {
		[[_draggedView window] orderOut:nil];
		[_draggedView release];
		_draggedView = nil;
	}

	_centersDragWindows = NO;

	[self setIsDragging:NO];
	[self removeAllPlaceholdersFromTabBar:[self sourceTabBar]];
	[self setSourceTabBar:nil];
	[self setDestinationTabBar:nil];
	NSEnumerator *e = [_participatingTabBars objectEnumerator];
	PSMTabBarControl *tabBar;
	while((tabBar = [e nextObject])) {
		[self removeAllPlaceholdersFromTabBar:tabBar];
	}
	[_participatingTabBars removeAllObjects];
	[self setDraggedCell:nil];
	[_animationTimer invalidate];
	_animationTimer = nil;
	[_sineCurveWidths removeAllObjects];
	[self setTargetCell:nil];
}

- (void)draggingBeganAt:(NSPoint)aPoint {
	if(_draggedTab) {
		[[_draggedTab window] setFrameTopLeftPoint:aPoint];
		[[_draggedTab window] orderFront:nil];

		if([[[self sourceTabBar] tabView] numberOfTabViewItems] == 1) {
			[self draggingExitedTabBar:[self sourceTabBar]];
			[[_draggedTab window] setAlphaValue:0.0];
		}
	}
}

- (void)draggingMovedTo:(NSPoint)aPoint {
	if(_draggedTab) {
		if(_centersDragWindows) {
			if([_draggedTab isAnimating]) {
				return;
			}

			//Ignore aPoint, as it seems to give wacky values
			NSRect frame = [[_draggedTab window] frame];
			frame.origin = [NSEvent mouseLocation];
			frame.origin.x -= frame.size.width / 2;
			frame.origin.y -= frame.size.height / 2;
			[[_draggedTab window] setFrame:frame display:NO];
		} else {
			[[_draggedTab window] setFrameTopLeftPoint:aPoint];
		}

		if(_draggedView) {
			//move the view representation with the tab
			//the relative position of the dragged view window will be different
			//depending on the position of the tab bar relative to the controlled tab view

			aPoint.y -= [[_draggedTab window] frame].size.height;
			aPoint.x -= _dragWindowOffset.width;
			aPoint.y += _dragWindowOffset.height;
			[[_draggedView window] setFrameTopLeftPoint:aPoint];
		}
	}
}

- (void)fadeInDragWindow:(NSTimer *)timer {
	CGFloat value = [[_draggedView window] alphaValue];
	if(value >= kPSMTabDragWindowAlpha || _draggedTab == nil) {
		[timer invalidate];
		_fadeTimer = nil;
	} else {
		[[_draggedTab window] setAlphaValue:[[_draggedTab window] alphaValue] - kPSMTabDragAlphaInterval];
		[[_draggedView window] setAlphaValue:value + kPSMTabDragAlphaInterval];
	}
}

- (void)fadeOutDragWindow:(NSTimer *)timer {
	CGFloat value = [[_draggedView window] alphaValue];
	NSWindow *tabWindow = [_draggedTab window], *viewWindow = [_draggedView window];

	if(value <= 0.0) {
		[viewWindow setAlphaValue:0.0];
		[tabWindow setAlphaValue:kPSMTabDragWindowAlpha];

		[timer invalidate];
		_fadeTimer = nil;
	} else {
		if([tabWindow alphaValue] < kPSMTabDragWindowAlpha) {
			[tabWindow setAlphaValue:[tabWindow alphaValue] + kPSMTabDragAlphaInterval];
		}
		[viewWindow setAlphaValue:value - kPSMTabDragAlphaInterval];
	}
}

#pragma mark -
#pragma mark Private

- (NSImage *)_imageForViewOfCell:(PSMTabBarCell *)cell styleMask:(NSUInteger *)outMask {
	PSMTabBarControl *control = [cell controlView];
	NSImage *viewImage = nil;

	if(outMask) {
		*outMask = NSBorderlessWindowMask;
	}

	if([control delegate] && [[control delegate] respondsToSelector:@selector(tabView:imageForTabViewItem:offset:styleMask:)]) {
		//get a custom image representation of the view to drag from the delegate
		NSImage *tabImage = [_draggedTab image];
		NSPoint drawPoint;
		_dragWindowOffset = NSZeroSize;
		viewImage = [[control delegate] tabView:[control tabView] imageForTabViewItem:[cell representedObject] offset:&_dragWindowOffset styleMask:outMask];
		[viewImage lockFocus];

		//draw the tab into the returned window, that way we don't have two windows being dragged (this assumes the tab will be on the window)
		drawPoint = NSMakePoint(_dragWindowOffset.width, [viewImage size].height - _dragWindowOffset.height);

		if([control orientation] == PSMTabBarHorizontalOrientation) {
			drawPoint.y += [[control style] tabCellHeight] - [tabImage size].height;
			_dragWindowOffset.height -= [[control style] tabCellHeight] - [tabImage size].height;
		} else {
			drawPoint.x += [control frame].size.width - [tabImage size].width;
		}

		[tabImage compositeToPoint:drawPoint operation:NSCompositeSourceOver];

		[viewImage unlockFocus];
	} else {
		//the delegate doesn't give a custom image, so use an image of the view
		NSView *tabView = [[cell representedObject] view];
		viewImage = [[[NSImage alloc] initWithSize:[tabView frame].size] autorelease];
		[viewImage lockFocus];
		[tabView drawRect:[tabView bounds]];
		[viewImage unlockFocus];
	}

	if(*outMask | NSBorderlessWindowMask) {
		_dragWindowOffset.height += 22;
	}

	return viewImage;
}

- (NSImage *)_miniwindowImageOfWindow:(NSWindow *)window {
	NSRect rect = [window frame];
	NSImage *image = [[[NSImage alloc] initWithSize:rect.size] autorelease];
	[image lockFocus];
	rect.origin = NSZeroPoint;
	CGContextCopyWindowCaptureContentsToRect([[NSGraphicsContext currentContext] graphicsPort], *(CGRect *)&rect, [NSApp contextID], [window windowNumber], 0);
	[image unlockFocus];

	return image;
}

- (void)_expandWindow:(NSWindow *)window atPoint:(NSPoint)point {
	NSRect frame = [window frame];
	[window setFrameTopLeftPoint:NSMakePoint(point.x - frame.size.width / 2, point.y + frame.size.height / 2)];
	[window setAlphaValue:0.0];
	[window makeKeyAndOrderFront:nil];

	NSAnimation *animation = [[NSAnimation alloc] initWithDuration:0.25 animationCurve:NSAnimationEaseInOut];
	[animation setAnimationBlockingMode:NSAnimationNonblocking];
	[animation setCurrentProgress:0.1];
	[animation startAnimation];
	NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(_expandWindowTimerFired:) userInfo:[NSDictionary dictionaryWithObjectsAndKeys:window, @"Window", animation, @"Animation", nil] repeats:YES];
	[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
}

- (void)_expandWindowTimerFired:(NSTimer *)timer {
	NSWindow *window = [[timer userInfo] objectForKey:@"Window"];
	NSAnimation *animation = [[timer userInfo] objectForKey:@"Animation"];
	CGAffineTransform transform;
	NSPoint translation;
	NSRect winFrame = [window frame];

	translation.x = (winFrame.size.width / 2.0);
	translation.y = (winFrame.size.height / 2.0);
	transform = CGAffineTransformMakeTranslation(translation.x, translation.y);
	transform = CGAffineTransformScale(transform, 1.0 / [animation currentValue], 1.0 / [animation currentValue]);
	transform = CGAffineTransformTranslate(transform, -translation.x, -translation.y);

	translation.x = -winFrame.origin.x;
	translation.y = winFrame.origin.y + winFrame.size.height - [[NSScreen mainScreen] frame].size.height;

	transform = CGAffineTransformTranslate(transform, translation.x, translation.y);

	CGSSetWindowTransform([NSApp contextID], [window windowNumber], transform);

	[window setAlphaValue:[animation currentValue]];

	if(![animation isAnimating]) {
		[timer invalidate];
		[animation release];
	}
}

#pragma mark -
#pragma mark Animation

- (void)animateDrag:(NSTimer *)timer {
	NSEnumerator *e = [[[_participatingTabBars copy] autorelease] objectEnumerator];
	PSMTabBarControl *tabBar;
	while((tabBar = [e nextObject])) {
		[self calculateDragAnimationForTabBar:tabBar];
		[[NSRunLoop currentRunLoop] performSelector:@selector(display) target:tabBar argument:nil order:1 modes:[NSArray arrayWithObjects:@"NSEventTrackingRunLoopMode", @"NSDefaultRunLoopMode", nil]];
	}
}

- (void)calculateDragAnimationForTabBar:(PSMTabBarControl *)control {
	BOOL removeFlag = YES;
	NSMutableArray *cells = [control cells];
	NSInteger i, cellCount = [cells count];
	CGFloat position = [control orientation] == PSMTabBarHorizontalOrientation ?[[control style] leftMarginForTabBarControl] :[[control style] topMarginForTabBarControl];

	// identify target cell
	// mouse at beginning of tabs
	NSPoint mouseLoc = [self currentMouseLoc];
	if([self destinationTabBar] == control) {
		removeFlag = NO;
		if(mouseLoc.x < [[control style] leftMarginForTabBarControl]) {
			[self setTargetCell:[cells objectAtIndex:0]];
		} else {
			NSRect overCellRect;
			PSMTabBarCell *overCell = [control cellForPoint:mouseLoc cellFrame:&overCellRect];
			if(overCell) {
				// mouse among cells - placeholder
				if([overCell isPlaceholder]) {
					[self setTargetCell:overCell];
				} else if([control orientation] == PSMTabBarHorizontalOrientation) {
					// non-placeholders - horizontal orientation
					if(mouseLoc.x < (overCellRect.origin.x + (overCellRect.size.width / 2.0))) {
						// mouse on left side of cell
						[self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] - 1)]];
					} else {
						// mouse on right side of cell
						[self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] + 1)]];
					}
				} else {
					// non-placeholders - vertical orientation
					if(mouseLoc.y < (overCellRect.origin.y + (overCellRect.size.height / 2.0))) {
						// mouse on top of cell
						[self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] - 1)]];
					} else {
						// mouse on bottom of cell
						[self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] + 1)]];
					}
				}
			} else {
				// out at end - must find proper cell (could be more in overflow menu)
				[self setTargetCell:[control lastVisibleTab]];
			}
		}
	} else {
		[self setTargetCell:nil];
	}

	for(i = 0; i < cellCount; i++) {
		PSMTabBarCell *cell = [cells objectAtIndex:i];
		NSRect newRect = [cell frame];
		if(![cell isInOverflowMenu]) {
			if([cell isPlaceholder]) {
				if(cell == [self targetCell]) {
					[cell setCurrentStep:([cell currentStep] + 1)];
				} else {
					[cell setCurrentStep:([cell currentStep] - 1)];
					if([cell currentStep] > 0) {
						removeFlag = NO;
					}
				}

				if([control orientation] == PSMTabBarHorizontalOrientation) {
					newRect.size.width = [[_sineCurveWidths objectAtIndex:[cell currentStep]] integerValue];
				} else {
					newRect.size.height = [[_sineCurveWidths objectAtIndex:[cell currentStep]] integerValue];
				}
			}
		} else {
			break;
		}

		if([control orientation] == PSMTabBarHorizontalOrientation) {
			newRect.origin.x = position;
			position += newRect.size.width;
		} else {
			newRect.origin.y = position;
			position += newRect.size.height;
		}
		[cell setFrame:newRect];
		if([cell indicator]) {
			[[cell indicator] setFrame:[[control style] indicatorRectForTabCell:cell]];
		}
	}
	if(removeFlag) {
		[_participatingTabBars removeObject:control];
		[self removeAllPlaceholdersFromTabBar:control];
	}
}

#pragma mark -
#pragma mark Placeholders

- (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control withDraggedCell:(PSMTabBarCell *)cell {
	// called upon first drag - must distribute placeholders
	[self distributePlaceholdersInTabBar:control];

	NSMutableArray *cells = [control cells];

	// replace dragged cell with a placeholder, and clean up surrounding cells
	NSInteger cellIndex = [cells indexOfObject:cell];
	PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:YES inControlView:control] autorelease];
	[cells replaceObjectAtIndex:cellIndex withObject:pc];
	[cells removeObjectAtIndex:(cellIndex + 1)];
	[cells removeObjectAtIndex:(cellIndex - 1)];

	if(cellIndex - 2 >= 0) {
		pc = [cells objectAtIndex:cellIndex - 2];
		[pc setTabState:~[pc tabState] & PSMTab_RightIsSelectedMask];
	}
}

- (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control {
	NSInteger i, numVisibleTabs = [control numberOfVisibleTabs];
	for(i = 0; i < numVisibleTabs; i++) {
		PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control] autorelease];
		[[control cells] insertObject:pc atIndex:(2 * i)];
	}

	PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control] autorelease];
	if([[control cells] count] > (2 * numVisibleTabs)) {
		[[control cells] insertObject:pc atIndex:(2 * numVisibleTabs)];
	} else {
		[[control cells] addObject:pc];
	}
}

- (void)removeAllPlaceholdersFromTabBar:(PSMTabBarControl *)control {
	NSInteger i, cellCount = [[control cells] count];
	for(i = (cellCount - 1); i >= 0; i--) {
		PSMTabBarCell *cell = [[control cells] objectAtIndex:i];
		if([cell isPlaceholder]) {
			[control removeTabForCell:cell];
		}
	}
	// redraw
	[control update:NO];
}

#pragma mark -
#pragma mark Archiving

- (void)encodeWithCoder:(NSCoder *)aCoder {
	//[super encodeWithCoder:aCoder];
	if([aCoder allowsKeyedCoding]) {
		[aCoder encodeObject:_sourceTabBar forKey:@"sourceTabBar"];
		[aCoder encodeObject:_destinationTabBar forKey:@"destinationTabBar"];
		[aCoder encodeObject:_participatingTabBars forKey:@"participatingTabBars"];
		[aCoder encodeObject:_draggedCell forKey:@"draggedCell"];
		[aCoder encodeInteger:_draggedCellIndex forKey:@"draggedCellIndex"];
		[aCoder encodeBool:_isDragging forKey:@"isDragging"];
		[aCoder encodeObject:_animationTimer forKey:@"animationTimer"];
		[aCoder encodeObject:_sineCurveWidths forKey:@"sineCurveWidths"];
		[aCoder encodePoint:_currentMouseLoc forKey:@"currentMouseLoc"];
		[aCoder encodeObject:_targetCell forKey:@"targetCell"];
	}
}

- (id)initWithCoder:(NSCoder *)aDecoder {
	//self = [super initWithCoder:aDecoder];
	//if (self) {
	if([aDecoder allowsKeyedCoding]) {
		_sourceTabBar = [[aDecoder decodeObjectForKey:@"sourceTabBar"] retain];
		_destinationTabBar = [[aDecoder decodeObjectForKey:@"destinationTabBar"] retain];
		_participatingTabBars = [[aDecoder decodeObjectForKey:@"participatingTabBars"] retain];
		_draggedCell = [[aDecoder decodeObjectForKey:@"draggedCell"] retain];
		_draggedCellIndex = [aDecoder decodeIntegerForKey:@"draggedCellIndex"];
		_isDragging = [aDecoder decodeBoolForKey:@"isDragging"];
		_animationTimer = [[aDecoder decodeObjectForKey:@"animationTimer"] retain];
		_sineCurveWidths = [[aDecoder decodeObjectForKey:@"sineCurveWidths"] retain];
		_currentMouseLoc = [aDecoder decodePointForKey:@"currentMouseLoc"];
		_targetCell = [[aDecoder decodeObjectForKey:@"targetCell"] retain];
	}
	//}
	return self;
}


@end
