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.
5 #import "chrome/browser/ui/cocoa/clickhold_button_cell.h"
7 #include "base/logging.h"
9 // Minimum and maximum click-hold timeout.
10 static const NSTimeInterval kMinTimeout = 0.0;
11 static const NSTimeInterval kMaxTimeout = 5.0;
13 // Drag distance threshold to activate click-hold; should be >= 0.
14 static const CGFloat kDragDistThreshold = 2.5;
16 // See |-resetToDefaults| (and header file) for other default values.
18 @interface ClickHoldButtonCell (Private)
19 - (void)resetToDefaults;
20 - (BOOL)shouldExposeAccessibilityShowMenu;
21 @end // @interface ClickHoldButtonCell (Private)
23 @implementation ClickHoldButtonCell
25 @synthesize enableClickHold = enableClickHold_;
26 @synthesize enableRightClick = enableRightClick_;
27 @synthesize clickHoldTimeout = clickHoldTimeout_;
28 @synthesize trackOnlyInRect = trackOnlyInRect_;
29 @synthesize activateOnDrag = activateOnDrag_;
30 @synthesize clickHoldTarget = clickHoldTarget_;
31 @synthesize clickHoldAction = clickHoldAction_;
32 @synthesize accessibilityShowMenuTarget = accessibilityShowMenuTarget_;
33 @synthesize accessibilityShowMenuAction = accessibilityShowMenuAction_;
37 + (BOOL)prefersTrackingUntilMouseUp {
42 if ((self = [super init]))
43 [self resetToDefaults];
47 - (id)initWithCoder:(NSCoder*)decoder {
48 if ((self = [super initWithCoder:decoder]))
49 [self resetToDefaults];
53 - (id)initImageCell:(NSImage*)image {
54 if ((self = [super initImageCell:image]))
55 [self resetToDefaults];
59 - (id)initTextCell:(NSString*)string {
60 if ((self = [super initTextCell:string]))
61 [self resetToDefaults];
65 - (void)accessibilityPerformAction:(NSString*)action {
66 if ([action isEqualToString:NSAccessibilityShowMenuAction] &&
67 [self shouldExposeAccessibilityShowMenu]) {
68 NSControl* controlView = static_cast<NSControl*>([self controlView]);
70 [controlView sendAction:accessibilityShowMenuAction_
71 to:accessibilityShowMenuTarget_];
75 [super accessibilityPerformAction:action];
78 - (BOOL)startTrackingAt:(NSPoint)startPoint
79 inView:(NSView*)controlView {
80 return enableClickHold_ ? YES :
81 [super startTrackingAt:startPoint
85 - (BOOL)continueTracking:(NSPoint)lastPoint
86 at:(NSPoint)currentPoint
87 inView:(NSView*)controlView {
88 return enableClickHold_ ? YES :
89 [super continueTracking:lastPoint
94 - (BOOL)trackMouse:(NSEvent*)originalEvent
95 inRect:(NSRect)cellFrame
96 ofView:(NSView*)controlView
97 untilMouseUp:(BOOL)untilMouseUp {
98 if (!enableClickHold_) {
99 return [super trackMouse:originalEvent
102 untilMouseUp:untilMouseUp];
105 // If doing click-hold, track the mouse ourselves.
106 NSPoint currPoint = [controlView convertPoint:[originalEvent locationInWindow]
108 NSPoint lastPoint = currPoint;
109 NSPoint firstPoint = currPoint;
110 NSTimeInterval timeout =
111 MAX(MIN(clickHoldTimeout_, kMaxTimeout), kMinTimeout);
112 NSDate* clickHoldBailTime = [NSDate dateWithTimeIntervalSinceNow:timeout];
114 if (![self startTrackingAt:currPoint inView:controlView])
118 kContinueTrack, kStopClickHold, kStopMouseUp, kStopLeftRect, kStopNoContinue
119 } state = kContinueTrack;
121 NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
123 untilDate:clickHoldBailTime
124 inMode:NSEventTrackingRunLoopMode
126 currPoint = [controlView convertPoint:[event locationInWindow]
131 state = kStopClickHold;
133 // Drag? (If distance meets threshold.)
134 } else if (activateOnDrag_ && ([event type] == NSLeftMouseDragged)) {
135 CGFloat dx = currPoint.x - firstPoint.x;
136 CGFloat dy = currPoint.y - firstPoint.y;
137 if ((dx*dx + dy*dy) >= (kDragDistThreshold*kDragDistThreshold))
138 state = kStopClickHold;
141 } else if ([event type] == NSLeftMouseUp) {
142 state = kStopMouseUp;
144 // Stop tracking if mouse left frame rectangle (if requested to do so).
145 } else if (trackOnlyInRect_ && ![controlView mouse:currPoint
147 state = kStopLeftRect;
149 // Stop tracking if instructed to.
150 } else if (![self continueTracking:lastPoint
152 inView:controlView]) {
153 state = kStopNoContinue;
156 lastPoint = currPoint;
157 } while (state == kContinueTrack);
159 [self stopTracking:lastPoint
166 if (clickHoldAction_) {
167 [static_cast<NSControl*>(controlView) sendAction:clickHoldAction_
168 to:clickHoldTarget_];
174 [static_cast<NSControl*>(controlView) sendAction:[self action]
180 case kStopNoContinue:
184 NOTREACHED() << "Unknown terminating state!";
190 // Accessors and mutators:
192 - (NSArray*)accessibilityActionNames {
193 NSArray* actionNames = [super accessibilityActionNames];
194 if ([self shouldExposeAccessibilityShowMenu] &&
195 ![actionNames containsObject:NSAccessibilityShowMenuAction]) {
196 return [actionNames arrayByAddingObject:NSAccessibilityShowMenuAction];
202 @end // @implementation ClickHoldButtonCell
204 @implementation ClickHoldButtonCell (Private)
206 // Resets various members to defaults indicated in the header file. (Those
207 // without indicated defaults are *not* touched.) Please keep the values below
208 // in sync with the header file, and please be aware of side-effects on code
209 // which relies on the "published" defaults.
210 - (void)resetToDefaults {
211 [self setEnableClickHold:NO];
212 [self setClickHoldTimeout:0.25];
213 [self setTrackOnlyInRect:NO];
214 [self setActivateOnDrag:YES];
217 - (BOOL)shouldExposeAccessibilityShowMenu {
218 return (enableRightClick_ ||
219 (enableClickHold_ && clickHoldTimeout_ > kMinTimeout)) &&
220 accessibilityShowMenuAction_ && accessibilityShowMenuTarget_;
223 @end // @implementation ClickHoldButtonCell (Private)