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