• 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@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