• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
10static const NSTimeInterval kMinTimeout = 0.0;
11static const NSTimeInterval kMaxTimeout = 5.0;
12
13// Drag distance threshold to activate click-hold; should be >= 0.
14static 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