| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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/ui/cocoa/tabs/tab_view.h" | 5 #import "chrome/browser/ui/cocoa/tabs/tab_view.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #import "base/mac/mac_util.h" | |
| 9 #include "base/mac/scoped_cftyperef.h" | |
| 10 #include "chrome/browser/themes/theme_service.h" | 8 #include "chrome/browser/themes/theme_service.h" |
| 11 #import "chrome/browser/ui/cocoa/nsview_additions.h" | 9 #import "chrome/browser/ui/cocoa/nsview_additions.h" |
| 12 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h" | 10 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h" |
| 13 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h" | 11 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h" |
| 14 #import "chrome/browser/ui/cocoa/themed_window.h" | 12 #import "chrome/browser/ui/cocoa/themed_window.h" |
| 15 #import "chrome/browser/ui/cocoa/view_id_util.h" | 13 #import "chrome/browser/ui/cocoa/view_id_util.h" |
| 16 #include "grit/generated_resources.h" | 14 #include "grit/generated_resources.h" |
| 17 #include "grit/theme_resources.h" | 15 #include "grit/theme_resources.h" |
| 18 #include "grit/theme_resources_standard.h" | 16 #include "grit/theme_resources_standard.h" |
| 19 #include "ui/base/l10n/l10n_util.h" | 17 #include "ui/base/l10n/l10n_util.h" |
| (...skipping 12 matching lines...) Expand all Loading... |
| 32 const NSTimeInterval kHoverHoldDuration = 0.02; | 30 const NSTimeInterval kHoverHoldDuration = 0.02; |
| 33 const NSTimeInterval kHoverHideDuration = 0.4; | 31 const NSTimeInterval kHoverHideDuration = 0.4; |
| 34 const NSTimeInterval kAlertShowDuration = 0.4; | 32 const NSTimeInterval kAlertShowDuration = 0.4; |
| 35 const NSTimeInterval kAlertHoldDuration = 0.4; | 33 const NSTimeInterval kAlertHoldDuration = 0.4; |
| 36 const NSTimeInterval kAlertHideDuration = 0.4; | 34 const NSTimeInterval kAlertHideDuration = 0.4; |
| 37 | 35 |
| 38 // The default time interval in seconds between glow updates (when | 36 // The default time interval in seconds between glow updates (when |
| 39 // increasing/decreasing). | 37 // increasing/decreasing). |
| 40 const NSTimeInterval kGlowUpdateInterval = 0.025; | 38 const NSTimeInterval kGlowUpdateInterval = 0.025; |
| 41 | 39 |
| 42 const CGFloat kTearDistance = 36.0; | |
| 43 const NSTimeInterval kTearDuration = 0.333; | |
| 44 | |
| 45 // This is used to judge whether the mouse has moved during rapid closure; if it | 40 // This is used to judge whether the mouse has moved during rapid closure; if it |
| 46 // has moved less than the threshold, we want to close the tab. | 41 // has moved less than the threshold, we want to close the tab. |
| 47 const CGFloat kRapidCloseDist = 2.5; | 42 const CGFloat kRapidCloseDist = 2.5; |
| 48 | 43 |
| 49 } // namespace | 44 } // namespace |
| 50 | 45 |
| 51 @interface TabView(Private) | 46 @interface TabView(Private) |
| 52 | 47 |
| 53 - (void)resetLastGlowUpdateTime; | 48 - (void)resetLastGlowUpdateTime; |
| 54 - (NSTimeInterval)timeElapsedSinceLastGlowUpdate; | 49 - (NSTimeInterval)timeElapsedSinceLastGlowUpdate; |
| 55 - (void)adjustGlowValue; | 50 - (void)adjustGlowValue; |
| 56 // TODO(davidben): When we stop supporting 10.5, this can be removed. | |
| 57 - (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache; | |
| 58 - (NSBezierPath*)bezierPathForRect:(NSRect)rect; | 51 - (NSBezierPath*)bezierPathForRect:(NSRect)rect; |
| 59 | 52 |
| 60 @end // TabView(Private) | 53 @end // TabView(Private) |
| 61 | 54 |
| 62 @implementation TabView | 55 @implementation TabView |
| 63 | 56 |
| 64 @synthesize state = state_; | 57 @synthesize state = state_; |
| 65 @synthesize hoverAlpha = hoverAlpha_; | 58 @synthesize hoverAlpha = hoverAlpha_; |
| 66 @synthesize alertAlpha = alertAlpha_; | 59 @synthesize alertAlpha = alertAlpha_; |
| 67 @synthesize closing = closing_; | 60 @synthesize closing = closing_; |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 146 // works well enough. | 139 // works well enough. |
| 147 NSRect hitRect = NSInsetRect(frame, frame.size.height / 3.0f, 0); | 140 NSRect hitRect = NSInsetRect(frame, frame.size.height / 3.0f, 0); |
| 148 if (![closeButton_ isHidden]) | 141 if (![closeButton_ isHidden]) |
| 149 if (NSPointInRect(viewPoint, [closeButton_ frame])) return closeButton_; | 142 if (NSPointInRect(viewPoint, [closeButton_ frame])) return closeButton_; |
| 150 if (NSPointInRect(aPoint, hitRect)) return self; | 143 if (NSPointInRect(aPoint, hitRect)) return self; |
| 151 return nil; | 144 return nil; |
| 152 } | 145 } |
| 153 | 146 |
| 154 // Returns |YES| if this tab can be torn away into a new window. | 147 // Returns |YES| if this tab can be torn away into a new window. |
| 155 - (BOOL)canBeDragged { | 148 - (BOOL)canBeDragged { |
| 156 if ([self isClosing]) | 149 return [controller_ tabCanBeDragged:controller_]; |
| 157 return NO; | |
| 158 NSWindowController* controller = [sourceWindow_ windowController]; | |
| 159 if ([controller isKindOfClass:[TabWindowController class]]) { | |
| 160 TabWindowController* realController = | |
| 161 static_cast<TabWindowController*>(controller); | |
| 162 return [realController isTabDraggable:self]; | |
| 163 } | |
| 164 return YES; | |
| 165 } | |
| 166 | |
| 167 // Returns an array of controllers that could be a drop target, ordered front to | |
| 168 // back. It has to be of the appropriate class, and visible (obviously). Note | |
| 169 // that the window cannot be a target for itself. | |
| 170 - (NSArray*)dropTargetsForController:(TabWindowController*)dragController { | |
| 171 NSMutableArray* targets = [NSMutableArray array]; | |
| 172 NSWindow* dragWindow = [dragController window]; | |
| 173 for (NSWindow* window in [NSApp orderedWindows]) { | |
| 174 if (window == dragWindow) continue; | |
| 175 if (![window isVisible]) continue; | |
| 176 // Skip windows on the wrong space. | |
| 177 if ([window respondsToSelector:@selector(isOnActiveSpace)]) { | |
| 178 if (![window performSelector:@selector(isOnActiveSpace)]) | |
| 179 continue; | |
| 180 } else { | |
| 181 // TODO(davidben): When we stop supporting 10.5, this can be | |
| 182 // removed. | |
| 183 // | |
| 184 // We don't cache the workspace of |dragWindow| because it may | |
| 185 // move around spaces. | |
| 186 if ([self getWorkspaceID:dragWindow useCache:NO] != | |
| 187 [self getWorkspaceID:window useCache:YES]) | |
| 188 continue; | |
| 189 } | |
| 190 NSWindowController* controller = [window windowController]; | |
| 191 if ([controller isKindOfClass:[TabWindowController class]]) { | |
| 192 TabWindowController* realController = | |
| 193 static_cast<TabWindowController*>(controller); | |
| 194 if ([realController canReceiveFrom:dragController]) | |
| 195 [targets addObject:controller]; | |
| 196 } | |
| 197 } | |
| 198 return targets; | |
| 199 } | |
| 200 | |
| 201 // Call to clear out transient weak references we hold during drags. | |
| 202 - (void)resetDragControllers { | |
| 203 draggedController_ = nil; | |
| 204 dragWindow_ = nil; | |
| 205 dragOverlay_ = nil; | |
| 206 sourceController_ = nil; | |
| 207 sourceWindow_ = nil; | |
| 208 targetController_ = nil; | |
| 209 workspaceIDCache_.clear(); | |
| 210 } | |
| 211 | |
| 212 // Sets whether the window background should be visible or invisible when | |
| 213 // dragging a tab. The background should be invisible when the mouse is over a | |
| 214 // potential drop target for the tab (the tab strip). It should be visible when | |
| 215 // there's no drop target so the window looks more fully realized and ready to | |
| 216 // become a stand-alone window. | |
| 217 - (void)setWindowBackgroundVisibility:(BOOL)shouldBeVisible { | |
| 218 if (chromeIsVisible_ == shouldBeVisible) | |
| 219 return; | |
| 220 | |
| 221 // There appears to be a race-condition in CoreAnimation where if we use | |
| 222 // animators to set the alpha values, we can't guarantee that we cancel them. | |
| 223 // This has the side effect of sometimes leaving the dragged window | |
| 224 // translucent or invisible. As a result, don't animate the alpha change. | |
| 225 [[draggedController_ overlayWindow] setAlphaValue:1.0]; | |
| 226 if (targetController_) { | |
| 227 [dragWindow_ setAlphaValue:0.0]; | |
| 228 [[draggedController_ overlayWindow] setHasShadow:YES]; | |
| 229 [[targetController_ window] makeMainWindow]; | |
| 230 } else { | |
| 231 [dragWindow_ setAlphaValue:0.5]; | |
| 232 [[draggedController_ overlayWindow] setHasShadow:NO]; | |
| 233 [[draggedController_ window] makeMainWindow]; | |
| 234 } | |
| 235 chromeIsVisible_ = shouldBeVisible; | |
| 236 } | 150 } |
| 237 | 151 |
| 238 // Handle clicks and drags in this button. We get here because we have | 152 // Handle clicks and drags in this button. We get here because we have |
| 239 // overridden acceptsFirstMouse: and the click is within our bounds. | 153 // overridden acceptsFirstMouse: and the click is within our bounds. |
| 240 - (void)mouseDown:(NSEvent*)theEvent { | 154 - (void)mouseDown:(NSEvent*)theEvent { |
| 241 if ([self isClosing]) | 155 if ([self isClosing]) |
| 242 return; | 156 return; |
| 243 | 157 |
| 244 NSPoint downLocation = [theEvent locationInWindow]; | 158 // Record the point at which this event happened. This is used by other mouse |
| 159 // events that are dispatched from |-maybeStartDrag::|. |
| 160 mouseDownPoint_ = [theEvent locationInWindow]; |
| 245 | 161 |
| 246 // Record the state of the close button here, because selecting the tab will | 162 // Record the state of the close button here, because selecting the tab will |
| 247 // unhide it. | 163 // unhide it. |
| 248 BOOL closeButtonActive = [closeButton_ isHidden] ? NO : YES; | 164 BOOL closeButtonActive = ![closeButton_ isHidden]; |
| 249 | 165 |
| 250 // During the tab closure animation (in particular, during rapid tab closure), | 166 // During the tab closure animation (in particular, during rapid tab closure), |
| 251 // we may get incorrectly hit with a mouse down. If it should have gone to the | 167 // we may get incorrectly hit with a mouse down. If it should have gone to the |
| 252 // close button, we send it there -- it should then track the mouse, so we | 168 // close button, we send it there -- it should then track the mouse, so we |
| 253 // don't have to worry about mouse ups. | 169 // don't have to worry about mouse ups. |
| 254 if (closeButtonActive && [controller_ inRapidClosureMode]) { | 170 if (closeButtonActive && [controller_ inRapidClosureMode]) { |
| 255 NSPoint hitLocation = [[self superview] convertPoint:downLocation | 171 NSPoint hitLocation = [[self superview] convertPoint:mouseDownPoint_ |
| 256 fromView:nil]; | 172 fromView:nil]; |
| 257 if ([self hitTest:hitLocation] == closeButton_) { | 173 if ([self hitTest:hitLocation] == closeButton_) { |
| 258 [closeButton_ mouseDown:theEvent]; | 174 [closeButton_ mouseDown:theEvent]; |
| 259 return; | 175 return; |
| 260 } | 176 } |
| 261 } | 177 } |
| 262 | 178 |
| 263 [self resetDragControllers]; | |
| 264 | |
| 265 // Resolve overlay back to original window. | |
| 266 sourceWindow_ = [self window]; | |
| 267 if ([sourceWindow_ isKindOfClass:[NSPanel class]]) { | |
| 268 sourceWindow_ = [sourceWindow_ parentWindow]; | |
| 269 } | |
| 270 | |
| 271 sourceWindowFrame_ = [sourceWindow_ frame]; | |
| 272 sourceTabFrame_ = [self frame]; | |
| 273 sourceController_ = [sourceWindow_ windowController]; | |
| 274 tabWasDragged_ = NO; | |
| 275 tearTime_ = 0.0; | |
| 276 draggingWithinTabStrip_ = YES; | |
| 277 chromeIsVisible_ = NO; | |
| 278 | |
| 279 // If there's more than one potential window to be a drop target, we want to | |
| 280 // treat a drag of a tab just like dragging around a tab that's already | |
| 281 // detached. Note that unit tests might have |-numberOfTabs| reporting zero | |
| 282 // since the model won't be fully hooked up. We need to be prepared for that | |
| 283 // and not send them into the "magnetic" codepath. | |
| 284 NSArray* targets = [self dropTargetsForController:sourceController_]; | |
| 285 moveWindowOnDrag_ = | |
| 286 ([sourceController_ numberOfTabs] < 2 && ![targets count]) || | |
| 287 ![self canBeDragged] || | |
| 288 ![sourceController_ tabDraggingAllowed]; | |
| 289 // If we are dragging a tab, a window with a single tab should immediately | |
| 290 // snap off and not drag within the tab strip. | |
| 291 if (!moveWindowOnDrag_) | |
| 292 draggingWithinTabStrip_ = [sourceController_ numberOfTabs] > 1; | |
| 293 | |
| 294 dragOrigin_ = [NSEvent mouseLocation]; | |
| 295 | |
| 296 // If the tab gets torn off, the tab controller will be removed from the tab | 179 // If the tab gets torn off, the tab controller will be removed from the tab |
| 297 // strip and then deallocated. This will also result in *us* being | 180 // strip and then deallocated. This will also result in *us* being |
| 298 // deallocated. Both these are bad, so we prevent this by retaining the | 181 // deallocated. Both these are bad, so we prevent this by retaining the |
| 299 // controller. | 182 // controller. |
| 300 scoped_nsobject<TabController> controller([controller_ retain]); | 183 scoped_nsobject<TabController> controller([controller_ retain]); |
| 301 | 184 |
| 302 // Because we move views between windows, we need to handle the event loop | 185 // Try to initiate a drag. This will spin a custom event loop and may |
| 303 // ourselves. Ideally we should use the standard event loop. | 186 // dispatch other mouse events. |
| 304 while (1) { | 187 [controller_ maybeStartDrag:theEvent forTab:controller]; |
| 305 const NSUInteger mask = | |
| 306 NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSKeyUpMask; | |
| 307 theEvent = | |
| 308 [NSApp nextEventMatchingMask:mask | |
| 309 untilDate:[NSDate distantFuture] | |
| 310 inMode:NSDefaultRunLoopMode dequeue:YES]; | |
| 311 NSEventType type = [theEvent type]; | |
| 312 if (type == NSKeyUp) { | |
| 313 if ([theEvent keyCode] == kVK_Escape) { | |
| 314 // Cancel the drag and restore the previous state. | |
| 315 if (draggingWithinTabStrip_) { | |
| 316 // Simply pretend the tab wasn't dragged (far enough). | |
| 317 tabWasDragged_ = NO; | |
| 318 } else { | |
| 319 [targetController_ removePlaceholder]; | |
| 320 if ([sourceController_ numberOfTabs] < 2) { | |
| 321 // Revert to a single-tab window. | |
| 322 targetController_ = nil; | |
| 323 } else { | |
| 324 // Change the target to the source controller. | |
| 325 targetController_ = sourceController_; | |
| 326 [targetController_ insertPlaceholderForTab:self | |
| 327 frame:sourceTabFrame_ | |
| 328 yStretchiness:0]; | |
| 329 } | |
| 330 } | |
| 331 // Call the |mouseUp:| code to end the drag. | |
| 332 [self mouseUp:theEvent]; | |
| 333 break; | |
| 334 } | |
| 335 } else if (type == NSLeftMouseDragged) { | |
| 336 [self mouseDragged:theEvent]; | |
| 337 } else if (type == NSLeftMouseUp) { | |
| 338 NSPoint upLocation = [theEvent locationInWindow]; | |
| 339 CGFloat dx = upLocation.x - downLocation.x; | |
| 340 CGFloat dy = upLocation.y - downLocation.y; | |
| 341 | 188 |
| 342 // During rapid tab closure (mashing tab close buttons), we may get hit | 189 // The custom loop has ended, so clear the point. |
| 343 // with a mouse down. As long as the mouse up is over the close button, | 190 mouseDownPoint_ = NSZeroPoint; |
| 344 // and the mouse hasn't moved too much, we close the tab. | |
| 345 if (closeButtonActive && | |
| 346 (dx*dx + dy*dy) <= kRapidCloseDist*kRapidCloseDist && | |
| 347 [controller inRapidClosureMode]) { | |
| 348 NSPoint hitLocation = | |
| 349 [[self superview] convertPoint:[theEvent locationInWindow] | |
| 350 fromView:nil]; | |
| 351 if ([self hitTest:hitLocation] == closeButton_) { | |
| 352 [controller closeTab:self]; | |
| 353 break; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 [self mouseUp:theEvent]; | |
| 358 break; | |
| 359 } else { | |
| 360 // TODO(viettrungluu): [crbug.com/23830] We can receive right-mouse-ups | |
| 361 // (and maybe even others?) for reasons I don't understand. So we | |
| 362 // explicitly check for both events we're expecting, and log others. We | |
| 363 // should figure out what's going on. | |
| 364 LOG(WARNING) << "Spurious event received of type " << type << "."; | |
| 365 } | |
| 366 } | |
| 367 } | 191 } |
| 368 | 192 |
| 369 - (void)mouseDragged:(NSEvent*)theEvent { | 193 - (void)mouseDragged:(NSEvent*)theEvent { |
| 370 // Special-case this to keep the logic below simpler. | 194 [controller_ continueDrag:theEvent]; |
| 371 if (moveWindowOnDrag_) { | |
| 372 if ([sourceController_ windowMovementAllowed]) { | |
| 373 NSPoint thisPoint = [NSEvent mouseLocation]; | |
| 374 NSPoint origin = sourceWindowFrame_.origin; | |
| 375 origin.x += (thisPoint.x - dragOrigin_.x); | |
| 376 origin.y += (thisPoint.y - dragOrigin_.y); | |
| 377 [sourceWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; | |
| 378 } // else do nothing. | |
| 379 return; | |
| 380 } | |
| 381 | |
| 382 // First, go through the magnetic drag cycle. We break out of this if | |
| 383 // "stretchiness" ever exceeds a set amount. | |
| 384 tabWasDragged_ = YES; | |
| 385 | |
| 386 if (draggingWithinTabStrip_) { | |
| 387 NSPoint thisPoint = [NSEvent mouseLocation]; | |
| 388 CGFloat stretchiness = thisPoint.y - dragOrigin_.y; | |
| 389 stretchiness = copysign(sqrtf(fabs(stretchiness))/sqrtf(kTearDistance), | |
| 390 stretchiness) / 2.0; | |
| 391 CGFloat offset = thisPoint.x - dragOrigin_.x; | |
| 392 if (fabsf(offset) > 100) stretchiness = 0; | |
| 393 [sourceController_ insertPlaceholderForTab:self | |
| 394 frame:NSOffsetRect(sourceTabFrame_, | |
| 395 offset, 0) | |
| 396 yStretchiness:stretchiness]; | |
| 397 // Check that we haven't pulled the tab too far to start a drag. This | |
| 398 // can include either pulling it too far down, or off the side of the tab | |
| 399 // strip that would cause it to no longer be fully visible. | |
| 400 BOOL stillVisible = [sourceController_ isTabFullyVisible:self]; | |
| 401 CGFloat tearForce = fabs(thisPoint.y - dragOrigin_.y); | |
| 402 if ([sourceController_ tabTearingAllowed] && | |
| 403 (tearForce > kTearDistance || !stillVisible)) { | |
| 404 draggingWithinTabStrip_ = NO; | |
| 405 // When you finally leave the strip, we treat that as the origin. | |
| 406 dragOrigin_.x = thisPoint.x; | |
| 407 } else { | |
| 408 // Still dragging within the tab strip, wait for the next drag event. | |
| 409 return; | |
| 410 } | |
| 411 } | |
| 412 | |
| 413 // Do not start dragging until the user has "torn" the tab off by | |
| 414 // moving more than 3 pixels. | |
| 415 NSDate* targetDwellDate = nil; // The date this target was first chosen. | |
| 416 | |
| 417 NSPoint thisPoint = [NSEvent mouseLocation]; | |
| 418 | |
| 419 // Iterate over possible targets checking for the one the mouse is in. | |
| 420 // If the tab is just in the frame, bring the window forward to make it | |
| 421 // easier to drop something there. If it's in the tab strip, set the new | |
| 422 // target so that it pops into that window. We can't cache this because we | |
| 423 // need the z-order to be correct. | |
| 424 NSArray* targets = [self dropTargetsForController:draggedController_]; | |
| 425 TabWindowController* newTarget = nil; | |
| 426 for (TabWindowController* target in targets) { | |
| 427 NSRect windowFrame = [[target window] frame]; | |
| 428 if (NSPointInRect(thisPoint, windowFrame)) { | |
| 429 [[target window] orderFront:self]; | |
| 430 NSRect tabStripFrame = [[target tabStripView] frame]; | |
| 431 tabStripFrame.origin = [[target window] | |
| 432 convertBaseToScreen:tabStripFrame.origin]; | |
| 433 if (NSPointInRect(thisPoint, tabStripFrame)) { | |
| 434 newTarget = target; | |
| 435 } | |
| 436 break; | |
| 437 } | |
| 438 } | |
| 439 | |
| 440 // If we're now targeting a new window, re-layout the tabs in the old | |
| 441 // target and reset how long we've been hovering over this new one. | |
| 442 if (targetController_ != newTarget) { | |
| 443 targetDwellDate = [NSDate date]; | |
| 444 [targetController_ removePlaceholder]; | |
| 445 targetController_ = newTarget; | |
| 446 if (!newTarget) { | |
| 447 tearTime_ = [NSDate timeIntervalSinceReferenceDate]; | |
| 448 tearOrigin_ = [dragWindow_ frame].origin; | |
| 449 } | |
| 450 } | |
| 451 | |
| 452 // Create or identify the dragged controller. | |
| 453 if (!draggedController_) { | |
| 454 // Get rid of any placeholder remaining in the original source window. | |
| 455 [sourceController_ removePlaceholder]; | |
| 456 | |
| 457 // Detach from the current window and put it in a new window. If there are | |
| 458 // no more tabs remaining after detaching, the source window is about to | |
| 459 // go away (it's been autoreleased) so we need to ensure we don't reference | |
| 460 // it any more. In that case the new controller becomes our source | |
| 461 // controller. | |
| 462 draggedController_ = [sourceController_ detachTabToNewWindow:self]; | |
| 463 dragWindow_ = [draggedController_ window]; | |
| 464 [dragWindow_ setAlphaValue:0.0]; | |
| 465 if (![sourceController_ hasLiveTabs]) { | |
| 466 sourceController_ = draggedController_; | |
| 467 sourceWindow_ = dragWindow_; | |
| 468 } | |
| 469 | |
| 470 // If dragging the tab only moves the current window, do not show overlay | |
| 471 // so that sheets stay on top of the window. | |
| 472 // Bring the target window to the front and make sure it has a border. | |
| 473 [dragWindow_ setLevel:NSFloatingWindowLevel]; | |
| 474 [dragWindow_ setHasShadow:YES]; | |
| 475 [dragWindow_ orderFront:nil]; | |
| 476 [dragWindow_ makeMainWindow]; | |
| 477 [draggedController_ showOverlay]; | |
| 478 dragOverlay_ = [draggedController_ overlayWindow]; | |
| 479 // Force the new tab button to be hidden. We'll reset it on mouse up. | |
| 480 [draggedController_ showNewTabButton:NO]; | |
| 481 tearTime_ = [NSDate timeIntervalSinceReferenceDate]; | |
| 482 tearOrigin_ = sourceWindowFrame_.origin; | |
| 483 } | |
| 484 | |
| 485 // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by | |
| 486 // some weird circumstance that doesn't first go through mouseDown:. We | |
| 487 // really shouldn't go any farther. | |
| 488 if (!draggedController_ || !sourceController_) | |
| 489 return; | |
| 490 | |
| 491 // When the user first tears off the window, we want slide the window to | |
| 492 // the current mouse location (to reduce the jarring appearance). We do this | |
| 493 // by calling ourselves back with additional mouseDragged calls (not actual | |
| 494 // events). |tearProgress| is a normalized measure of how far through this | |
| 495 // tear "animation" (of length kTearDuration) we are and has values [0..1]. | |
| 496 // We use sqrt() so the animation is non-linear (slow down near the end | |
| 497 // point). | |
| 498 NSTimeInterval tearProgress = | |
| 499 [NSDate timeIntervalSinceReferenceDate] - tearTime_; | |
| 500 tearProgress /= kTearDuration; // Normalize. | |
| 501 tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0)); | |
| 502 | |
| 503 // Move the dragged window to the right place on the screen. | |
| 504 NSPoint origin = sourceWindowFrame_.origin; | |
| 505 origin.x += (thisPoint.x - dragOrigin_.x); | |
| 506 origin.y += (thisPoint.y - dragOrigin_.y); | |
| 507 | |
| 508 if (tearProgress < 1) { | |
| 509 // If the tear animation is not complete, call back to ourself with the | |
| 510 // same event to animate even if the mouse isn't moving. We need to make | |
| 511 // sure these get cancelled in mouseUp:. | |
| 512 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | |
| 513 [self performSelector:@selector(mouseDragged:) | |
| 514 withObject:theEvent | |
| 515 afterDelay:1.0f/30.0f]; | |
| 516 | |
| 517 // Set the current window origin based on how far we've progressed through | |
| 518 // the tear animation. | |
| 519 origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x; | |
| 520 origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y; | |
| 521 } | |
| 522 | |
| 523 if (targetController_) { | |
| 524 // In order to "snap" two windows of different sizes together at their | |
| 525 // toolbar, we can't just use the origin of the target frame. We also have | |
| 526 // to take into consideration the difference in height. | |
| 527 NSRect targetFrame = [[targetController_ window] frame]; | |
| 528 NSRect sourceFrame = [dragWindow_ frame]; | |
| 529 origin.y = NSMinY(targetFrame) + | |
| 530 (NSHeight(targetFrame) - NSHeight(sourceFrame)); | |
| 531 } | |
| 532 [dragWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; | |
| 533 | |
| 534 // If we're not hovering over any window, make the window fully | |
| 535 // opaque. Otherwise, find where the tab might be dropped and insert | |
| 536 // a placeholder so it appears like it's part of that window. | |
| 537 if (targetController_) { | |
| 538 if (![[targetController_ window] isKeyWindow]) { | |
| 539 // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) { | |
| 540 [[targetController_ window] orderFront:nil]; | |
| 541 targetDwellDate = nil; | |
| 542 } | |
| 543 | |
| 544 // Compute where placeholder should go and insert it into the | |
| 545 // destination tab strip. | |
| 546 TabView* draggedTabView = (TabView*)[draggedController_ activeTabView]; | |
| 547 NSRect tabFrame = [draggedTabView frame]; | |
| 548 tabFrame.origin = [dragWindow_ convertBaseToScreen:tabFrame.origin]; | |
| 549 tabFrame.origin = [[targetController_ window] | |
| 550 convertScreenToBase:tabFrame.origin]; | |
| 551 tabFrame = [[targetController_ tabStripView] | |
| 552 convertRect:tabFrame fromView:nil]; | |
| 553 [targetController_ insertPlaceholderForTab:self | |
| 554 frame:tabFrame | |
| 555 yStretchiness:0]; | |
| 556 [targetController_ layoutTabs]; | |
| 557 } else { | |
| 558 [dragWindow_ makeKeyAndOrderFront:nil]; | |
| 559 } | |
| 560 | |
| 561 // Adjust the visibility of the window background. If there is a drop target, | |
| 562 // we want to hide the window background so the tab stands out for | |
| 563 // positioning. If not, we want to show it so it looks like a new window will | |
| 564 // be realized. | |
| 565 BOOL chromeShouldBeVisible = targetController_ == nil; | |
| 566 [self setWindowBackgroundVisibility:chromeShouldBeVisible]; | |
| 567 } | 195 } |
| 568 | 196 |
| 569 - (void)mouseUp:(NSEvent*)theEvent { | 197 - (void)mouseUp:(NSEvent*)theEvent { |
| 570 // The drag/click is done. If the user dragged the mouse, finalize the drag | |
| 571 // and clean up. | |
| 572 | |
| 573 // Fire the action to select the tab. | 198 // Fire the action to select the tab. |
| 574 if ([[controller_ target] respondsToSelector:[controller_ action]]) | 199 if ([[controller_ target] respondsToSelector:[controller_ action]]) |
| 575 [[controller_ target] performSelector:[controller_ action] | 200 [[controller_ target] performSelector:[controller_ action] |
| 576 withObject:self]; | 201 withObject:self]; |
| 577 | 202 |
| 578 // Special-case this to keep the logic below simpler. | 203 // Check for rapid tab closure. |
| 579 if (moveWindowOnDrag_) | 204 if ([theEvent type] == NSLeftMouseUp) { |
| 580 return; | 205 NSPoint upLocation = [theEvent locationInWindow]; |
| 206 CGFloat dx = upLocation.x - mouseDownPoint_.x; |
| 207 CGFloat dy = upLocation.y - mouseDownPoint_.y; |
| 581 | 208 |
| 582 // Cancel any delayed -mouseDragged: requests that may still be pending. | 209 // During rapid tab closure (mashing tab close buttons), we may get hit |
| 583 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 210 // with a mouse down. As long as the mouse up is over the close button, |
| 211 // and the mouse hasn't moved too much, we close the tab. |
| 212 if (![closeButton_ isHidden] && |
| 213 (dx*dx + dy*dy) <= kRapidCloseDist*kRapidCloseDist && |
| 214 [controller_ inRapidClosureMode]) { |
| 215 NSPoint hitLocation = |
| 216 [[self superview] convertPoint:[theEvent locationInWindow] |
| 217 fromView:nil]; |
| 218 if ([self hitTest:hitLocation] == closeButton_) { |
| 219 [controller_ closeTab:self]; |
| 220 } |
| 221 } |
| 222 } |
| 584 | 223 |
| 585 // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by | 224 // Messaging the drag controller with |-endDrag:| would seem like the right |
| 586 // some weird circumstance that doesn't first go through mouseDown:. We | 225 // thing to do here. But, when a tab has been detached, the controller's |
| 587 // really shouldn't go any farther. | 226 // target is nil until the drag is finalized. Since |-mouseUp:| gets called |
| 588 if (!sourceController_) | 227 // via the manual event loop inside -[TabStripDragController |
| 589 return; | 228 // maybeStartDrag:forTab:], the drag controller can end the dragging session |
| 590 | 229 // itself directly after calling this. |
| 591 // We are now free to re-display the new tab button in the window we're | |
| 592 // dragging. It will show when the next call to -layoutTabs (which happens | |
| 593 // indrectly by several of the calls below, such as removing the placeholder). | |
| 594 [draggedController_ showNewTabButton:YES]; | |
| 595 | |
| 596 if (draggingWithinTabStrip_) { | |
| 597 if (tabWasDragged_) { | |
| 598 // Move tab to new location. | |
| 599 DCHECK([sourceController_ numberOfTabs]); | |
| 600 TabWindowController* dropController = sourceController_; | |
| 601 [dropController moveTabView:[dropController activeTabView] | |
| 602 fromController:nil]; | |
| 603 } | |
| 604 } else if (targetController_) { | |
| 605 // Move between windows. If |targetController_| is nil, we're not dropping | |
| 606 // into any existing window. | |
| 607 NSView* draggedTabView = [draggedController_ activeTabView]; | |
| 608 [targetController_ moveTabView:draggedTabView | |
| 609 fromController:draggedController_]; | |
| 610 // Force redraw to avoid flashes of old content before returning to event | |
| 611 // loop. | |
| 612 [[targetController_ window] display]; | |
| 613 [targetController_ showWindow:nil]; | |
| 614 [draggedController_ removeOverlay]; | |
| 615 } else { | |
| 616 // Only move the window around on screen. Make sure it's set back to | |
| 617 // normal state (fully opaque, has shadow, has key, etc). | |
| 618 [draggedController_ removeOverlay]; | |
| 619 // Don't want to re-show the window if it was closed during the drag. | |
| 620 if ([dragWindow_ isVisible]) { | |
| 621 [dragWindow_ setAlphaValue:1.0]; | |
| 622 [dragOverlay_ setHasShadow:NO]; | |
| 623 [dragWindow_ setHasShadow:YES]; | |
| 624 [dragWindow_ makeKeyAndOrderFront:nil]; | |
| 625 } | |
| 626 [[draggedController_ window] setLevel:NSNormalWindowLevel]; | |
| 627 [draggedController_ removePlaceholder]; | |
| 628 } | |
| 629 [sourceController_ removePlaceholder]; | |
| 630 chromeIsVisible_ = YES; | |
| 631 | |
| 632 [self resetDragControllers]; | |
| 633 } | 230 } |
| 634 | 231 |
| 635 - (void)otherMouseUp:(NSEvent*)theEvent { | 232 - (void)otherMouseUp:(NSEvent*)theEvent { |
| 636 if ([self isClosing]) | 233 if ([self isClosing]) |
| 637 return; | 234 return; |
| 638 | 235 |
| 639 // Support middle-click-to-close. | 236 // Support middle-click-to-close. |
| 640 if ([theEvent buttonNumber] == 2) { | 237 if ([theEvent buttonNumber] == 2) { |
| 641 // |-hitTest:| takes a location in the superview's coordinates. | 238 // |-hitTest:| takes a location in the superview's coordinates. |
| 642 NSPoint upLocation = | 239 NSPoint upLocation = |
| (...skipping 322 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 965 } | 562 } |
| 966 } | 563 } |
| 967 | 564 |
| 968 if (nextUpdate < kNoUpdate) | 565 if (nextUpdate < kNoUpdate) |
| 969 [self performSelector:_cmd withObject:nil afterDelay:nextUpdate]; | 566 [self performSelector:_cmd withObject:nil afterDelay:nextUpdate]; |
| 970 | 567 |
| 971 [self resetLastGlowUpdateTime]; | 568 [self resetLastGlowUpdateTime]; |
| 972 [self setNeedsDisplay:YES]; | 569 [self setNeedsDisplay:YES]; |
| 973 } | 570 } |
| 974 | 571 |
| 975 // Returns the workspace id of |window|. If |useCache|, then lookup | |
| 976 // and remember the value in |workspaceIDCache_| until the end of the | |
| 977 // current drag. | |
| 978 - (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache { | |
| 979 CGWindowID windowID = [window windowNumber]; | |
| 980 if (useCache) { | |
| 981 std::map<CGWindowID, int>::iterator iter = | |
| 982 workspaceIDCache_.find(windowID); | |
| 983 if (iter != workspaceIDCache_.end()) | |
| 984 return iter->second; | |
| 985 } | |
| 986 | |
| 987 int workspace = -1; | |
| 988 // It's possible to query in bulk, but probably not necessary. | |
| 989 base::mac::ScopedCFTypeRef<CFArrayRef> windowIDs(CFArrayCreate( | |
| 990 NULL, reinterpret_cast<const void **>(&windowID), 1, NULL)); | |
| 991 base::mac::ScopedCFTypeRef<CFArrayRef> descriptions( | |
| 992 CGWindowListCreateDescriptionFromArray(windowIDs)); | |
| 993 DCHECK(CFArrayGetCount(descriptions.get()) <= 1); | |
| 994 if (CFArrayGetCount(descriptions.get()) > 0) { | |
| 995 CFDictionaryRef dict = static_cast<CFDictionaryRef>( | |
| 996 CFArrayGetValueAtIndex(descriptions.get(), 0)); | |
| 997 DCHECK(CFGetTypeID(dict) == CFDictionaryGetTypeID()); | |
| 998 | |
| 999 // Sanity check the ID. | |
| 1000 CFNumberRef otherIDRef = (CFNumberRef)base::mac::GetValueFromDictionary( | |
| 1001 dict, kCGWindowNumber, CFNumberGetTypeID()); | |
| 1002 CGWindowID otherID; | |
| 1003 if (otherIDRef && | |
| 1004 CFNumberGetValue(otherIDRef, kCGWindowIDCFNumberType, &otherID) && | |
| 1005 otherID == windowID) { | |
| 1006 // And then get the workspace. | |
| 1007 CFNumberRef workspaceRef = (CFNumberRef)base::mac::GetValueFromDictionary( | |
| 1008 dict, kCGWindowWorkspace, CFNumberGetTypeID()); | |
| 1009 if (!workspaceRef || | |
| 1010 !CFNumberGetValue(workspaceRef, kCFNumberIntType, &workspace)) { | |
| 1011 workspace = -1; | |
| 1012 } | |
| 1013 } else { | |
| 1014 NOTREACHED(); | |
| 1015 } | |
| 1016 } | |
| 1017 if (useCache) { | |
| 1018 workspaceIDCache_[windowID] = workspace; | |
| 1019 } | |
| 1020 return workspace; | |
| 1021 } | |
| 1022 | |
| 1023 // Returns the bezier path used to draw the tab given the bounds to draw it in. | 572 // Returns the bezier path used to draw the tab given the bounds to draw it in. |
| 1024 - (NSBezierPath*)bezierPathForRect:(NSRect)rect { | 573 - (NSBezierPath*)bezierPathForRect:(NSRect)rect { |
| 1025 const CGFloat lineWidth = [self cr_lineWidth]; | 574 const CGFloat lineWidth = [self cr_lineWidth]; |
| 1026 const CGFloat halfLineWidth = lineWidth / 2.0; | 575 const CGFloat halfLineWidth = lineWidth / 2.0; |
| 1027 | 576 |
| 1028 // Outset by halfLineWidth in order to draw on pixels rather than on borders | 577 // Outset by halfLineWidth in order to draw on pixels rather than on borders |
| 1029 // (which would cause blurry pixels). Subtract lineWidth of height to | 578 // (which would cause blurry pixels). Subtract lineWidth of height to |
| 1030 // compensate, otherwise clipping will occur. | 579 // compensate, otherwise clipping will occur. |
| 1031 rect = NSInsetRect(rect, -halfLineWidth, -halfLineWidth); | 580 rect = NSInsetRect(rect, -halfLineWidth, -halfLineWidth); |
| 1032 rect.size.height -= lineWidth; | 581 rect.size.height -= lineWidth; |
| (...skipping 28 matching lines...) Expand all Loading... |
| 1061 topRight.y) | 610 topRight.y) |
| 1062 controlPoint2:NSMakePoint(bottomRight.x - baseControlPointOutset, | 611 controlPoint2:NSMakePoint(bottomRight.x - baseControlPointOutset, |
| 1063 bottomRight.y)]; | 612 bottomRight.y)]; |
| 1064 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth, bottomRight.y)]; | 613 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth, bottomRight.y)]; |
| 1065 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth, | 614 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth, |
| 1066 bottomRight.y - (2 * lineWidth))]; | 615 bottomRight.y - (2 * lineWidth))]; |
| 1067 return path; | 616 return path; |
| 1068 } | 617 } |
| 1069 | 618 |
| 1070 @end // @implementation TabView(Private) | 619 @end // @implementation TabView(Private) |
| OLD | NEW |