2 // PSMTabDragAssistant.m
5 // Created by John Pannell on 4/10/06.
6 // Copyright 2006 Positive Spin Media. All rights reserved.
9 #import "PSMTabDragAssistant.h"
10 #import "PSMTabBarCell.h"
11 #import "PSMTabStyle.h"
14 @implementation PSMTabDragAssistant
16 static PSMTabDragAssistant *sharedDragAssistant = nil;
19 #pragma mark Creation/Destruction
21 + (PSMTabDragAssistant *)sharedDragAssistant
23 if (!sharedDragAssistant){
24 sharedDragAssistant = [[PSMTabDragAssistant alloc] init];
27 return sharedDragAssistant;
32 if(self = [super init]){
34 _destinationTabBar = nil;
35 _participatingTabBars = [[NSMutableSet alloc] init];
37 _animationTimer = nil;
38 _sineCurveWidths = [[NSMutableArray alloc] initWithCapacity:kPSMTabDragAnimationSteps];
48 [_sourceTabBar release];
49 [_destinationTabBar release];
50 [_participatingTabBars release];
51 [_draggedCell release];
52 [_animationTimer release];
53 [_sineCurveWidths release];
54 [_targetCell release];
59 #pragma mark Accessors
61 - (PSMTabBarControl *)sourceTabBar
66 - (void)setSourceTabBar:(PSMTabBarControl *)tabBar
69 [_sourceTabBar release];
70 _sourceTabBar = tabBar;
73 - (PSMTabBarControl *)destinationTabBar
75 return _destinationTabBar;
78 - (void)setDestinationTabBar:(PSMTabBarControl *)tabBar
81 [_destinationTabBar release];
82 _destinationTabBar = tabBar;
85 - (PSMTabBarCell *)draggedCell
90 - (void)setDraggedCell:(PSMTabBarCell *)cell
93 [_draggedCell release];
97 - (int)draggedCellIndex
99 return _draggedCellIndex;
102 - (void)setDraggedCellIndex:(int)value
104 _draggedCellIndex = value;
112 - (void)setIsDragging:(BOOL)value
117 - (NSPoint)currentMouseLoc
119 return _currentMouseLoc;
122 - (void)setCurrentMouseLoc:(NSPoint)point
124 _currentMouseLoc = point;
127 - (PSMTabBarCell *)targetCell
132 - (void)setTargetCell:(PSMTabBarCell *)cell
135 [_targetCell release];
140 #pragma mark Functionality
142 - (void)startDraggingCell:(PSMTabBarCell *)cell fromTabBar:(PSMTabBarControl *)control withMouseDownEvent:(NSEvent *)event
144 [self setIsDragging:YES];
145 [self setSourceTabBar:control];
146 [self setDestinationTabBar:control];
147 [_participatingTabBars addObject:control];
148 [self setDraggedCell:cell];
149 [self setDraggedCellIndex:[[control cells] indexOfObject:cell]];
151 NSRect cellFrame = [cell frame];
152 // list of widths for animation
154 float cellWidth = cellFrame.size.width;
155 for(i = 0; i < kPSMTabDragAnimationSteps; i++){
157 thisWidth = (int)(cellWidth - ((cellWidth/2.0) + ((sin((PI/2.0) + ((float)i/(float)kPSMTabDragAnimationSteps)*PI) * cellWidth) / 2.0)));
158 [_sineCurveWidths addObject:[NSNumber numberWithInt:thisWidth]];
162 [[control overflowPopUpButton] setHidden:YES];
163 [[control addTabButton] setHidden:YES];
165 [[NSCursor closedHandCursor] set];
167 NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
168 NSImage *dragImage = [cell dragImageForRect:cellFrame];
169 [[cell indicator] removeFromSuperview];
170 [self distributePlaceholdersInTabBar:control withDraggedCell:cell];
172 if([control isFlipped]){
173 cellFrame.origin.y += cellFrame.size.height;
175 [cell setHighlighted:NO];
176 NSSize offset = NSZeroSize;
177 [pboard declareTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil] owner: nil];
178 [pboard setString:[[NSNumber numberWithInt:[[control cells] indexOfObject:cell]] stringValue] forType:@"PSMTabBarControlItemPBType"];
179 _animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0/30.0) target:self selector:@selector(animateDrag:) userInfo:nil repeats:YES];
180 [control dragImage:dragImage at:cellFrame.origin offset:offset event:event pasteboard:pboard source:control slideBack:YES];
183 - (void)draggingEnteredTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc
185 [self setDestinationTabBar:control];
186 [self setCurrentMouseLoc:mouseLoc];
188 [[control overflowPopUpButton] setHidden:YES];
189 [[control addTabButton] setHidden:YES];
190 if(![[[control cells] objectAtIndex:0] isPlaceholder])
191 [self distributePlaceholdersInTabBar:control];
192 [_participatingTabBars addObject:control];
195 - (void)draggingUpdatedInTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc
197 if([self destinationTabBar] != control)
198 [self setDestinationTabBar:control];
199 [self setCurrentMouseLoc:mouseLoc];
202 - (void)draggingExitedTabBar:(PSMTabBarControl *)control
204 [self setDestinationTabBar:nil];
205 [self setCurrentMouseLoc:NSMakePoint(-1.0, -1.0)];
208 - (void)performDragOperation
212 [[[self destinationTabBar] cells] replaceObjectAtIndex:[[[self destinationTabBar] cells] indexOfObject:[self targetCell]] withObject:[self draggedCell]];
213 [[self draggedCell] setControlView:[self destinationTabBar]];
214 // move actual NSTabViewItem
215 if([self sourceTabBar] != [self destinationTabBar]){
216 [[[self sourceTabBar] tabView] removeTabViewItem:[[self draggedCell] representedObject]];
217 [[[self destinationTabBar] tabView] addTabViewItem:[[self draggedCell] representedObject]];
221 unsigned idx = [[[self destinationTabBar] cells] indexOfObject:[self targetCell]];
224 [[[self destinationTabBar] cells] replaceObjectAtIndex:idx withObject:[self draggedCell]];
225 [[self draggedCell] setControlView:[self destinationTabBar]];
226 // move actual NSTabViewItem
227 if([self sourceTabBar] != [self destinationTabBar]){
228 [[[self sourceTabBar] tabView] removeTabViewItem:[[self draggedCell] representedObject]];
229 idx = [[[self destinationTabBar] cells] indexOfObject:[self draggedCell]];
230 NSLog(@"Inserting at index %d", idx);
231 [[[self destinationTabBar] tabView] insertTabViewItem:[[self draggedCell] representedObject] atIndex:idx];
237 - (void)draggedImageEndedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
239 if([self isDragging]){ // means there was not a successful drop (performDragOperation)
241 [[[self sourceTabBar] cells] insertObject:[self draggedCell] atIndex:[self draggedCellIndex]];
248 [self setIsDragging:NO];
249 [self removeAllPlaceholdersFromTabBar:[self sourceTabBar]];
250 [self setSourceTabBar:nil];
251 [self setDestinationTabBar:nil];
252 NSEnumerator *e = [_participatingTabBars objectEnumerator];
253 PSMTabBarControl *tabBar;
254 while(tabBar = [e nextObject]){
255 [self removeAllPlaceholdersFromTabBar:tabBar];
257 [_participatingTabBars removeAllObjects];
258 [self setDraggedCell:nil];
259 [_animationTimer invalidate];
260 _animationTimer = nil;
261 [_sineCurveWidths removeAllObjects];
262 [self setTargetCell:nil];
266 #pragma mark Animation
268 - (void)animateDrag:(NSTimer *)timer
270 NSEnumerator *e = [_participatingTabBars objectEnumerator];
271 PSMTabBarControl *tabBar;
272 while(tabBar = [e nextObject]){
273 [self calculateDragAnimationForTabBar:tabBar];
274 [[NSRunLoop currentRunLoop] performSelector:@selector(display) target:tabBar argument:nil order:1 modes:[NSArray arrayWithObjects:@"NSEventTrackingRunLoopMode", @"NSDefaultRunLoopMode", nil]];
278 - (void)calculateDragAnimationForTabBar:(PSMTabBarControl *)control
280 BOOL removeFlag = YES;
281 NSMutableArray *cells = [control cells];
282 int i, cellCount = [cells count];
283 float xPos = [[control style] leftMarginForTabBarControl];
285 // identify target cell
286 // mouse at beginning of tabs
287 NSPoint mouseLoc = [self currentMouseLoc];
288 if([self destinationTabBar] == control){
290 if(mouseLoc.x < [[control style] leftMarginForTabBarControl]){
291 [self setTargetCell:[cells objectAtIndex:0]];
296 PSMTabBarCell *overCell = [control cellForPoint:mouseLoc cellFrame:&overCellRect];
298 // mouse among cells - placeholder
299 if([overCell isPlaceholder]){
300 [self setTargetCell:overCell];
305 if(mouseLoc.x < (overCellRect.origin.x + (overCellRect.size.width / 2.0))){
306 // mouse on left side of cell
307 [self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] - 1)]];
310 // mouse on right side of cell
311 [self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] + 1)]];
315 // out at end - must find proper cell (could be more in overflow menu)
316 [self setTargetCell:[control lastVisibleTab]];
320 [self setTargetCell:nil];
324 for(i = 0; i < cellCount; i++){
325 PSMTabBarCell *cell = [cells objectAtIndex:i];
326 NSRect newRect = [cell frame];
327 if(![cell isInOverflowMenu]){
328 if([cell isPlaceholder]){
329 if(cell == [self targetCell]){
330 [cell setCurrentStep:([cell currentStep] + 1)];
332 [cell setCurrentStep:([cell currentStep] - 1)];
333 if([cell currentStep] > 0){
337 newRect.size.width = [[_sineCurveWidths objectAtIndex:[cell currentStep]] intValue];
342 newRect.origin.x = xPos;
343 [cell setFrame:newRect];
345 [[cell indicator] setFrame:[[control style] indicatorRectForTabCell:cell]];
346 xPos += newRect.size.width;
349 [_participatingTabBars removeObject:control];
350 [self removeAllPlaceholdersFromTabBar:control];
355 #pragma mark Placeholders
357 - (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control withDraggedCell:(PSMTabBarCell *)cell
359 // called upon first drag - must distribute placeholders
360 [self distributePlaceholdersInTabBar:control];
361 // replace dragged cell with a placeholder, and clean up surrounding cells
362 int cellIndex = [[control cells] indexOfObject:cell];
363 PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:YES inControlView:control] autorelease];
364 [[control cells] replaceObjectAtIndex:cellIndex withObject:pc];
365 [[control cells] removeObjectAtIndex:(cellIndex + 1)];
366 [[control cells] removeObjectAtIndex:(cellIndex - 1)];
370 - (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control
372 int i, numVisibleTabs = [control numberOfVisibleTabs];
373 for(i = 0; i < numVisibleTabs; i++){
374 PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control] autorelease];
375 [[control cells] insertObject:pc atIndex:(2 * i)];
377 if(numVisibleTabs > 0){
378 PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control] autorelease];
379 if([[control cells] count] > (2 * numVisibleTabs)){
380 [[control cells] insertObject:pc atIndex:(2 * numVisibleTabs)];
382 [[control cells] addObject:pc];
387 - (void)removeAllPlaceholdersFromTabBar:(PSMTabBarControl *)control
389 int i, cellCount = [[control cells] count];
390 for(i = (cellCount - 1); i >= 0; i--){
391 PSMTabBarCell *cell = [[control cells] objectAtIndex:i];
392 if([cell isPlaceholder])
393 [[control cells] removeObject:cell];
396 [[NSRunLoop currentRunLoop] performSelector:@selector(update) target:control argument:nil order:1 modes:[NSArray arrayWithObjects:@"NSEventTrackingRunLoopMode", @"NSDefaultRunLoopMode", nil]];
397 [[NSRunLoop currentRunLoop] performSelector:@selector(display) target:control argument:nil order:1 modes:[NSArray arrayWithObjects:@"NSEventTrackingRunLoopMode", @"NSDefaultRunLoopMode", nil]];
401 #pragma mark Archiving
403 - (void)encodeWithCoder:(NSCoder *)aCoder {
404 //[super encodeWithCoder:aCoder];
405 if ([aCoder allowsKeyedCoding]) {
406 [aCoder encodeObject:_sourceTabBar forKey:@"sourceTabBar"];
407 [aCoder encodeObject:_destinationTabBar forKey:@"destinationTabBar"];
408 [aCoder encodeObject:_participatingTabBars forKey:@"participatingTabBars"];
409 [aCoder encodeObject:_draggedCell forKey:@"draggedCell"];
410 [aCoder encodeInt:_draggedCellIndex forKey:@"draggedCellIndex"];
411 [aCoder encodeBool:_isDragging forKey:@"isDragging"];
412 [aCoder encodeObject:_animationTimer forKey:@"animationTimer"];
413 [aCoder encodeObject:_sineCurveWidths forKey:@"sineCurveWidths"];
414 [aCoder encodePoint:_currentMouseLoc forKey:@"currentMouseLoc"];
415 [aCoder encodeObject:_targetCell forKey:@"targetCell"];
419 - (id)initWithCoder:(NSCoder *)aDecoder {
420 //self = [super initWithCoder:aDecoder];
422 if ([aDecoder allowsKeyedCoding]) {
423 _sourceTabBar = [[aDecoder decodeObjectForKey:@"sourceTabBar"] retain];
424 _destinationTabBar = [[aDecoder decodeObjectForKey:@"destinationTabBar"] retain];
425 _participatingTabBars = [[aDecoder decodeObjectForKey:@"participatingTabBars"] retain];
426 _draggedCell = [[aDecoder decodeObjectForKey:@"draggedCell"] retain];
427 _draggedCellIndex = [aDecoder decodeIntForKey:@"draggedCellIndex"];
428 _isDragging = [aDecoder decodeBoolForKey:@"isDragging"];
429 _animationTimer = [[aDecoder decodeObjectForKey:@"animationTimer"] retain];
430 _sineCurveWidths = [[aDecoder decodeObjectForKey:@"sineCurveWidths"] retain];
431 _currentMouseLoc = [aDecoder decodePointForKey:@"currentMouseLoc"];
432 _targetCell = [[aDecoder decodeObjectForKey:@"targetCell"] retain];