// Copyright (c) 2012 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. #include "chrome/browser/ui/cocoa/panels/mouse_drag_controller.h" #include // kVK_Escape #import #include "base/logging.h" #include "base/mac/scoped_nsautorelease_pool.h" // The distance the user has to move the mouse while keeping the left button // down before panel resizing operation actually starts. const double kDragThreshold = 3.0; @implementation MouseDragController - (NSView*)client { return client_; } - (NSPoint)initialMouseLocation { return initialMouseLocation_; } - (BOOL)exceedsDragThreshold:(NSPoint)mouseLocation { float deltaX = fabs(initialMouseLocation_.x - mouseLocation.x); float deltaY = fabs(initialMouseLocation_.y - mouseLocation.y); return deltaX > kDragThreshold || deltaY > kDragThreshold; } - (BOOL)tryStartDrag:(NSEvent*)event { DCHECK(dragState_ == PANEL_DRAG_CAN_START); NSPoint mouseLocation = [event locationInWindow]; if (![self exceedsDragThreshold:mouseLocation]) return NO; // Mouse moved over threshold, start drag. dragState_ = PANEL_DRAG_IN_PROGRESS; [client_ dragStarted:initialMouseLocation_]; return YES; } - (void)cleanupAfterDrag { if (dragState_ == PANEL_DRAG_SUPPRESSED) return; dragState_ = PANEL_DRAG_SUPPRESSED; initialMouseLocation_ = NSZeroPoint; [client_ cleanupAfterDrag]; } - (MouseDragController*)initWithClient: (NSView*)client { client_ = client; dragState_ = PANEL_DRAG_SUPPRESSED; return self; } - (void)mouseDown:(NSEvent*)event { DCHECK(dragState_ == PANEL_DRAG_SUPPRESSED); dragState_ = PANEL_DRAG_CAN_START; initialMouseLocation_ = [event locationInWindow]; [client_ prepareForDrag]; } - (void)mouseDragged:(NSEvent*)event { if (dragState_ == PANEL_DRAG_SUPPRESSED) return; // In addition to events needed to control the drag operation, fetch the right // mouse click events and key down events and ignore them, to prevent their // accumulation in the queue and "playing out" when the mouse is released. const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSKeyUpMask | NSRightMouseDownMask | NSKeyDownMask ; while (true) { base::mac::ScopedNSAutoreleasePool autorelease_pool; BOOL keepGoing = YES; switch ([event type]) { case NSLeftMouseDragged: { // If drag didn't start yet, see if mouse moved far enough to start it. if (dragState_ == PANEL_DRAG_CAN_START && ![self tryStartDrag:event]) return; DCHECK(dragState_ == PANEL_DRAG_IN_PROGRESS); [client_ dragProgress:[event locationInWindow]]; break; } case NSKeyUp: if ([event keyCode] == kVK_Escape) { // The drag might not be started yet because of threshold, so check. if (dragState_ == PANEL_DRAG_IN_PROGRESS) [client_ dragEnded:YES]; keepGoing = NO; } break; case NSLeftMouseUp: // The drag might not be started yet because of threshold, so check. if (dragState_ == PANEL_DRAG_IN_PROGRESS) [client_ dragEnded:NO]; keepGoing = NO; break; case NSRightMouseDownMask: break; default: // Dequeue and ignore other mouse and key events so the Chrome context // menu does not come after right click on a page during Panel // resize, or the keystrokes are not 'accumulated' and entered // at once when the drag ends. break; } if (!keepGoing) break; autorelease_pool.Recycle(); event = [NSApp nextEventMatchingMask:mask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES]; } [self cleanupAfterDrag]; } - (void)mouseUp:(NSEvent*)event { if (dragState_ == PANEL_DRAG_SUPPRESSED) return; // The mouseUp while in drag should be processed by nested message loop // in mouseDragged: method. DCHECK(dragState_ != PANEL_DRAG_IN_PROGRESS); // Do cleanup in case the actual drag was not started (because of threshold). [self cleanupAfterDrag]; } @end