OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "chrome/browser/cocoa/tab_view.h" | 5 #import "chrome/browser/cocoa/tab_view.h" |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/logging.h" |
8 #include "chrome/browser/cocoa/nsimage_cache.h" | 8 #include "chrome/browser/cocoa/nsimage_cache.h" |
9 #import "chrome/browser/cocoa/tab_controller.h" | 9 #import "chrome/browser/cocoa/tab_controller.h" |
10 #import "chrome/browser/cocoa/tab_window_controller.h" | 10 #import "chrome/browser/cocoa/tab_window_controller.h" |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
42 closeTrackingArea_.reset( | 42 closeTrackingArea_.reset( |
43 [[NSTrackingArea alloc] initWithRect:[closeButton_ bounds] | 43 [[NSTrackingArea alloc] initWithRect:[closeButton_ bounds] |
44 options:NSTrackingMouseEnteredAndExited | | 44 options:NSTrackingMouseEnteredAndExited | |
45 NSTrackingActiveAlways | 45 NSTrackingActiveAlways |
46 owner:self | 46 owner:self |
47 userInfo:nil]); | 47 userInfo:nil]); |
48 [closeButton_ addTrackingArea:closeTrackingArea_.get()]; | 48 [closeButton_ addTrackingArea:closeTrackingArea_.get()]; |
49 } | 49 } |
50 | 50 |
51 - (void)dealloc { | 51 - (void)dealloc { |
| 52 // Cancel any delayed requests that may still be pending (drags or hover). |
| 53 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
52 // [self gtm_unregisterForThemeNotifications]; | 54 // [self gtm_unregisterForThemeNotifications]; |
53 [closeButton_ removeTrackingArea:closeTrackingArea_.get()]; | 55 [closeButton_ removeTrackingArea:closeTrackingArea_.get()]; |
54 [super dealloc]; | 56 [super dealloc]; |
55 } | 57 } |
56 | 58 |
57 // Overridden so that mouse clicks come to this view (the parent of the | 59 // Overridden so that mouse clicks come to this view (the parent of the |
58 // hierarchy) first. We want to handle clicks and drags in this class and | 60 // hierarchy) first. We want to handle clicks and drags in this class and |
59 // leave the background button for display purposes only. | 61 // leave the background button for display purposes only. |
60 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { | 62 - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { |
61 return YES; | 63 return YES; |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
157 TabWindowController* realController = | 159 TabWindowController* realController = |
158 static_cast<TabWindowController*>(controller); | 160 static_cast<TabWindowController*>(controller); |
159 if ([realController canReceiveFrom:dragController]) { | 161 if ([realController canReceiveFrom:dragController]) { |
160 [targets addObject:controller]; | 162 [targets addObject:controller]; |
161 } | 163 } |
162 } | 164 } |
163 } | 165 } |
164 return targets; | 166 return targets; |
165 } | 167 } |
166 | 168 |
| 169 // Call to clear out transient weak references we hold during drags. |
| 170 - (void)resetDragControllers { |
| 171 draggedController_ = nil; |
| 172 dragWindow_ = nil; |
| 173 dragOverlay_ = nil; |
| 174 sourceController_ = nil; |
| 175 sourceWindow_ = nil; |
| 176 targetController_ = nil; |
| 177 } |
| 178 |
167 // Handle clicks and drags in this button. We get here because we have | 179 // Handle clicks and drags in this button. We get here because we have |
168 // overridden acceptsFirstMouse: and the click is within our bounds. | 180 // overridden acceptsFirstMouse: and the click is within our bounds. |
169 // TODO(pinkerton/alcor): This routine needs *a lot* of work to marry Cole's | 181 // TODO(pinkerton/alcor): This routine needs *a lot* of work to marry Cole's |
170 // ideas of dragging cocoa views between windows and how the Browser and | 182 // ideas of dragging cocoa views between windows and how the Browser and |
171 // TabStrip models want to manage tabs. | 183 // TabStrip models want to manage tabs. |
172 | 184 |
173 static const CGFloat kTearDistance = 36.0; | 185 static const CGFloat kTearDistance = 36.0; |
174 static const NSTimeInterval kTearDuration = 0.333; | 186 static const NSTimeInterval kTearDuration = 0.333; |
175 | 187 |
176 // This is used to judge whether the mouse has moved during rapid closure; if it | 188 // This is used to judge whether the mouse has moved during rapid closure; if it |
(...skipping 14 matching lines...) Expand all Loading... |
191 [closeButton_ mouseDown:theEvent]; | 203 [closeButton_ mouseDown:theEvent]; |
192 return; | 204 return; |
193 } | 205 } |
194 } | 206 } |
195 | 207 |
196 // Fire the action to select the tab. | 208 // Fire the action to select the tab. |
197 if ([[controller_ target] respondsToSelector:[controller_ action]]) | 209 if ([[controller_ target] respondsToSelector:[controller_ action]]) |
198 [[controller_ target] performSelector:[controller_ action] | 210 [[controller_ target] performSelector:[controller_ action] |
199 withObject:self]; | 211 withObject:self]; |
200 | 212 |
| 213 [self resetDragControllers]; |
| 214 |
201 // Resolve overlay back to original window. | 215 // Resolve overlay back to original window. |
202 sourceWindow_ = [self window]; | 216 sourceWindow_ = [self window]; |
203 if ([sourceWindow_ isKindOfClass:[NSPanel class]]) { | 217 if ([sourceWindow_ isKindOfClass:[NSPanel class]]) { |
204 sourceWindow_ = [sourceWindow_ parentWindow]; | 218 sourceWindow_ = [sourceWindow_ parentWindow]; |
205 } | 219 } |
206 | 220 |
207 sourceWindowFrame_ = [sourceWindow_ frame]; | 221 sourceWindowFrame_ = [sourceWindow_ frame]; |
208 sourceTabFrame_ = [self frame]; | 222 sourceTabFrame_ = [self frame]; |
209 sourceController_ = [sourceWindow_ windowController]; | 223 sourceController_ = [sourceWindow_ windowController]; |
210 draggedController_ = nil; | |
211 dragWindow_ = nil; | |
212 dragOverlay_ = nil; | |
213 targetController_ = nil; | |
214 tabWasDragged_ = NO; | 224 tabWasDragged_ = NO; |
215 tearTime_ = 0.0; | 225 tearTime_ = 0.0; |
216 draggingWithinTabStrip_ = YES; | 226 draggingWithinTabStrip_ = YES; |
217 | 227 |
218 // If there's more than one potential window to be a drop target, we want to | 228 // If there's more than one potential window to be a drop target, we want to |
219 // treat a drag of a tab just like dragging around a tab that's already | 229 // treat a drag of a tab just like dragging around a tab that's already |
220 // detached. Note that unit tests might have |-numberOfTabs| reporting zero | 230 // detached. Note that unit tests might have |-numberOfTabs| reporting zero |
221 // since the model won't be fully hooked up. We need to be prepared for that | 231 // since the model won't be fully hooked up. We need to be prepared for that |
222 // and not send them into the "magnetic" codepath. | 232 // and not send them into the "magnetic" codepath. |
223 NSArray* targets = [self dropTargetsForController:sourceController_]; | 233 NSArray* targets = [self dropTargetsForController:sourceController_]; |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
371 [draggedController_ showOverlay]; | 381 [draggedController_ showOverlay]; |
372 dragOverlay_ = [draggedController_ overlayWindow]; | 382 dragOverlay_ = [draggedController_ overlayWindow]; |
373 // Force the new tab button to be hidden. We'll reset it on mouse up. | 383 // Force the new tab button to be hidden. We'll reset it on mouse up. |
374 [draggedController_ showNewTabButton:NO]; | 384 [draggedController_ showNewTabButton:NO]; |
375 //if (![targets count]) | 385 //if (![targets count]) |
376 // [dragOverlay_ setHasShadow:NO]; | 386 // [dragOverlay_ setHasShadow:NO]; |
377 tearTime_ = [NSDate timeIntervalSinceReferenceDate]; | 387 tearTime_ = [NSDate timeIntervalSinceReferenceDate]; |
378 tearOrigin_ = sourceWindowFrame_.origin; | 388 tearOrigin_ = sourceWindowFrame_.origin; |
379 } | 389 } |
380 | 390 |
| 391 DCHECK(draggedController_); |
| 392 DCHECK(sourceController_); |
| 393 |
| 394 // When the user first tears off the window, we want slide the window to |
| 395 // the current mouse location (to reduce the jarring appearance). We do this |
| 396 // by calling ourselves back with additional mouseDragged calls (not actual |
| 397 // events). |tearProgress| is a normalized measure of how far through this |
| 398 // tear "animation" (of length kTearDuration) we are and has values [0..1]. |
| 399 // We use sqrt() so the animation is non-linear (slow down near the end |
| 400 // point). |
381 float tearProgress = [NSDate timeIntervalSinceReferenceDate] - tearTime_; | 401 float tearProgress = [NSDate timeIntervalSinceReferenceDate] - tearTime_; |
382 tearProgress /= kTearDuration; | 402 tearProgress /= kTearDuration; // Normalize. |
383 tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0)); | 403 tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0)); |
384 | 404 |
385 // Move the dragged window to the right place on the screen. | 405 // Move the dragged window to the right place on the screen. |
386 NSPoint origin = sourceWindowFrame_.origin; | 406 NSPoint origin = sourceWindowFrame_.origin; |
387 origin.x += (thisPoint.x - dragOrigin_.x); | 407 origin.x += (thisPoint.x - dragOrigin_.x); |
388 origin.y += (thisPoint.y - dragOrigin_.y); | 408 origin.y += (thisPoint.y - dragOrigin_.y); |
389 | 409 |
390 if (tearProgress < 1) { | 410 if (tearProgress < 1) { |
391 // If the tear animation is not complete, call back to ourself with the | 411 // If the tear animation is not complete, call back to ourself with the |
392 // same event to animate even if the mouse isn't moving. | 412 // same event to animate even if the mouse isn't moving. We need to make |
| 413 // sure these get cancelled in mouseUp:. |
393 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 414 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
394 [self performSelector:@selector(mouseDragged:) | 415 [self performSelector:@selector(mouseDragged:) |
395 withObject:theEvent | 416 withObject:theEvent |
396 afterDelay:1.0f/30.0f]; | 417 afterDelay:1.0f/30.0f]; |
397 | 418 |
398 origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x; | 419 // Set the current window origin based on how far we've progressed through |
| 420 // the tear animation. |
| 421 origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x; |
399 origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y; | 422 origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y; |
400 } | 423 } |
401 | 424 |
402 if (targetController_) { | 425 if (targetController_) { |
403 // In order to "snap" two windows of different sizes together at their | 426 // In order to "snap" two windows of different sizes together at their |
404 // toolbar, we can't just use the origin of the target frame. We also have | 427 // toolbar, we can't just use the origin of the target frame. We also have |
405 // to take into consideration the difference in height. | 428 // to take into consideration the difference in height. |
406 NSRect targetFrame = [[targetController_ window] frame]; | 429 NSRect targetFrame = [[targetController_ window] frame]; |
407 NSRect sourceFrame = [dragWindow_ frame]; | 430 NSRect sourceFrame = [dragWindow_ frame]; |
408 origin.y = NSMinY(targetFrame) + | 431 origin.y = NSMinY(targetFrame) + |
(...skipping 27 matching lines...) Expand all Loading... |
436 [targetController_ insertPlaceholderForTab:self | 459 [targetController_ insertPlaceholderForTab:self |
437 frame:tabFrame | 460 frame:tabFrame |
438 yStretchiness:0]; | 461 yStretchiness:0]; |
439 [targetController_ layoutTabs]; | 462 [targetController_ layoutTabs]; |
440 } else { | 463 } else { |
441 [dragWindow_ makeKeyAndOrderFront:nil]; | 464 [dragWindow_ makeKeyAndOrderFront:nil]; |
442 } | 465 } |
443 BOOL chromeShouldBeVisible = targetController_ == nil; | 466 BOOL chromeShouldBeVisible = targetController_ == nil; |
444 | 467 |
445 if (chromeIsVisible_ != chromeShouldBeVisible) { | 468 if (chromeIsVisible_ != chromeShouldBeVisible) { |
| 469 // TODO(pinkerton): There appears to be a race-condition in CoreAnimation |
| 470 // where if we use animators to set the alpha values, we can't guarantee |
| 471 // that we cancel them. This has the side effect of sometimes leaving |
| 472 // the dragged window translucent or invisible. We should re-visit this, |
| 473 // but for now, don't animate the alpha change. |
446 [dragWindow_ setHasShadow:YES]; | 474 [dragWindow_ setHasShadow:YES]; |
| 475 [[draggedController_ overlayWindow] setAlphaValue:1.0]; |
447 if (targetController_) { | 476 if (targetController_) { |
448 [NSAnimationContext beginGrouping]; | 477 [dragWindow_ setAlphaValue:0.0]; |
449 [[NSAnimationContext currentContext] setDuration:0.00001]; | |
450 [[dragWindow_ animator] setAlphaValue:0.0]; | |
451 [NSAnimationContext endGrouping]; | |
452 [[draggedController_ overlayWindow] setHasShadow:YES]; | 478 [[draggedController_ overlayWindow] setHasShadow:YES]; |
453 [[[draggedController_ overlayWindow] animator] setAlphaValue:1.0]; | |
454 } else { | 479 } else { |
455 [[draggedController_ overlayWindow] setAlphaValue:1.0]; | 480 [dragWindow_ setAlphaValue:0.5]; |
456 [[draggedController_ overlayWindow] setHasShadow:NO]; | 481 [[draggedController_ overlayWindow] setHasShadow:NO]; |
457 [[dragWindow_ animator] setAlphaValue:0.5]; | |
458 } | 482 } |
459 chromeIsVisible_ = chromeShouldBeVisible; | 483 chromeIsVisible_ = chromeShouldBeVisible; |
460 } | 484 } |
461 } | 485 } |
462 | 486 |
463 - (void)mouseUp:(NSEvent *)theEvent { | 487 - (void)mouseUp:(NSEvent *)theEvent { |
464 // The drag/click is done. If the user dragged the mouse, finalize the drag | 488 // The drag/click is done. If the user dragged the mouse, finalize the drag |
465 // and clean up. | 489 // and clean up. |
466 | 490 |
467 // Special-case this to keep the logic below simpler. | 491 // Special-case this to keep the logic below simpler. |
468 if (moveWindowOnDrag_) | 492 if (moveWindowOnDrag_) |
469 return; | 493 return; |
470 | 494 |
| 495 // Cancel any delayed -mouseDragged: requests that may still be pending. |
| 496 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
| 497 |
471 // We are now free to re-display the new tab button in the window we're | 498 // We are now free to re-display the new tab button in the window we're |
472 // dragging. It will show when the next call to -layoutTabs (which happens | 499 // dragging. It will show when the next call to -layoutTabs (which happens |
473 // indrectly by several of the calls below, such as removing the placeholder). | 500 // indrectly by several of the calls below, such as removing the placeholder). |
474 [draggedController_ showNewTabButton:YES]; | 501 [draggedController_ showNewTabButton:YES]; |
475 | 502 |
476 if (draggingWithinTabStrip_) { | 503 if (draggingWithinTabStrip_) { |
477 if (tabWasDragged_) { | 504 if (tabWasDragged_) { |
478 // Move tab to new location. | 505 // Move tab to new location. |
479 DCHECK([sourceController_ numberOfTabs]); | 506 DCHECK([sourceController_ numberOfTabs]); |
480 TabWindowController* dropController = sourceController_; | 507 TabWindowController* dropController = sourceController_; |
481 [dropController moveTabView:[dropController selectedTabView] | 508 [dropController moveTabView:[dropController selectedTabView] |
482 fromController:nil]; | 509 fromController:nil]; |
483 } | 510 } |
484 } else if (targetController_) { | 511 } else if (targetController_) { |
485 // Move between windows. If |targetController_| is nil, we're not dropping | 512 // Move between windows. If |targetController_| is nil, we're not dropping |
486 // into any existing window. | 513 // into any existing window. |
487 NSView* draggedTabView = [draggedController_ selectedTabView]; | 514 NSView* draggedTabView = [draggedController_ selectedTabView]; |
488 [draggedController_ removeOverlay]; | 515 [draggedController_ removeOverlay]; |
489 [targetController_ moveTabView:draggedTabView | 516 [targetController_ moveTabView:draggedTabView |
490 fromController:draggedController_]; | 517 fromController:draggedController_]; |
491 [targetController_ showWindow:nil]; | 518 [targetController_ showWindow:nil]; |
492 } else { | 519 } else { |
493 // Tab dragging did move window only. | 520 // Only move the window around on screen. Make sure it's set back to |
| 521 // normal state (fully opaque, has shadow, has key, etc). |
494 [draggedController_ removeOverlay]; | 522 [draggedController_ removeOverlay]; |
495 // Don't want to re-show the window if it was closed during the drag. | 523 // Don't want to re-show the window if it was closed during the drag. |
496 if ([dragWindow_ isVisible]) { | 524 if ([dragWindow_ isVisible]) { |
497 [dragWindow_ setAlphaValue:1.0]; | 525 [dragWindow_ setAlphaValue:1.0]; |
498 [dragOverlay_ setHasShadow:NO]; | 526 [dragOverlay_ setHasShadow:NO]; |
499 [dragWindow_ setHasShadow:YES]; | 527 [dragWindow_ setHasShadow:YES]; |
500 [dragWindow_ makeKeyAndOrderFront:nil]; | 528 [dragWindow_ makeKeyAndOrderFront:nil]; |
501 } | 529 } |
502 [[draggedController_ window] setLevel:NSNormalWindowLevel]; | 530 [[draggedController_ window] setLevel:NSNormalWindowLevel]; |
503 [draggedController_ removePlaceholder]; | 531 [draggedController_ removePlaceholder]; |
504 } | 532 } |
505 [sourceController_ removePlaceholder]; | 533 [sourceController_ removePlaceholder]; |
506 chromeIsVisible_ = YES; | 534 chromeIsVisible_ = YES; |
| 535 |
| 536 [self resetDragControllers]; |
507 } | 537 } |
508 | 538 |
509 - (void)otherMouseUp:(NSEvent*)theEvent { | 539 - (void)otherMouseUp:(NSEvent*)theEvent { |
510 // Support middle-click-to-close. | 540 // Support middle-click-to-close. |
511 if ([theEvent buttonNumber] == 2) { | 541 if ([theEvent buttonNumber] == 2) { |
512 [controller_ closeTab:self]; | 542 [controller_ closeTab:self]; |
513 } | 543 } |
514 } | 544 } |
515 | 545 |
516 - (NSPoint)patternPhase { | 546 - (NSPoint)patternPhase { |
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
673 [[NSGraphicsContext currentContext] restoreGraphicsState]; | 703 [[NSGraphicsContext currentContext] restoreGraphicsState]; |
674 } | 704 } |
675 | 705 |
676 // Called when the user hits the right mouse button (or control-clicks) to | 706 // Called when the user hits the right mouse button (or control-clicks) to |
677 // show a context menu. | 707 // show a context menu. |
678 - (void)rightMouseDown:(NSEvent*)theEvent { | 708 - (void)rightMouseDown:(NSEvent*)theEvent { |
679 [NSMenu popUpContextMenu:[self menu] withEvent:theEvent forView:self]; | 709 [NSMenu popUpContextMenu:[self menu] withEvent:theEvent forView:self]; |
680 } | 710 } |
681 | 711 |
682 @end | 712 @end |
OLD | NEW |