Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / clickhold_button_cell.mm
blob870a35c8c1d25d86f27040b665d7faf694ba5920
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_;
35 // Overrides:
37 + (BOOL)prefersTrackingUntilMouseUp {
38   return NO;
41 - (id)init {
42   if ((self = [super init]))
43     [self resetToDefaults];
44   return self;
47 - (id)initWithCoder:(NSCoder*)decoder {
48   if ((self = [super initWithCoder:decoder]))
49     [self resetToDefaults];
50   return self;
53 - (id)initImageCell:(NSImage*)image {
54   if ((self = [super initImageCell:image]))
55     [self resetToDefaults];
56   return self;
59 - (id)initTextCell:(NSString*)string {
60   if ((self = [super initTextCell:string]))
61     [self resetToDefaults];
62   return self;
65 - (void)accessibilityPerformAction:(NSString*)action {
66   if ([action isEqualToString:NSAccessibilityShowMenuAction] &&
67       [self shouldExposeAccessibilityShowMenu]) {
68     NSControl* controlView = static_cast<NSControl*>([self controlView]);
69     if (controlView)
70       [controlView sendAction:accessibilityShowMenuAction_
71                            to:accessibilityShowMenuTarget_];
72     return;
73   }
75   [super accessibilityPerformAction:action];
78 - (BOOL)startTrackingAt:(NSPoint)startPoint
79                  inView:(NSView*)controlView {
80   return enableClickHold_ ? YES :
81                             [super startTrackingAt:startPoint
82                                             inView:controlView];
85 - (BOOL)continueTracking:(NSPoint)lastPoint
86                       at:(NSPoint)currentPoint
87                   inView:(NSView*)controlView {
88   return enableClickHold_ ? YES :
89                             [super continueTracking:lastPoint
90                                                  at:currentPoint
91                                              inView:controlView];
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
100                       inRect:cellFrame
101                       ofView:controlView
102                 untilMouseUp:untilMouseUp];
103   }
105   // If doing click-hold, track the mouse ourselves.
106   NSPoint currPoint = [controlView convertPoint:[originalEvent locationInWindow]
107                                        fromView:nil];
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])
115     return NO;
117   enum {
118     kContinueTrack, kStopClickHold, kStopMouseUp, kStopLeftRect, kStopNoContinue
119   } state = kContinueTrack;
120   do {
121     NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
122                                                    NSLeftMouseUpMask)
123                                         untilDate:clickHoldBailTime
124                                            inMode:NSEventTrackingRunLoopMode
125                                           dequeue:YES];
126     currPoint = [controlView convertPoint:[event locationInWindow]
127                                  fromView:nil];
129     // Time-out.
130     if (!event) {
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;
140     // Mouse up.
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
146                                                 inRect:cellFrame]) {
147       state = kStopLeftRect;
149     // Stop tracking if instructed to.
150     } else if (![self continueTracking:lastPoint
151                                     at:currPoint
152                                 inView:controlView]) {
153       state = kStopNoContinue;
154     }
156     lastPoint = currPoint;
157   } while (state == kContinueTrack);
159   [self stopTracking:lastPoint
160                   at:lastPoint
161               inView:controlView
162            mouseIsUp:NO];
164   switch (state) {
165     case kStopClickHold:
166       if (clickHoldAction_) {
167         [static_cast<NSControl*>(controlView) sendAction:clickHoldAction_
168                                                       to:clickHoldTarget_];
169       }
170       return YES;
172     case kStopMouseUp:
173       if ([self action]) {
174         [static_cast<NSControl*>(controlView) sendAction:[self action]
175                                                       to:[self target]];
176       }
177       return YES;
179     case kStopLeftRect:
180     case kStopNoContinue:
181       return NO;
183     default:
184       NOTREACHED() << "Unknown terminating state!";
185   }
187   return NO;
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];
197   }
199   return actionNames;
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)