// // 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