OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.h" |
| 6 |
| 7 #import "base/mac/mac_util.h" |
| 8 #include "base/mac/scoped_cftyperef.h" |
| 9 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h" |
| 10 #import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h" |
| 11 #import "chrome/browser/ui/cocoa/tabs/tab_view.h" |
| 12 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h" |
| 13 |
| 14 const CGFloat kTearDistance = 36.0; |
| 15 const NSTimeInterval kTearDuration = 0.333; |
| 16 |
| 17 @interface TabStripDragController (Private) |
| 18 - (void)resetDragControllers; |
| 19 - (NSArray*)dropTargetsForController:(TabWindowController*)dragController; |
| 20 - (void)setWindowBackgroundVisibility:(BOOL)shouldBeVisible; |
| 21 // TODO(davidben): When we stop supporting 10.5, this can be removed. |
| 22 - (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache; |
| 23 @end |
| 24 |
| 25 //////////////////////////////////////////////////////////////////////////////// |
| 26 |
| 27 @implementation TabStripDragController |
| 28 |
| 29 - (id)initWithTabStripController:(TabStripController*)controller { |
| 30 if ((self = [super init])) { |
| 31 tabStrip_ = controller; |
| 32 } |
| 33 return self; |
| 34 } |
| 35 |
| 36 - (void)dealloc { |
| 37 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
| 38 [super dealloc]; |
| 39 } |
| 40 |
| 41 - (BOOL)tabCanBeDragged:(TabController*)tab { |
| 42 if ([[tab tabView] isClosing]) |
| 43 return NO; |
| 44 NSWindowController* controller = [sourceWindow_ windowController]; |
| 45 if ([controller isKindOfClass:[TabWindowController class]]) { |
| 46 TabWindowController* realController = |
| 47 static_cast<TabWindowController*>(controller); |
| 48 return [realController isTabDraggable:[tab tabView]]; |
| 49 } |
| 50 return YES; |
| 51 } |
| 52 |
| 53 - (void)maybeStartDrag:(NSEvent*)theEvent forTab:(TabController*)tab { |
| 54 [self resetDragControllers]; |
| 55 |
| 56 // Resolve overlay back to original window. |
| 57 sourceWindow_ = [[tab view] window]; |
| 58 if ([sourceWindow_ isKindOfClass:[NSPanel class]]) { |
| 59 sourceWindow_ = [sourceWindow_ parentWindow]; |
| 60 } |
| 61 |
| 62 sourceWindowFrame_ = [sourceWindow_ frame]; |
| 63 sourceTabFrame_ = [[tab view] frame]; |
| 64 sourceController_ = [sourceWindow_ windowController]; |
| 65 draggedTab_ = tab; |
| 66 tabWasDragged_ = NO; |
| 67 tearTime_ = 0.0; |
| 68 draggingWithinTabStrip_ = YES; |
| 69 chromeIsVisible_ = NO; |
| 70 |
| 71 // If there's more than one potential window to be a drop target, we want to |
| 72 // treat a drag of a tab just like dragging around a tab that's already |
| 73 // detached. Note that unit tests might have |-numberOfTabs| reporting zero |
| 74 // since the model won't be fully hooked up. We need to be prepared for that |
| 75 // and not send them into the "magnetic" codepath. |
| 76 NSArray* targets = [self dropTargetsForController:sourceController_]; |
| 77 moveWindowOnDrag_ = |
| 78 ([sourceController_ numberOfTabs] < 2 && ![targets count]) || |
| 79 ![self tabCanBeDragged:tab] || |
| 80 ![sourceController_ tabDraggingAllowed]; |
| 81 // If we are dragging a tab, a window with a single tab should immediately |
| 82 // snap off and not drag within the tab strip. |
| 83 if (!moveWindowOnDrag_) |
| 84 draggingWithinTabStrip_ = [sourceController_ numberOfTabs] > 1; |
| 85 |
| 86 dragOrigin_ = [NSEvent mouseLocation]; |
| 87 |
| 88 // When spinning the event loop, a tab can get detached, which could lead to |
| 89 // our own destruction. Keep ourselves around while spinning the loop. |
| 90 scoped_nsobject<TabStripDragController> keepAlive([self retain]); |
| 91 |
| 92 // Because we move views between windows, we need to handle the event loop |
| 93 // ourselves. Ideally we should use the standard event loop. |
| 94 while (1) { |
| 95 const NSUInteger mask = |
| 96 NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSKeyUpMask; |
| 97 theEvent = |
| 98 [NSApp nextEventMatchingMask:mask |
| 99 untilDate:[NSDate distantFuture] |
| 100 inMode:NSDefaultRunLoopMode |
| 101 dequeue:YES]; |
| 102 NSEventType type = [theEvent type]; |
| 103 if (type == NSKeyUp) { |
| 104 if ([theEvent keyCode] == kVK_Escape) { |
| 105 // Cancel the drag and restore the previous state. |
| 106 if (draggingWithinTabStrip_) { |
| 107 // Simply pretend the tab wasn't dragged (far enough). |
| 108 tabWasDragged_ = NO; |
| 109 } else { |
| 110 [targetController_ removePlaceholder]; |
| 111 if ([sourceController_ numberOfTabs] < 2) { |
| 112 // Revert to a single-tab window. |
| 113 targetController_ = nil; |
| 114 } else { |
| 115 // Change the target to the source controller. |
| 116 targetController_ = sourceController_; |
| 117 [targetController_ insertPlaceholderForTab:[tab tabView] |
| 118 frame:sourceTabFrame_ |
| 119 yStretchiness:0]; |
| 120 } |
| 121 } |
| 122 // Simply end the drag at this point. |
| 123 [self endDrag:theEvent]; |
| 124 break; |
| 125 } |
| 126 } else if (type == NSLeftMouseDragged) { |
| 127 [self continueDrag:theEvent]; |
| 128 } else if (type == NSLeftMouseUp) { |
| 129 [[tab view] mouseUp:theEvent]; |
| 130 [self endDrag:theEvent]; |
| 131 break; |
| 132 } else { |
| 133 // TODO(viettrungluu): [crbug.com/23830] We can receive right-mouse-ups |
| 134 // (and maybe even others?) for reasons I don't understand. So we |
| 135 // explicitly check for both events we're expecting, and log others. We |
| 136 // should figure out what's going on. |
| 137 LOG(WARNING) << "Spurious event received of type " << type << "."; |
| 138 } |
| 139 } |
| 140 } |
| 141 |
| 142 - (void)continueDrag:(NSEvent*)theEvent { |
| 143 // Special-case this to keep the logic below simpler. |
| 144 if (moveWindowOnDrag_) { |
| 145 if ([sourceController_ windowMovementAllowed]) { |
| 146 NSPoint thisPoint = [NSEvent mouseLocation]; |
| 147 NSPoint origin = sourceWindowFrame_.origin; |
| 148 origin.x += (thisPoint.x - dragOrigin_.x); |
| 149 origin.y += (thisPoint.y - dragOrigin_.y); |
| 150 [sourceWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; |
| 151 } // else do nothing. |
| 152 return; |
| 153 } |
| 154 |
| 155 // First, go through the magnetic drag cycle. We break out of this if |
| 156 // "stretchiness" ever exceeds a set amount. |
| 157 tabWasDragged_ = YES; |
| 158 |
| 159 if (draggingWithinTabStrip_) { |
| 160 NSPoint thisPoint = [NSEvent mouseLocation]; |
| 161 CGFloat stretchiness = thisPoint.y - dragOrigin_.y; |
| 162 stretchiness = copysign(sqrtf(fabs(stretchiness))/sqrtf(kTearDistance), |
| 163 stretchiness) / 2.0; |
| 164 CGFloat offset = thisPoint.x - dragOrigin_.x; |
| 165 if (fabsf(offset) > 100) stretchiness = 0; |
| 166 [sourceController_ insertPlaceholderForTab:[draggedTab_ tabView] |
| 167 frame:NSOffsetRect(sourceTabFrame_, |
| 168 offset, 0) |
| 169 yStretchiness:stretchiness]; |
| 170 // Check that we haven't pulled the tab too far to start a drag. This |
| 171 // can include either pulling it too far down, or off the side of the tab |
| 172 // strip that would cause it to no longer be fully visible. |
| 173 BOOL stillVisible = |
| 174 [sourceController_ isTabFullyVisible:[draggedTab_ tabView]]; |
| 175 CGFloat tearForce = fabs(thisPoint.y - dragOrigin_.y); |
| 176 if ([sourceController_ tabTearingAllowed] && |
| 177 (tearForce > kTearDistance || !stillVisible)) { |
| 178 draggingWithinTabStrip_ = NO; |
| 179 // When you finally leave the strip, we treat that as the origin. |
| 180 dragOrigin_.x = thisPoint.x; |
| 181 } else { |
| 182 // Still dragging within the tab strip, wait for the next drag event. |
| 183 return; |
| 184 } |
| 185 } |
| 186 |
| 187 // Do not start dragging until the user has "torn" the tab off by |
| 188 // moving more than 3 pixels. |
| 189 NSDate* targetDwellDate = nil; // The date this target was first chosen. |
| 190 |
| 191 NSPoint thisPoint = [NSEvent mouseLocation]; |
| 192 |
| 193 // Iterate over possible targets checking for the one the mouse is in. |
| 194 // If the tab is just in the frame, bring the window forward to make it |
| 195 // easier to drop something there. If it's in the tab strip, set the new |
| 196 // target so that it pops into that window. We can't cache this because we |
| 197 // need the z-order to be correct. |
| 198 NSArray* targets = [self dropTargetsForController:draggedController_]; |
| 199 TabWindowController* newTarget = nil; |
| 200 for (TabWindowController* target in targets) { |
| 201 NSRect windowFrame = [[target window] frame]; |
| 202 if (NSPointInRect(thisPoint, windowFrame)) { |
| 203 [[target window] orderFront:self]; |
| 204 NSRect tabStripFrame = [[target tabStripView] frame]; |
| 205 tabStripFrame.origin = [[target window] |
| 206 convertBaseToScreen:tabStripFrame.origin]; |
| 207 if (NSPointInRect(thisPoint, tabStripFrame)) { |
| 208 newTarget = target; |
| 209 } |
| 210 break; |
| 211 } |
| 212 } |
| 213 |
| 214 // If we're now targeting a new window, re-layout the tabs in the old |
| 215 // target and reset how long we've been hovering over this new one. |
| 216 if (targetController_ != newTarget) { |
| 217 targetDwellDate = [NSDate date]; |
| 218 [targetController_ removePlaceholder]; |
| 219 targetController_ = newTarget; |
| 220 if (!newTarget) { |
| 221 tearTime_ = [NSDate timeIntervalSinceReferenceDate]; |
| 222 tearOrigin_ = [dragWindow_ frame].origin; |
| 223 } |
| 224 } |
| 225 |
| 226 // Create or identify the dragged controller. |
| 227 if (!draggedController_) { |
| 228 // Get rid of any placeholder remaining in the original source window. |
| 229 [sourceController_ removePlaceholder]; |
| 230 |
| 231 // Detach from the current window and put it in a new window. If there are |
| 232 // no more tabs remaining after detaching, the source window is about to |
| 233 // go away (it's been autoreleased) so we need to ensure we don't reference |
| 234 // it any more. In that case the new controller becomes our source |
| 235 // controller. |
| 236 draggedController_ = |
| 237 [sourceController_ detachTabToNewWindow:[draggedTab_ tabView]]; |
| 238 dragWindow_ = [draggedController_ window]; |
| 239 [dragWindow_ setAlphaValue:0.0]; |
| 240 if (![sourceController_ hasLiveTabs]) { |
| 241 sourceController_ = draggedController_; |
| 242 sourceWindow_ = dragWindow_; |
| 243 } |
| 244 |
| 245 // If dragging the tab only moves the current window, do not show overlay |
| 246 // so that sheets stay on top of the window. |
| 247 // Bring the target window to the front and make sure it has a border. |
| 248 [dragWindow_ setLevel:NSFloatingWindowLevel]; |
| 249 [dragWindow_ setHasShadow:YES]; |
| 250 [dragWindow_ orderFront:nil]; |
| 251 [dragWindow_ makeMainWindow]; |
| 252 [draggedController_ showOverlay]; |
| 253 dragOverlay_ = [draggedController_ overlayWindow]; |
| 254 // Force the new tab button to be hidden. We'll reset it on mouse up. |
| 255 [draggedController_ showNewTabButton:NO]; |
| 256 tearTime_ = [NSDate timeIntervalSinceReferenceDate]; |
| 257 tearOrigin_ = sourceWindowFrame_.origin; |
| 258 } |
| 259 |
| 260 // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by |
| 261 // some weird circumstance that doesn't first go through mouseDown:. We |
| 262 // really shouldn't go any farther. |
| 263 if (!draggedController_ || !sourceController_) |
| 264 return; |
| 265 |
| 266 // When the user first tears off the window, we want slide the window to |
| 267 // the current mouse location (to reduce the jarring appearance). We do this |
| 268 // by calling ourselves back with additional mouseDragged calls (not actual |
| 269 // events). |tearProgress| is a normalized measure of how far through this |
| 270 // tear "animation" (of length kTearDuration) we are and has values [0..1]. |
| 271 // We use sqrt() so the animation is non-linear (slow down near the end |
| 272 // point). |
| 273 NSTimeInterval tearProgress = |
| 274 [NSDate timeIntervalSinceReferenceDate] - tearTime_; |
| 275 tearProgress /= kTearDuration; // Normalize. |
| 276 tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0)); |
| 277 |
| 278 // Move the dragged window to the right place on the screen. |
| 279 NSPoint origin = sourceWindowFrame_.origin; |
| 280 origin.x += (thisPoint.x - dragOrigin_.x); |
| 281 origin.y += (thisPoint.y - dragOrigin_.y); |
| 282 |
| 283 if (tearProgress < 1) { |
| 284 // If the tear animation is not complete, call back to ourself with the |
| 285 // same event to animate even if the mouse isn't moving. We need to make |
| 286 // sure these get cancelled in mouseUp:. |
| 287 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
| 288 [self performSelector:@selector(continueDrag:) |
| 289 withObject:theEvent |
| 290 afterDelay:1.0f/30.0f]; |
| 291 |
| 292 // Set the current window origin based on how far we've progressed through |
| 293 // the tear animation. |
| 294 origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x; |
| 295 origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y; |
| 296 } |
| 297 |
| 298 if (targetController_) { |
| 299 // In order to "snap" two windows of different sizes together at their |
| 300 // toolbar, we can't just use the origin of the target frame. We also have |
| 301 // to take into consideration the difference in height. |
| 302 NSRect targetFrame = [[targetController_ window] frame]; |
| 303 NSRect sourceFrame = [dragWindow_ frame]; |
| 304 origin.y = NSMinY(targetFrame) + |
| 305 (NSHeight(targetFrame) - NSHeight(sourceFrame)); |
| 306 } |
| 307 [dragWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; |
| 308 |
| 309 // If we're not hovering over any window, make the window fully |
| 310 // opaque. Otherwise, find where the tab might be dropped and insert |
| 311 // a placeholder so it appears like it's part of that window. |
| 312 if (targetController_) { |
| 313 if (![[targetController_ window] isKeyWindow]) { |
| 314 // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) { |
| 315 [[targetController_ window] orderFront:nil]; |
| 316 targetDwellDate = nil; |
| 317 } |
| 318 |
| 319 // Compute where placeholder should go and insert it into the |
| 320 // destination tab strip. |
| 321 TabView* draggedTabView = (TabView*)[draggedController_ activeTabView]; |
| 322 NSRect tabFrame = [draggedTabView frame]; |
| 323 tabFrame.origin = [dragWindow_ convertBaseToScreen:tabFrame.origin]; |
| 324 tabFrame.origin = [[targetController_ window] |
| 325 convertScreenToBase:tabFrame.origin]; |
| 326 tabFrame = [[targetController_ tabStripView] |
| 327 convertRect:tabFrame fromView:nil]; |
| 328 [targetController_ insertPlaceholderForTab:[draggedTab_ tabView] |
| 329 frame:tabFrame |
| 330 yStretchiness:0]; |
| 331 [targetController_ layoutTabs]; |
| 332 } else { |
| 333 [dragWindow_ makeKeyAndOrderFront:nil]; |
| 334 } |
| 335 |
| 336 // Adjust the visibility of the window background. If there is a drop target, |
| 337 // we want to hide the window background so the tab stands out for |
| 338 // positioning. If not, we want to show it so it looks like a new window will |
| 339 // be realized. |
| 340 BOOL chromeShouldBeVisible = targetController_ == nil; |
| 341 [self setWindowBackgroundVisibility:chromeShouldBeVisible]; |
| 342 } |
| 343 |
| 344 - (void)endDrag:(NSEvent*)event { |
| 345 // Special-case this to keep the logic below simpler. |
| 346 if (moveWindowOnDrag_) |
| 347 return; |
| 348 |
| 349 // Cancel any delayed -mouseDragged: requests that may still be pending. |
| 350 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
| 351 |
| 352 // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by |
| 353 // some weird circumstance that doesn't first go through mouseDown:. We |
| 354 // really shouldn't go any farther. |
| 355 if (!sourceController_) |
| 356 return; |
| 357 |
| 358 // We are now free to re-display the new tab button in the window we're |
| 359 // dragging. It will show when the next call to -layoutTabs (which happens |
| 360 // indrectly by several of the calls below, such as removing the placeholder). |
| 361 [draggedController_ showNewTabButton:YES]; |
| 362 |
| 363 if (draggingWithinTabStrip_) { |
| 364 if (tabWasDragged_) { |
| 365 // Move tab to new location. |
| 366 DCHECK([sourceController_ numberOfTabs]); |
| 367 TabWindowController* dropController = sourceController_; |
| 368 [dropController moveTabView:[dropController activeTabView] |
| 369 fromController:nil]; |
| 370 } |
| 371 } else if (targetController_) { |
| 372 // Move between windows. If |targetController_| is nil, we're not dropping |
| 373 // into any existing window. |
| 374 NSView* draggedTabView = [draggedController_ activeTabView]; |
| 375 [targetController_ moveTabView:draggedTabView |
| 376 fromController:draggedController_]; |
| 377 // Force redraw to avoid flashes of old content before returning to event |
| 378 // loop. |
| 379 [[targetController_ window] display]; |
| 380 [targetController_ showWindow:nil]; |
| 381 [draggedController_ removeOverlay]; |
| 382 } else { |
| 383 // Only move the window around on screen. Make sure it's set back to |
| 384 // normal state (fully opaque, has shadow, has key, etc). |
| 385 [draggedController_ removeOverlay]; |
| 386 // Don't want to re-show the window if it was closed during the drag. |
| 387 if ([dragWindow_ isVisible]) { |
| 388 [dragWindow_ setAlphaValue:1.0]; |
| 389 [dragOverlay_ setHasShadow:NO]; |
| 390 [dragWindow_ setHasShadow:YES]; |
| 391 [dragWindow_ makeKeyAndOrderFront:nil]; |
| 392 } |
| 393 [[draggedController_ window] setLevel:NSNormalWindowLevel]; |
| 394 [draggedController_ removePlaceholder]; |
| 395 } |
| 396 [sourceController_ removePlaceholder]; |
| 397 chromeIsVisible_ = YES; |
| 398 |
| 399 [self resetDragControllers]; |
| 400 } |
| 401 |
| 402 // Private ///////////////////////////////////////////////////////////////////// |
| 403 |
| 404 // Call to clear out transient weak references we hold during drags. |
| 405 - (void)resetDragControllers { |
| 406 draggedController_ = nil; |
| 407 dragWindow_ = nil; |
| 408 dragOverlay_ = nil; |
| 409 sourceController_ = nil; |
| 410 sourceWindow_ = nil; |
| 411 targetController_ = nil; |
| 412 workspaceIDCache_.clear(); |
| 413 } |
| 414 |
| 415 // Returns an array of controllers that could be a drop target, ordered front to |
| 416 // back. It has to be of the appropriate class, and visible (obviously). Note |
| 417 // that the window cannot be a target for itself. |
| 418 - (NSArray*)dropTargetsForController:(TabWindowController*)dragController { |
| 419 NSMutableArray* targets = [NSMutableArray array]; |
| 420 NSWindow* dragWindow = [dragController window]; |
| 421 for (NSWindow* window in [NSApp orderedWindows]) { |
| 422 if (window == dragWindow) continue; |
| 423 if (![window isVisible]) continue; |
| 424 // Skip windows on the wrong space. |
| 425 if ([window respondsToSelector:@selector(isOnActiveSpace)]) { |
| 426 if (![window performSelector:@selector(isOnActiveSpace)]) |
| 427 continue; |
| 428 } else { |
| 429 // TODO(davidben): When we stop supporting 10.5, this can be |
| 430 // removed. |
| 431 // |
| 432 // We don't cache the workspace of |dragWindow| because it may |
| 433 // move around spaces. |
| 434 if ([self getWorkspaceID:dragWindow useCache:NO] != |
| 435 [self getWorkspaceID:window useCache:YES]) |
| 436 continue; |
| 437 } |
| 438 NSWindowController* controller = [window windowController]; |
| 439 if ([controller isKindOfClass:[TabWindowController class]]) { |
| 440 TabWindowController* realController = |
| 441 static_cast<TabWindowController*>(controller); |
| 442 if ([realController canReceiveFrom:dragController]) |
| 443 [targets addObject:controller]; |
| 444 } |
| 445 } |
| 446 return targets; |
| 447 } |
| 448 |
| 449 // Sets whether the window background should be visible or invisible when |
| 450 // dragging a tab. The background should be invisible when the mouse is over a |
| 451 // potential drop target for the tab (the tab strip). It should be visible when |
| 452 // there's no drop target so the window looks more fully realized and ready to |
| 453 // become a stand-alone window. |
| 454 - (void)setWindowBackgroundVisibility:(BOOL)shouldBeVisible { |
| 455 if (chromeIsVisible_ == shouldBeVisible) |
| 456 return; |
| 457 |
| 458 // There appears to be a race-condition in CoreAnimation where if we use |
| 459 // animators to set the alpha values, we can't guarantee that we cancel them. |
| 460 // This has the side effect of sometimes leaving the dragged window |
| 461 // translucent or invisible. As a result, don't animate the alpha change. |
| 462 [[draggedController_ overlayWindow] setAlphaValue:1.0]; |
| 463 if (targetController_) { |
| 464 [dragWindow_ setAlphaValue:0.0]; |
| 465 [[draggedController_ overlayWindow] setHasShadow:YES]; |
| 466 [[targetController_ window] makeMainWindow]; |
| 467 } else { |
| 468 [dragWindow_ setAlphaValue:0.5]; |
| 469 [[draggedController_ overlayWindow] setHasShadow:NO]; |
| 470 [[draggedController_ window] makeMainWindow]; |
| 471 } |
| 472 chromeIsVisible_ = shouldBeVisible; |
| 473 } |
| 474 |
| 475 // Returns the workspace id of |window|. If |useCache|, then lookup |
| 476 // and remember the value in |workspaceIDCache_| until the end of the |
| 477 // current drag. |
| 478 - (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache { |
| 479 CGWindowID windowID = [window windowNumber]; |
| 480 if (useCache) { |
| 481 std::map<CGWindowID, int>::iterator iter = |
| 482 workspaceIDCache_.find(windowID); |
| 483 if (iter != workspaceIDCache_.end()) |
| 484 return iter->second; |
| 485 } |
| 486 |
| 487 int workspace = -1; |
| 488 // It's possible to query in bulk, but probably not necessary. |
| 489 base::mac::ScopedCFTypeRef<CFArrayRef> windowIDs(CFArrayCreate( |
| 490 NULL, reinterpret_cast<const void **>(&windowID), 1, NULL)); |
| 491 base::mac::ScopedCFTypeRef<CFArrayRef> descriptions( |
| 492 CGWindowListCreateDescriptionFromArray(windowIDs)); |
| 493 DCHECK(CFArrayGetCount(descriptions.get()) <= 1); |
| 494 if (CFArrayGetCount(descriptions.get()) > 0) { |
| 495 CFDictionaryRef dict = static_cast<CFDictionaryRef>( |
| 496 CFArrayGetValueAtIndex(descriptions.get(), 0)); |
| 497 DCHECK(CFGetTypeID(dict) == CFDictionaryGetTypeID()); |
| 498 |
| 499 // Sanity check the ID. |
| 500 CFNumberRef otherIDRef = (CFNumberRef)base::mac::GetValueFromDictionary( |
| 501 dict, kCGWindowNumber, CFNumberGetTypeID()); |
| 502 CGWindowID otherID; |
| 503 if (otherIDRef && |
| 504 CFNumberGetValue(otherIDRef, kCGWindowIDCFNumberType, &otherID) && |
| 505 otherID == windowID) { |
| 506 // And then get the workspace. |
| 507 CFNumberRef workspaceRef = (CFNumberRef)base::mac::GetValueFromDictionary( |
| 508 dict, kCGWindowWorkspace, CFNumberGetTypeID()); |
| 509 if (!workspaceRef || |
| 510 !CFNumberGetValue(workspaceRef, kCFNumberIntType, &workspace)) { |
| 511 workspace = -1; |
| 512 } |
| 513 } else { |
| 514 NOTREACHED(); |
| 515 } |
| 516 } |
| 517 if (useCache) { |
| 518 workspaceIDCache_[windowID] = workspace; |
| 519 } |
| 520 return workspace; |
| 521 } |
| 522 |
| 523 @end |
OLD | NEW |