| Index: chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.mm | 
| diff --git a/chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.mm b/chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.mm | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..f9b26628c050df6fc558cd84c90d59f1472ce70e | 
| --- /dev/null | 
| +++ b/chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.mm | 
| @@ -0,0 +1,523 @@ | 
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#import "chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.h" | 
| + | 
| +#import "base/mac/mac_util.h" | 
| +#include "base/mac/scoped_cftyperef.h" | 
| +#import "chrome/browser/ui/cocoa/tabs/tab_controller.h" | 
| +#import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h" | 
| +#import "chrome/browser/ui/cocoa/tabs/tab_view.h" | 
| +#import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h" | 
| + | 
| +const CGFloat kTearDistance = 36.0; | 
| +const NSTimeInterval kTearDuration = 0.333; | 
| + | 
| +@interface TabStripDragController (Private) | 
| +- (void)resetDragControllers; | 
| +- (NSArray*)dropTargetsForController:(TabWindowController*)dragController; | 
| +- (void)setWindowBackgroundVisibility:(BOOL)shouldBeVisible; | 
| +// TODO(davidben): When we stop supporting 10.5, this can be removed. | 
| +- (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache; | 
| +@end | 
| + | 
| +//////////////////////////////////////////////////////////////////////////////// | 
| + | 
| +@implementation TabStripDragController | 
| + | 
| +- (id)initWithTabStripController:(TabStripController*)controller { | 
| +  if ((self = [super init])) { | 
| +    tabStrip_ = controller; | 
| +  } | 
| +  return self; | 
| +} | 
| + | 
| +- (void)dealloc { | 
| +  [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 
| +  [super dealloc]; | 
| +} | 
| + | 
| +- (BOOL)tabCanBeDragged:(TabController*)tab { | 
| +  if ([[tab tabView] isClosing]) | 
| +    return NO; | 
| +  NSWindowController* controller = [sourceWindow_ windowController]; | 
| +  if ([controller isKindOfClass:[TabWindowController class]]) { | 
| +    TabWindowController* realController = | 
| +        static_cast<TabWindowController*>(controller); | 
| +    return [realController isTabDraggable:[tab tabView]]; | 
| +  } | 
| +  return YES; | 
| +} | 
| + | 
| +- (void)maybeStartDrag:(NSEvent*)theEvent forTab:(TabController*)tab { | 
| +  [self resetDragControllers]; | 
| + | 
| +  // Resolve overlay back to original window. | 
| +  sourceWindow_ = [[tab view] window]; | 
| +  if ([sourceWindow_ isKindOfClass:[NSPanel class]]) { | 
| +    sourceWindow_ = [sourceWindow_ parentWindow]; | 
| +  } | 
| + | 
| +  sourceWindowFrame_ = [sourceWindow_ frame]; | 
| +  sourceTabFrame_ = [[tab view] frame]; | 
| +  sourceController_ = [sourceWindow_ windowController]; | 
| +  draggedTab_ = tab; | 
| +  tabWasDragged_ = NO; | 
| +  tearTime_ = 0.0; | 
| +  draggingWithinTabStrip_ = YES; | 
| +  chromeIsVisible_ = NO; | 
| + | 
| +  // If there's more than one potential window to be a drop target, we want to | 
| +  // treat a drag of a tab just like dragging around a tab that's already | 
| +  // detached. Note that unit tests might have |-numberOfTabs| reporting zero | 
| +  // since the model won't be fully hooked up. We need to be prepared for that | 
| +  // and not send them into the "magnetic" codepath. | 
| +  NSArray* targets = [self dropTargetsForController:sourceController_]; | 
| +  moveWindowOnDrag_ = | 
| +      ([sourceController_ numberOfTabs] < 2 && ![targets count]) || | 
| +      ![self tabCanBeDragged:tab] || | 
| +      ![sourceController_ tabDraggingAllowed]; | 
| +  // If we are dragging a tab, a window with a single tab should immediately | 
| +  // snap off and not drag within the tab strip. | 
| +  if (!moveWindowOnDrag_) | 
| +    draggingWithinTabStrip_ = [sourceController_ numberOfTabs] > 1; | 
| + | 
| +  dragOrigin_ = [NSEvent mouseLocation]; | 
| + | 
| +  // When spinning the event loop, a tab can get detached, which could lead to | 
| +  // our own destruction. Keep ourselves around while spinning the loop. | 
| +  scoped_nsobject<TabStripDragController> keepAlive([self retain]); | 
| + | 
| +  // Because we move views between windows, we need to handle the event loop | 
| +  // ourselves. Ideally we should use the standard event loop. | 
| +  while (1) { | 
| +    const NSUInteger mask = | 
| +        NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSKeyUpMask; | 
| +    theEvent = | 
| +        [NSApp nextEventMatchingMask:mask | 
| +                           untilDate:[NSDate distantFuture] | 
| +                              inMode:NSDefaultRunLoopMode | 
| +                             dequeue:YES]; | 
| +    NSEventType type = [theEvent type]; | 
| +    if (type == NSKeyUp) { | 
| +      if ([theEvent keyCode] == kVK_Escape) { | 
| +        // Cancel the drag and restore the previous state. | 
| +        if (draggingWithinTabStrip_) { | 
| +          // Simply pretend the tab wasn't dragged (far enough). | 
| +          tabWasDragged_ = NO; | 
| +        } else { | 
| +          [targetController_ removePlaceholder]; | 
| +          if ([sourceController_ numberOfTabs] < 2) { | 
| +            // Revert to a single-tab window. | 
| +            targetController_ = nil; | 
| +          } else { | 
| +            // Change the target to the source controller. | 
| +            targetController_ = sourceController_; | 
| +            [targetController_ insertPlaceholderForTab:[tab tabView] | 
| +                                                 frame:sourceTabFrame_ | 
| +                                         yStretchiness:0]; | 
| +          } | 
| +        } | 
| +        // Simply end the drag at this point. | 
| +        [self endDrag:theEvent]; | 
| +        break; | 
| +      } | 
| +    } else if (type == NSLeftMouseDragged) { | 
| +      [self continueDrag:theEvent]; | 
| +    } else if (type == NSLeftMouseUp) { | 
| +      [[tab view] mouseUp:theEvent]; | 
| +      [self endDrag:theEvent]; | 
| +      break; | 
| +    } else { | 
| +      // TODO(viettrungluu): [crbug.com/23830] We can receive right-mouse-ups | 
| +      // (and maybe even others?) for reasons I don't understand. So we | 
| +      // explicitly check for both events we're expecting, and log others. We | 
| +      // should figure out what's going on. | 
| +      LOG(WARNING) << "Spurious event received of type " << type << "."; | 
| +    } | 
| +  } | 
| +} | 
| + | 
| +- (void)continueDrag:(NSEvent*)theEvent { | 
| +  // Special-case this to keep the logic below simpler. | 
| +  if (moveWindowOnDrag_) { | 
| +    if ([sourceController_ windowMovementAllowed]) { | 
| +      NSPoint thisPoint = [NSEvent mouseLocation]; | 
| +      NSPoint origin = sourceWindowFrame_.origin; | 
| +      origin.x += (thisPoint.x - dragOrigin_.x); | 
| +      origin.y += (thisPoint.y - dragOrigin_.y); | 
| +      [sourceWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; | 
| +    }  // else do nothing. | 
| +    return; | 
| +  } | 
| + | 
| +  // First, go through the magnetic drag cycle. We break out of this if | 
| +  // "stretchiness" ever exceeds a set amount. | 
| +  tabWasDragged_ = YES; | 
| + | 
| +  if (draggingWithinTabStrip_) { | 
| +    NSPoint thisPoint = [NSEvent mouseLocation]; | 
| +    CGFloat stretchiness = thisPoint.y - dragOrigin_.y; | 
| +    stretchiness = copysign(sqrtf(fabs(stretchiness))/sqrtf(kTearDistance), | 
| +                            stretchiness) / 2.0; | 
| +    CGFloat offset = thisPoint.x - dragOrigin_.x; | 
| +    if (fabsf(offset) > 100) stretchiness = 0; | 
| +    [sourceController_ insertPlaceholderForTab:[draggedTab_ tabView] | 
| +                                         frame:NSOffsetRect(sourceTabFrame_, | 
| +                                                            offset, 0) | 
| +                                 yStretchiness:stretchiness]; | 
| +    // Check that we haven't pulled the tab too far to start a drag. This | 
| +    // can include either pulling it too far down, or off the side of the tab | 
| +    // strip that would cause it to no longer be fully visible. | 
| +    BOOL stillVisible = | 
| +        [sourceController_ isTabFullyVisible:[draggedTab_ tabView]]; | 
| +    CGFloat tearForce = fabs(thisPoint.y - dragOrigin_.y); | 
| +    if ([sourceController_ tabTearingAllowed] && | 
| +        (tearForce > kTearDistance || !stillVisible)) { | 
| +      draggingWithinTabStrip_ = NO; | 
| +      // When you finally leave the strip, we treat that as the origin. | 
| +      dragOrigin_.x = thisPoint.x; | 
| +    } else { | 
| +      // Still dragging within the tab strip, wait for the next drag event. | 
| +      return; | 
| +    } | 
| +  } | 
| + | 
| +  // Do not start dragging until the user has "torn" the tab off by | 
| +  // moving more than 3 pixels. | 
| +  NSDate* targetDwellDate = nil;  // The date this target was first chosen. | 
| + | 
| +  NSPoint thisPoint = [NSEvent mouseLocation]; | 
| + | 
| +  // Iterate over possible targets checking for the one the mouse is in. | 
| +  // If the tab is just in the frame, bring the window forward to make it | 
| +  // easier to drop something there. If it's in the tab strip, set the new | 
| +  // target so that it pops into that window. We can't cache this because we | 
| +  // need the z-order to be correct. | 
| +  NSArray* targets = [self dropTargetsForController:draggedController_]; | 
| +  TabWindowController* newTarget = nil; | 
| +  for (TabWindowController* target in targets) { | 
| +    NSRect windowFrame = [[target window] frame]; | 
| +    if (NSPointInRect(thisPoint, windowFrame)) { | 
| +      [[target window] orderFront:self]; | 
| +      NSRect tabStripFrame = [[target tabStripView] frame]; | 
| +      tabStripFrame.origin = [[target window] | 
| +                              convertBaseToScreen:tabStripFrame.origin]; | 
| +      if (NSPointInRect(thisPoint, tabStripFrame)) { | 
| +        newTarget = target; | 
| +      } | 
| +      break; | 
| +    } | 
| +  } | 
| + | 
| +  // If we're now targeting a new window, re-layout the tabs in the old | 
| +  // target and reset how long we've been hovering over this new one. | 
| +  if (targetController_ != newTarget) { | 
| +    targetDwellDate = [NSDate date]; | 
| +    [targetController_ removePlaceholder]; | 
| +    targetController_ = newTarget; | 
| +    if (!newTarget) { | 
| +      tearTime_ = [NSDate timeIntervalSinceReferenceDate]; | 
| +      tearOrigin_ = [dragWindow_ frame].origin; | 
| +    } | 
| +  } | 
| + | 
| +  // Create or identify the dragged controller. | 
| +  if (!draggedController_) { | 
| +    // Get rid of any placeholder remaining in the original source window. | 
| +    [sourceController_ removePlaceholder]; | 
| + | 
| +    // Detach from the current window and put it in a new window. If there are | 
| +    // no more tabs remaining after detaching, the source window is about to | 
| +    // go away (it's been autoreleased) so we need to ensure we don't reference | 
| +    // it any more. In that case the new controller becomes our source | 
| +    // controller. | 
| +    draggedController_ = | 
| +        [sourceController_ detachTabToNewWindow:[draggedTab_ tabView]]; | 
| +    dragWindow_ = [draggedController_ window]; | 
| +    [dragWindow_ setAlphaValue:0.0]; | 
| +    if (![sourceController_ hasLiveTabs]) { | 
| +      sourceController_ = draggedController_; | 
| +      sourceWindow_ = dragWindow_; | 
| +    } | 
| + | 
| +    // If dragging the tab only moves the current window, do not show overlay | 
| +    // so that sheets stay on top of the window. | 
| +    // Bring the target window to the front and make sure it has a border. | 
| +    [dragWindow_ setLevel:NSFloatingWindowLevel]; | 
| +    [dragWindow_ setHasShadow:YES]; | 
| +    [dragWindow_ orderFront:nil]; | 
| +    [dragWindow_ makeMainWindow]; | 
| +    [draggedController_ showOverlay]; | 
| +    dragOverlay_ = [draggedController_ overlayWindow]; | 
| +    // Force the new tab button to be hidden. We'll reset it on mouse up. | 
| +    [draggedController_ showNewTabButton:NO]; | 
| +    tearTime_ = [NSDate timeIntervalSinceReferenceDate]; | 
| +    tearOrigin_ = sourceWindowFrame_.origin; | 
| +  } | 
| + | 
| +  // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by | 
| +  // some weird circumstance that doesn't first go through mouseDown:. We | 
| +  // really shouldn't go any farther. | 
| +  if (!draggedController_ || !sourceController_) | 
| +    return; | 
| + | 
| +  // When the user first tears off the window, we want slide the window to | 
| +  // the current mouse location (to reduce the jarring appearance). We do this | 
| +  // by calling ourselves back with additional mouseDragged calls (not actual | 
| +  // events). |tearProgress| is a normalized measure of how far through this | 
| +  // tear "animation" (of length kTearDuration) we are and has values [0..1]. | 
| +  // We use sqrt() so the animation is non-linear (slow down near the end | 
| +  // point). | 
| +  NSTimeInterval tearProgress = | 
| +      [NSDate timeIntervalSinceReferenceDate] - tearTime_; | 
| +  tearProgress /= kTearDuration;  // Normalize. | 
| +  tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0)); | 
| + | 
| +  // Move the dragged window to the right place on the screen. | 
| +  NSPoint origin = sourceWindowFrame_.origin; | 
| +  origin.x += (thisPoint.x - dragOrigin_.x); | 
| +  origin.y += (thisPoint.y - dragOrigin_.y); | 
| + | 
| +  if (tearProgress < 1) { | 
| +    // If the tear animation is not complete, call back to ourself with the | 
| +    // same event to animate even if the mouse isn't moving. We need to make | 
| +    // sure these get cancelled in mouseUp:. | 
| +    [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 
| +    [self performSelector:@selector(continueDrag:) | 
| +               withObject:theEvent | 
| +               afterDelay:1.0f/30.0f]; | 
| + | 
| +    // Set the current window origin based on how far we've progressed through | 
| +    // the tear animation. | 
| +    origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x; | 
| +    origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y; | 
| +  } | 
| + | 
| +  if (targetController_) { | 
| +    // In order to "snap" two windows of different sizes together at their | 
| +    // toolbar, we can't just use the origin of the target frame. We also have | 
| +    // to take into consideration the difference in height. | 
| +    NSRect targetFrame = [[targetController_ window] frame]; | 
| +    NSRect sourceFrame = [dragWindow_ frame]; | 
| +    origin.y = NSMinY(targetFrame) + | 
| +                (NSHeight(targetFrame) - NSHeight(sourceFrame)); | 
| +  } | 
| +  [dragWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)]; | 
| + | 
| +  // If we're not hovering over any window, make the window fully | 
| +  // opaque. Otherwise, find where the tab might be dropped and insert | 
| +  // a placeholder so it appears like it's part of that window. | 
| +  if (targetController_) { | 
| +    if (![[targetController_ window] isKeyWindow]) { | 
| +      // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) { | 
| +      [[targetController_ window] orderFront:nil]; | 
| +      targetDwellDate = nil; | 
| +    } | 
| + | 
| +    // Compute where placeholder should go and insert it into the | 
| +    // destination tab strip. | 
| +    TabView* draggedTabView = (TabView*)[draggedController_ activeTabView]; | 
| +    NSRect tabFrame = [draggedTabView frame]; | 
| +    tabFrame.origin = [dragWindow_ convertBaseToScreen:tabFrame.origin]; | 
| +    tabFrame.origin = [[targetController_ window] | 
| +                        convertScreenToBase:tabFrame.origin]; | 
| +    tabFrame = [[targetController_ tabStripView] | 
| +                convertRect:tabFrame fromView:nil]; | 
| +    [targetController_ insertPlaceholderForTab:[draggedTab_ tabView] | 
| +                                         frame:tabFrame | 
| +                                 yStretchiness:0]; | 
| +    [targetController_ layoutTabs]; | 
| +  } else { | 
| +    [dragWindow_ makeKeyAndOrderFront:nil]; | 
| +  } | 
| + | 
| +  // Adjust the visibility of the window background. If there is a drop target, | 
| +  // we want to hide the window background so the tab stands out for | 
| +  // positioning. If not, we want to show it so it looks like a new window will | 
| +  // be realized. | 
| +  BOOL chromeShouldBeVisible = targetController_ == nil; | 
| +  [self setWindowBackgroundVisibility:chromeShouldBeVisible]; | 
| +} | 
| + | 
| +- (void)endDrag:(NSEvent*)event { | 
| +  // Special-case this to keep the logic below simpler. | 
| +  if (moveWindowOnDrag_) | 
| +    return; | 
| + | 
| +  // Cancel any delayed -mouseDragged: requests that may still be pending. | 
| +  [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 
| + | 
| +  // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by | 
| +  // some weird circumstance that doesn't first go through mouseDown:. We | 
| +  // really shouldn't go any farther. | 
| +  if (!sourceController_) | 
| +    return; | 
| + | 
| +  // We are now free to re-display the new tab button in the window we're | 
| +  // dragging. It will show when the next call to -layoutTabs (which happens | 
| +  // indrectly by several of the calls below, such as removing the placeholder). | 
| +  [draggedController_ showNewTabButton:YES]; | 
| + | 
| +  if (draggingWithinTabStrip_) { | 
| +    if (tabWasDragged_) { | 
| +      // Move tab to new location. | 
| +      DCHECK([sourceController_ numberOfTabs]); | 
| +      TabWindowController* dropController = sourceController_; | 
| +      [dropController moveTabView:[dropController activeTabView] | 
| +                   fromController:nil]; | 
| +    } | 
| +  } else if (targetController_) { | 
| +    // Move between windows. If |targetController_| is nil, we're not dropping | 
| +    // into any existing window. | 
| +    NSView* draggedTabView = [draggedController_ activeTabView]; | 
| +    [targetController_ moveTabView:draggedTabView | 
| +                    fromController:draggedController_]; | 
| +    // Force redraw to avoid flashes of old content before returning to event | 
| +    // loop. | 
| +    [[targetController_ window] display]; | 
| +    [targetController_ showWindow:nil]; | 
| +    [draggedController_ removeOverlay]; | 
| +  } else { | 
| +    // Only move the window around on screen. Make sure it's set back to | 
| +    // normal state (fully opaque, has shadow, has key, etc). | 
| +    [draggedController_ removeOverlay]; | 
| +    // Don't want to re-show the window if it was closed during the drag. | 
| +    if ([dragWindow_ isVisible]) { | 
| +      [dragWindow_ setAlphaValue:1.0]; | 
| +      [dragOverlay_ setHasShadow:NO]; | 
| +      [dragWindow_ setHasShadow:YES]; | 
| +      [dragWindow_ makeKeyAndOrderFront:nil]; | 
| +    } | 
| +    [[draggedController_ window] setLevel:NSNormalWindowLevel]; | 
| +    [draggedController_ removePlaceholder]; | 
| +  } | 
| +  [sourceController_ removePlaceholder]; | 
| +  chromeIsVisible_ = YES; | 
| + | 
| +  [self resetDragControllers]; | 
| +} | 
| + | 
| +// Private ///////////////////////////////////////////////////////////////////// | 
| + | 
| +// Call to clear out transient weak references we hold during drags. | 
| +- (void)resetDragControllers { | 
| +  draggedController_ = nil; | 
| +  dragWindow_ = nil; | 
| +  dragOverlay_ = nil; | 
| +  sourceController_ = nil; | 
| +  sourceWindow_ = nil; | 
| +  targetController_ = nil; | 
| +  workspaceIDCache_.clear(); | 
| +} | 
| + | 
| +// Returns an array of controllers that could be a drop target, ordered front to | 
| +// back. It has to be of the appropriate class, and visible (obviously). Note | 
| +// that the window cannot be a target for itself. | 
| +- (NSArray*)dropTargetsForController:(TabWindowController*)dragController { | 
| +  NSMutableArray* targets = [NSMutableArray array]; | 
| +  NSWindow* dragWindow = [dragController window]; | 
| +  for (NSWindow* window in [NSApp orderedWindows]) { | 
| +    if (window == dragWindow) continue; | 
| +    if (![window isVisible]) continue; | 
| +    // Skip windows on the wrong space. | 
| +    if ([window respondsToSelector:@selector(isOnActiveSpace)]) { | 
| +      if (![window performSelector:@selector(isOnActiveSpace)]) | 
| +        continue; | 
| +    } else { | 
| +      // TODO(davidben): When we stop supporting 10.5, this can be | 
| +      // removed. | 
| +      // | 
| +      // We don't cache the workspace of |dragWindow| because it may | 
| +      // move around spaces. | 
| +      if ([self getWorkspaceID:dragWindow useCache:NO] != | 
| +          [self getWorkspaceID:window useCache:YES]) | 
| +        continue; | 
| +    } | 
| +    NSWindowController* controller = [window windowController]; | 
| +    if ([controller isKindOfClass:[TabWindowController class]]) { | 
| +      TabWindowController* realController = | 
| +          static_cast<TabWindowController*>(controller); | 
| +      if ([realController canReceiveFrom:dragController]) | 
| +        [targets addObject:controller]; | 
| +    } | 
| +  } | 
| +  return targets; | 
| +} | 
| + | 
| +// Sets whether the window background should be visible or invisible when | 
| +// dragging a tab. The background should be invisible when the mouse is over a | 
| +// potential drop target for the tab (the tab strip). It should be visible when | 
| +// there's no drop target so the window looks more fully realized and ready to | 
| +// become a stand-alone window. | 
| +- (void)setWindowBackgroundVisibility:(BOOL)shouldBeVisible { | 
| +  if (chromeIsVisible_ == shouldBeVisible) | 
| +    return; | 
| + | 
| +  // There appears to be a race-condition in CoreAnimation where if we use | 
| +  // animators to set the alpha values, we can't guarantee that we cancel them. | 
| +  // This has the side effect of sometimes leaving the dragged window | 
| +  // translucent or invisible. As a result, don't animate the alpha change. | 
| +  [[draggedController_ overlayWindow] setAlphaValue:1.0]; | 
| +  if (targetController_) { | 
| +    [dragWindow_ setAlphaValue:0.0]; | 
| +    [[draggedController_ overlayWindow] setHasShadow:YES]; | 
| +    [[targetController_ window] makeMainWindow]; | 
| +  } else { | 
| +    [dragWindow_ setAlphaValue:0.5]; | 
| +    [[draggedController_ overlayWindow] setHasShadow:NO]; | 
| +    [[draggedController_ window] makeMainWindow]; | 
| +  } | 
| +  chromeIsVisible_ = shouldBeVisible; | 
| +} | 
| + | 
| +// Returns the workspace id of |window|. If |useCache|, then lookup | 
| +// and remember the value in |workspaceIDCache_| until the end of the | 
| +// current drag. | 
| +- (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache { | 
| +  CGWindowID windowID = [window windowNumber]; | 
| +  if (useCache) { | 
| +    std::map<CGWindowID, int>::iterator iter = | 
| +        workspaceIDCache_.find(windowID); | 
| +    if (iter != workspaceIDCache_.end()) | 
| +      return iter->second; | 
| +  } | 
| + | 
| +  int workspace = -1; | 
| +  // It's possible to query in bulk, but probably not necessary. | 
| +  base::mac::ScopedCFTypeRef<CFArrayRef> windowIDs(CFArrayCreate( | 
| +      NULL, reinterpret_cast<const void **>(&windowID), 1, NULL)); | 
| +  base::mac::ScopedCFTypeRef<CFArrayRef> descriptions( | 
| +      CGWindowListCreateDescriptionFromArray(windowIDs)); | 
| +  DCHECK(CFArrayGetCount(descriptions.get()) <= 1); | 
| +  if (CFArrayGetCount(descriptions.get()) > 0) { | 
| +    CFDictionaryRef dict = static_cast<CFDictionaryRef>( | 
| +        CFArrayGetValueAtIndex(descriptions.get(), 0)); | 
| +    DCHECK(CFGetTypeID(dict) == CFDictionaryGetTypeID()); | 
| + | 
| +    // Sanity check the ID. | 
| +    CFNumberRef otherIDRef = (CFNumberRef)base::mac::GetValueFromDictionary( | 
| +        dict, kCGWindowNumber, CFNumberGetTypeID()); | 
| +    CGWindowID otherID; | 
| +    if (otherIDRef && | 
| +        CFNumberGetValue(otherIDRef, kCGWindowIDCFNumberType, &otherID) && | 
| +        otherID == windowID) { | 
| +      // And then get the workspace. | 
| +      CFNumberRef workspaceRef = (CFNumberRef)base::mac::GetValueFromDictionary( | 
| +          dict, kCGWindowWorkspace, CFNumberGetTypeID()); | 
| +      if (!workspaceRef || | 
| +          !CFNumberGetValue(workspaceRef, kCFNumberIntType, &workspace)) { | 
| +        workspace = -1; | 
| +      } | 
| +    } else { | 
| +      NOTREACHED(); | 
| +    } | 
| +  } | 
| +  if (useCache) { | 
| +    workspaceIDCache_[windowID] = workspace; | 
| +  } | 
| +  return workspace; | 
| +} | 
| + | 
| +@end | 
|  |