1 // Copyright (c) 2009 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/clickhold_button_cell.h" 6 7 #include "base/logging.h" 8 9 // Minimum and maximum click-hold timeout. 10 static const NSTimeInterval kMinTimeout = 0.0; 11 static const NSTimeInterval kMaxTimeout = 5.0; 12 13 // Drag distance threshold to activate click-hold; should be >= 0. 14 static const CGFloat kDragDistThreshold = 2.5; 15 16 // See |-resetToDefaults| (and header file) for other default values. 17 18 @interface ClickHoldButtonCell (Private) 19 - (void)resetToDefaults; 20 @end // @interface ClickHoldButtonCell (Private) 21 22 @implementation ClickHoldButtonCell 23 24 @synthesize enableClickHold = enableClickHold_; 25 @synthesize clickHoldTimeout = clickHoldTimeout_; 26 @synthesize trackOnlyInRect = trackOnlyInRect_; 27 @synthesize activateOnDrag = activateOnDrag_; 28 @synthesize clickHoldTarget = clickHoldTarget_; 29 @synthesize clickHoldAction = clickHoldAction_; 30 31 // Overrides: 32 33 + (BOOL)prefersTrackingUntilMouseUp { 34 return NO; 35 } 36 37 - (id)init { 38 if ((self = [super init])) 39 [self resetToDefaults]; 40 return self; 41 } 42 43 - (id)initWithCoder:(NSCoder*)decoder { 44 if ((self = [super initWithCoder:decoder])) 45 [self resetToDefaults]; 46 return self; 47 } 48 49 - (id)initImageCell:(NSImage*)image { 50 if ((self = [super initImageCell:image])) 51 [self resetToDefaults]; 52 return self; 53 } 54 55 - (id)initTextCell:(NSString*)string { 56 if ((self = [super initTextCell:string])) 57 [self resetToDefaults]; 58 return self; 59 } 60 61 - (BOOL)startTrackingAt:(NSPoint)startPoint 62 inView:(NSView*)controlView { 63 return enableClickHold_ ? YES : 64 [super startTrackingAt:startPoint 65 inView:controlView]; 66 } 67 68 - (BOOL)continueTracking:(NSPoint)lastPoint 69 at:(NSPoint)currentPoint 70 inView:(NSView*)controlView { 71 return enableClickHold_ ? YES : 72 [super continueTracking:lastPoint 73 at:currentPoint 74 inView:controlView]; 75 } 76 77 - (BOOL)trackMouse:(NSEvent*)originalEvent 78 inRect:(NSRect)cellFrame 79 ofView:(NSView*)controlView 80 untilMouseUp:(BOOL)untilMouseUp { 81 if (!enableClickHold_) { 82 return [super trackMouse:originalEvent 83 inRect:cellFrame 84 ofView:controlView 85 untilMouseUp:untilMouseUp]; 86 } 87 88 // If doing click-hold, track the mouse ourselves. 89 NSPoint currPoint = [controlView convertPoint:[originalEvent locationInWindow] 90 fromView:nil]; 91 NSPoint lastPoint = currPoint; 92 NSPoint firstPoint = currPoint; 93 NSTimeInterval timeout = 94 MAX(MIN(clickHoldTimeout_, kMaxTimeout), kMinTimeout); 95 NSDate* clickHoldBailTime = [NSDate dateWithTimeIntervalSinceNow:timeout]; 96 97 if (![self startTrackingAt:currPoint inView:controlView]) 98 return NO; 99 100 enum { 101 kContinueTrack, kStopClickHold, kStopMouseUp, kStopLeftRect, kStopNoContinue 102 } state = kContinueTrack; 103 do { 104 NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask | 105 NSLeftMouseUpMask) 106 untilDate:clickHoldBailTime 107 inMode:NSEventTrackingRunLoopMode 108 dequeue:YES]; 109 currPoint = [controlView convertPoint:[event locationInWindow] 110 fromView:nil]; 111 112 // Time-out. 113 if (!event) { 114 state = kStopClickHold; 115 116 // Drag? (If distance meets threshold.) 117 } else if (activateOnDrag_ && ([event type] == NSLeftMouseDragged)) { 118 CGFloat dx = currPoint.x - firstPoint.x; 119 CGFloat dy = currPoint.y - firstPoint.y; 120 if ((dx*dx + dy*dy) >= (kDragDistThreshold*kDragDistThreshold)) 121 state = kStopClickHold; 122 123 // Mouse up. 124 } else if ([event type] == NSLeftMouseUp) { 125 state = kStopMouseUp; 126 127 // Stop tracking if mouse left frame rectangle (if requested to do so). 128 } else if (trackOnlyInRect_ && ![controlView mouse:currPoint 129 inRect:cellFrame]) { 130 state = kStopLeftRect; 131 132 // Stop tracking if instructed to. 133 } else if (![self continueTracking:lastPoint 134 at:currPoint 135 inView:controlView]) { 136 state = kStopNoContinue; 137 } 138 139 lastPoint = currPoint; 140 } while (state == kContinueTrack); 141 142 [self stopTracking:lastPoint 143 at:lastPoint 144 inView:controlView 145 mouseIsUp:NO]; 146 147 switch (state) { 148 case kStopClickHold: 149 if (clickHoldAction_) { 150 [static_cast<NSControl*>(controlView) sendAction:clickHoldAction_ 151 to:clickHoldTarget_]; 152 } 153 return YES; 154 155 case kStopMouseUp: 156 if ([self action]) { 157 [static_cast<NSControl*>(controlView) sendAction:[self action] 158 to:[self target]]; 159 } 160 return YES; 161 162 case kStopLeftRect: 163 case kStopNoContinue: 164 return NO; 165 166 default: 167 NOTREACHED() << "Unknown terminating state!"; 168 } 169 170 return NO; 171 } 172 173 @end // @implementation ClickHoldButtonCell 174 175 @implementation ClickHoldButtonCell (Private) 176 177 // Resets various members to defaults indicated in the header file. (Those 178 // without indicated defaults are *not* touched.) Please keep the values below 179 // in sync with the header file, and please be aware of side-effects on code 180 // which relies on the "published" defaults. 181 - (void)resetToDefaults { 182 [self setEnableClickHold:NO]; 183 [self setClickHoldTimeout:0.25]; 184 [self setTrackOnlyInRect:NO]; 185 [self setActivateOnDrag:YES]; 186 } 187 188 @end // @implementation ClickHoldButtonCell (Private) 189