netsurf/cocoa/PSMTabBarControl/PSMTabDragAssistant.m

835 lines
30 KiB
Objective-C

//
// 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
NSUInteger 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]];
NSUInteger 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);
NSArray *cells = [[self sourceTabBar] cells];
NSUInteger index;
//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 {
NSUInteger 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