// Copyright (c) 2009 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/cocoa/clickhold_button_cell.h" #include "base/logging.h" // Minimum and maximum click-hold timeout. static const NSTimeInterval kMinTimeout = 0.0; static const NSTimeInterval kMaxTimeout = 5.0; // Drag distance threshold to activate click-hold; should be >= 0. static const CGFloat kDragDistThreshold = 2.5; // See |-resetToDefaults| (and header file) for other default values. @interface ClickHoldButtonCell (Private) - (void)resetToDefaults; @end // @interface ClickHoldButtonCell (Private) @implementation ClickHoldButtonCell // Overrides: + (BOOL)prefersTrackingUntilMouseUp { return NO; } - (id)init { if ((self = [super init])) [self resetToDefaults]; return self; } - (id)initWithCoder:(NSCoder*)decoder { if ((self = [super initWithCoder:decoder])) [self resetToDefaults]; return self; } - (id)initImageCell:(NSImage*)image { if ((self = [super initImageCell:image])) [self resetToDefaults]; return self; } - (id)initTextCell:(NSString*)string { if ((self = [super initTextCell:string])) [self resetToDefaults]; return self; } - (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView*)controlView { return enableClickHold_ ? YES : [super startTrackingAt:startPoint inView:controlView]; } - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView*)controlView { return enableClickHold_ ? YES : [super continueTracking:lastPoint at:currentPoint inView:controlView]; } - (BOOL)trackMouse:(NSEvent*)originalEvent inRect:(NSRect)cellFrame ofView:(NSView*)controlView untilMouseUp:(BOOL)untilMouseUp { if (!enableClickHold_) { return [super trackMouse:originalEvent inRect:cellFrame ofView:controlView untilMouseUp:untilMouseUp]; } // If doing click-hold, track the mouse ourselves. NSPoint currPoint = [controlView convertPoint:[originalEvent locationInWindow] fromView:nil]; NSPoint lastPoint = currPoint; NSPoint firstPoint = currPoint; NSTimeInterval timeout = MAX(MIN(clickHoldTimeout_, kMaxTimeout), kMinTimeout); NSDate* clickHoldBailTime = [NSDate dateWithTimeIntervalSinceNow:timeout]; if (![self startTrackingAt:currPoint inView:controlView]) return NO; enum { kContinueTrack, kStopClickHold, kStopMouseUp, kStopLeftRect, kStopNoContinue } state = kContinueTrack; do { NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask) untilDate:clickHoldBailTime inMode:NSEventTrackingRunLoopMode dequeue:YES]; currPoint = [controlView convertPoint:[event locationInWindow] fromView:nil]; // Time-out. if (!event) { state = kStopClickHold; // Drag? (If distance meets threshold.) } else if (activateOnDrag_ && ([event type] == NSLeftMouseDragged)) { CGFloat dx = currPoint.x - firstPoint.x; CGFloat dy = currPoint.y - firstPoint.y; if ((dx*dx + dy*dy) >= (kDragDistThreshold*kDragDistThreshold)) state = kStopClickHold; // Mouse up. } else if ([event type] == NSLeftMouseUp) { state = kStopMouseUp; // Stop tracking if mouse left frame rectangle (if requested to do so). } else if (trackOnlyInRect_ && ![controlView mouse:currPoint inRect:cellFrame]) { state = kStopLeftRect; // Stop tracking if instructed to. } else if (![self continueTracking:lastPoint at:currPoint inView:controlView]) { state = kStopNoContinue; } lastPoint = currPoint; } while (state == kContinueTrack); [self stopTracking:lastPoint at:lastPoint inView:controlView mouseIsUp:NO]; switch (state) { case kStopClickHold: if (clickHoldAction_) { [static_cast(controlView) sendAction:clickHoldAction_ to:clickHoldTarget_]; } return YES; case kStopMouseUp: if ([self action]) { [static_cast(controlView) sendAction:[self action] to:[self target]]; } return YES; case kStopLeftRect: case kStopNoContinue: return NO; default: NOTREACHED() << "Unknown terminating state!"; } return NO; } // Accessors and mutators: @synthesize enableClickHold = enableClickHold_; @synthesize clickHoldTimeout = clickHoldTimeout_; @synthesize trackOnlyInRect = trackOnlyInRect_; @synthesize activateOnDrag = activateOnDrag_; @synthesize clickHoldTarget = clickHoldTarget_; @synthesize clickHoldAction = clickHoldAction_; @end // @implementation ClickHoldButtonCell @implementation ClickHoldButtonCell (Private) // Resets various members to defaults indicated in the header file. (Those // without indicated defaults are *not* touched.) Please keep the values below // in sync with the header file, and please be aware of side-effects on code // which relies on the "published" defaults. - (void)resetToDefaults { [self setEnableClickHold:NO]; [self setClickHoldTimeout:0.25]; [self setTrackOnlyInRect:NO]; [self setActivateOnDrag:YES]; } @end // @implementation ClickHoldButtonCell (Private)